activeentity 0.0.1.beta14 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +24 -7
  4. data/Rakefile +7 -7
  5. data/lib/active_entity.rb +30 -7
  6. data/lib/active_entity/aggregations.rb +2 -1
  7. data/lib/active_entity/associations.rb +46 -24
  8. data/lib/active_entity/associations/{embedded → embeds}/association.rb +2 -2
  9. data/lib/active_entity/associations/{embedded → embeds}/builder/association.rb +1 -1
  10. data/lib/active_entity/associations/{embedded → embeds}/builder/collection_association.rb +1 -1
  11. data/lib/active_entity/associations/{embedded → embeds}/builder/embedded_in.rb +1 -1
  12. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_many.rb +1 -1
  13. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_one.rb +1 -1
  14. data/lib/active_entity/associations/{embedded → embeds}/builder/singular_association.rb +1 -1
  15. data/lib/active_entity/associations/{embedded → embeds}/collection_association.rb +1 -1
  16. data/lib/active_entity/associations/{embedded → embeds}/collection_proxy.rb +2 -2
  17. data/lib/active_entity/associations/{embedded → embeds}/embedded_in_association.rb +1 -1
  18. data/lib/active_entity/associations/{embedded → embeds}/embeds_many_association.rb +1 -1
  19. data/lib/active_entity/associations/{embedded → embeds}/embeds_one_association.rb +2 -1
  20. data/lib/active_entity/associations/{embedded → embeds}/singular_association.rb +1 -1
  21. data/lib/active_entity/attribute_assignment.rb +10 -8
  22. data/lib/active_entity/attribute_methods.rb +52 -61
  23. data/lib/active_entity/attribute_methods/before_type_cast.rb +6 -6
  24. data/lib/active_entity/attribute_methods/dirty.rb +13 -0
  25. data/lib/active_entity/attribute_methods/primary_key.rb +6 -8
  26. data/lib/active_entity/attribute_methods/query.rb +11 -8
  27. data/lib/active_entity/attribute_methods/read.rb +10 -13
  28. data/lib/active_entity/attribute_methods/time_zone_conversion.rb +2 -0
  29. data/lib/active_entity/attribute_methods/write.rb +16 -25
  30. data/lib/active_entity/attributes.rb +76 -2
  31. data/lib/active_entity/base.rb +3 -14
  32. data/lib/active_entity/{define_callbacks.rb → callbacks.rb} +5 -1
  33. data/lib/active_entity/core.rb +97 -39
  34. data/lib/active_entity/enum.rb +30 -4
  35. data/lib/active_entity/errors.rb +0 -11
  36. data/lib/active_entity/gem_version.rb +4 -4
  37. data/lib/active_entity/inheritance.rb +4 -106
  38. data/lib/active_entity/integration.rb +1 -1
  39. data/lib/active_entity/model_schema.rb +0 -12
  40. data/lib/active_entity/nested_attributes.rb +5 -12
  41. data/lib/active_entity/railtie.rb +61 -1
  42. data/lib/active_entity/readonly_attributes.rb +9 -1
  43. data/lib/active_entity/reflection.rb +22 -19
  44. data/lib/active_entity/serialization.rb +9 -6
  45. data/lib/active_entity/store.rb +51 -2
  46. data/lib/active_entity/type.rb +8 -8
  47. data/lib/active_entity/type/registry.rb +5 -5
  48. data/lib/active_entity/{validate_embedded_association.rb → validate_embeds_association.rb} +6 -6
  49. data/lib/active_entity/validations.rb +2 -6
  50. data/lib/active_entity/validations/associated.rb +1 -1
  51. data/lib/active_entity/validations/{uniqueness_in_embedding.rb → uniqueness_in_embeds.rb} +1 -1
  52. data/lib/active_entity/validations/uniqueness_on_active_record.rb +42 -47
  53. metadata +33 -36
  54. data/lib/active_entity/type/decimal_without_scale.rb +0 -15
  55. data/lib/active_entity/type/hash_lookup_type_map.rb +0 -25
  56. data/lib/active_entity/type/type_map.rb +0 -62
  57. data/lib/tasks/active_entity_tasks.rake +0 -6
@@ -2,10 +2,11 @@
2
2
 
3
3
  module ActiveEntity
4
4
  module Associations
5
- module Embedded
5
+ module Embeds
6
6
  # = Active Entity Has One Association
7
7
  class EmbedsOneAssociation < SingularAssociation #:nodoc:
8
8
  private
9
+
9
10
  def replace(record)
10
11
  self.target =
11
12
  if record.is_a? reflection.klass
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveEntity
4
4
  module Associations
5
- module Embedded
5
+ module Embeds
6
6
  class SingularAssociation < Association #:nodoc:
7
7
  # Implements the reader method, e.g. foo.bar for Foo.has_one :bar
8
8
  def reader
@@ -9,20 +9,22 @@ module ActiveEntity
9
9
  private
10
10
 
11
11
  def _assign_attributes(attributes)
12
- multi_parameter_attributes = {}
13
- nested_parameter_attributes = {}
12
+ multi_parameter_attributes = nested_parameter_attributes = nil
14
13
 
15
14
  attributes.each do |k, v|
16
- if k.to_s.include?("(")
17
- multi_parameter_attributes[k] = attributes.delete(k)
15
+ key = k.to_s
16
+
17
+ if key.include?("(")
18
+ (multi_parameter_attributes ||= {})[key] = v
18
19
  elsif v.is_a?(Hash)
19
- nested_parameter_attributes[k] = attributes.delete(k)
20
+ (nested_parameter_attributes ||= {})[key] = v
21
+ else
22
+ _assign_attribute(key, v)
20
23
  end
21
24
  end
22
- super(attributes)
23
25
 
24
- assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
25
- assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
26
+ assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
27
+ assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
26
28
  end
27
29
 
28
30
  # Assign any deferred nested attributes after the base attributes have been set.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mutex_m"
4
+ require "active_support/core_ext/enumerable"
4
5
 
5
6
  module ActiveEntity
6
7
  # = Active Entity Attribute Methods
@@ -16,6 +17,7 @@ module ActiveEntity
16
17
  include Query
17
18
  include PrimaryKey
18
19
  include TimeZoneConversion
20
+ include Dirty
19
21
  include Serialization
20
22
  end
21
23
 
@@ -25,6 +27,17 @@ module ActiveEntity
25
27
  include Mutex_m
26
28
  end
27
29
 
30
+ class << self
31
+ def dangerous_attribute_methods # :nodoc:
32
+ @dangerous_attribute_methods ||= (
33
+ Base.instance_methods +
34
+ Base.private_instance_methods -
35
+ Base.superclass.instance_methods -
36
+ Base.superclass.private_instance_methods
37
+ ).map { |m| -m.to_s }.to_set.freeze
38
+ end
39
+ end
40
+
28
41
  module ClassMethods
29
42
  def inherited(child_class) #:nodoc:
30
43
  child_class.initialize_generated_modules
@@ -32,7 +45,8 @@ module ActiveEntity
32
45
  end
33
46
 
34
47
  def initialize_generated_modules # :nodoc:
35
- @generated_attribute_methods = GeneratedAttributeMethods.new
48
+ @generated_attribute_methods = const_set(:GeneratedAttributeMethods, GeneratedAttributeMethods.new)
49
+ private_constant :GeneratedAttributeMethods
36
50
  @attribute_methods_generated = false
37
51
  include @generated_attribute_methods
38
52
 
@@ -93,7 +107,7 @@ module ActiveEntity
93
107
  # A method name is 'dangerous' if it is already (re)defined by Active Entity, but
94
108
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
95
109
  def dangerous_attribute_method?(name) # :nodoc:
96
- method_defined_within?(name, Base)
110
+ ::ActiveEntity::AttributeMethods.dangerous_attribute_methods.include?(name.to_s)
97
111
  end
98
112
 
99
113
  def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
@@ -111,13 +125,11 @@ module ActiveEntity
111
125
  # A class method is 'dangerous' if it is already (re)defined by Active Entity, but
112
126
  # not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
113
127
  def dangerous_class_method?(method_name)
114
- RESTRICTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
115
- end
128
+ return true if RESTRICTED_CLASS_METHODS.include?(method_name.to_s)
116
129
 
117
- def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
118
- if klass.respond_to?(name, true)
119
- if superklass.respond_to?(name, true)
120
- klass.method(name).owner != superklass.method(name).owner
130
+ if Base.respond_to?(method_name, true)
131
+ if Object.respond_to?(method_name, true)
132
+ Base.method(method_name).owner != Object.method(method_name).owner
121
133
  else
122
134
  true
123
135
  end
@@ -126,8 +138,8 @@ module ActiveEntity
126
138
  end
127
139
  end
128
140
 
129
- # Returns an array of column names as strings if it's not an abstract class and
130
- # table exists. Otherwise it returns an empty array.
141
+ # Returns an array of column names as strings if it's not an abstract class.
142
+ # Otherwise it returns an empty array.
131
143
  #
132
144
  # class Person < ActiveEntity::Base
133
145
  # end
@@ -147,25 +159,40 @@ module ActiveEntity
147
159
  # class Person < ActiveEntity::Base
148
160
  # end
149
161
  #
150
- # Person.has_attribute?('name') # => true
151
- # Person.has_attribute?(:age) # => true
152
- # Person.has_attribute?(:nothing) # => false
162
+ # Person.has_attribute?('name') # => true
163
+ # Person.has_attribute?('new_name') # => true
164
+ # Person.has_attribute?(:age) # => true
165
+ # Person.has_attribute?(:nothing) # => false
153
166
  def has_attribute?(attr_name)
154
- attribute_types.key?(attr_name.to_s)
167
+ attr_name = attr_name.to_s
168
+ attr_name = attribute_aliases[attr_name] || attr_name
169
+ attribute_types.key?(attr_name)
170
+ end
171
+
172
+ def _has_attribute?(attr_name) # :nodoc:
173
+ attribute_types.key?(attr_name)
155
174
  end
156
175
  end
157
176
 
158
177
  # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
159
178
  #
160
179
  # class Person < ActiveEntity::Base
180
+ # alias_attribute :new_name, :name
161
181
  # end
162
182
  #
163
183
  # person = Person.new
164
- # person.has_attribute?(:name) # => true
165
- # person.has_attribute?('age') # => true
166
- # person.has_attribute?(:nothing) # => false
184
+ # person.has_attribute?(:name) # => true
185
+ # person.has_attribute?(:new_name) # => true
186
+ # person.has_attribute?('age') # => true
187
+ # person.has_attribute?(:nothing) # => false
167
188
  def has_attribute?(attr_name)
168
- @attributes.key?(attr_name.to_s)
189
+ attr_name = attr_name.to_s
190
+ attr_name = self.class.attribute_aliases[attr_name] || attr_name
191
+ @attributes.key?(attr_name)
192
+ end
193
+
194
+ def _has_attribute?(attr_name) # :nodoc:
195
+ @attributes.key?(attr_name)
169
196
  end
170
197
 
171
198
  # Returns an array of names for the attributes available on this object.
@@ -209,6 +236,7 @@ module ActiveEntity
209
236
  # person.attribute_for_inspect(:tag_ids)
210
237
  # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
211
238
  def attribute_for_inspect(attr_name)
239
+ attr_name = attr_name.to_s
212
240
  value = _read_attribute(attr_name)
213
241
  format_for_inspect(value)
214
242
  end
@@ -228,8 +256,9 @@ module ActiveEntity
228
256
  # task.is_done = true
229
257
  # task.attribute_present?(:title) # => true
230
258
  # task.attribute_present?(:is_done) # => true
231
- def attribute_present?(attribute)
232
- value = _read_attribute(attribute)
259
+ def attribute_present?(attr_name)
260
+ attr_name = attr_name.to_s
261
+ value = _read_attribute(attr_name)
233
262
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
234
263
  end
235
264
 
@@ -268,51 +297,13 @@ module ActiveEntity
268
297
  write_attribute(attr_name, value)
269
298
  end
270
299
 
271
- # Returns the name of all database fields which have been read from this
272
- # model. This can be useful in development mode to determine which fields
273
- # need to be selected. For performance critical pages, selecting only the
274
- # required fields can be an easy performance win (assuming you aren't using
275
- # all of the fields on the model).
276
- #
277
- # For example:
278
- #
279
- # class PostsController < ActionController::Base
280
- # after_action :print_accessed_fields, only: :index
281
- #
282
- # def index
283
- # @posts = Post.all
284
- # end
285
- #
286
- # private
287
- #
288
- # def print_accessed_fields
289
- # p @posts.first.accessed_fields
290
- # end
291
- # end
292
- #
293
- # Which allows you to quickly change your code to:
294
- #
295
- # class PostsController < ActionController::Base
296
- # def index
297
- # @posts = Post.select(:id, :title, :author_id, :updated_at)
298
- # end
299
- # end
300
- def accessed_fields
301
- @attributes.accessed
302
- end
303
-
304
300
  private
301
+
305
302
  def attribute_method?(attr_name)
306
303
  # We check defined? because Syck calls respond_to? before actually calling initialize.
307
304
  defined?(@attributes) && @attributes.key?(attr_name)
308
305
  end
309
306
 
310
- def attributes_with_values(attribute_names)
311
- attribute_names.each_with_object({}) do |name, attrs|
312
- attrs[name] = _read_attribute(name)
313
- end
314
- end
315
-
316
307
  def format_for_inspect(value)
317
308
  if value.is_a?(String) && value.length > 50
318
309
  "#{value[0, 50]}...".inspect
@@ -323,8 +314,8 @@ module ActiveEntity
323
314
  end
324
315
  end
325
316
 
326
- def readonly_attribute?(name)
327
- self.class.readonly_attributes.include?(name)
317
+ def pk_attribute?(name)
318
+ name == @primary_key
328
319
  end
329
320
  end
330
321
  end
@@ -46,7 +46,7 @@ module ActiveEntity
46
46
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
47
47
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
48
48
  def read_attribute_before_type_cast(attr_name)
49
- @attributes[attr_name.to_s].value_before_type_cast
49
+ attribute_before_type_cast(attr_name.to_s)
50
50
  end
51
51
 
52
52
  # Returns a hash of attributes before typecasting and deserialization.
@@ -65,13 +65,13 @@ module ActiveEntity
65
65
 
66
66
  private
67
67
 
68
- # Handle *_before_type_cast for method_missing.
69
- def attribute_before_type_cast(attribute_name)
70
- read_attribute_before_type_cast(attribute_name)
68
+ # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
69
+ def attribute_before_type_cast(attr_name)
70
+ @attributes[attr_name].value_before_type_cast
71
71
  end
72
72
 
73
- def attribute_came_from_user?(attribute_name)
74
- @attributes[attribute_name].came_from_user?
73
+ def attribute_came_from_user?(attr_name)
74
+ @attributes[attr_name].came_from_user?
75
75
  end
76
76
  end
77
77
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+
5
+ module ActiveEntity
6
+ module AttributeMethods
7
+ module Dirty
8
+ extend ActiveSupport::Concern
9
+
10
+ include ActiveModel::Dirty
11
+ end
12
+ end
13
+ end
@@ -16,29 +16,27 @@ module ActiveEntity
16
16
 
17
17
  # Returns the primary key column's value.
18
18
  def id
19
- primary_key = self.class.primary_key
20
- _read_attribute(primary_key) if primary_key
19
+ _read_attribute(@primary_key)
21
20
  end
22
21
 
23
22
  # Sets the primary key column's value.
24
23
  def id=(value)
25
- primary_key = self.class.primary_key
26
- _write_attribute(primary_key, value) if primary_key
24
+ _write_attribute(@primary_key, value)
27
25
  end
28
26
 
29
27
  # Queries the primary key column's value.
30
28
  def id?
31
- query_attribute(self.class.primary_key)
29
+ query_attribute(@primary_key)
32
30
  end
33
31
 
34
32
  # Returns the primary key column's value before type cast.
35
33
  def id_before_type_cast
36
- read_attribute_before_type_cast(self.class.primary_key)
34
+ read_attribute_before_type_cast(@primary_key)
37
35
  end
38
36
 
39
37
  # Returns the primary key column's previous value.
40
38
  def id_was
41
- attribute_was(self.class.primary_key)
39
+ attribute_was(@primary_key)
42
40
  end
43
41
 
44
42
  private
@@ -90,7 +88,7 @@ module ActiveEntity
90
88
  #
91
89
  # Project.primary_key # => "foo_id"
92
90
  def primary_key=(value)
93
- @primary_key = value&.to_s
91
+ @primary_key = value && -value.to_s
94
92
  end
95
93
  end
96
94
  end
@@ -16,20 +16,23 @@ module ActiveEntity
16
16
  when true then true
17
17
  when false, nil then false
18
18
  else
19
- if Numeric === value || value !~ /[^0-9]/
20
- !value.to_i.zero?
19
+ if !type_for_attribute(attr_name) { false }
20
+ if Numeric === value || !value.match?(/[^0-9]/)
21
+ !value.to_i.zero?
22
+ else
23
+ return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
24
+ !value.blank?
25
+ end
26
+ elsif value.respond_to?(:zero?)
27
+ !value.zero?
21
28
  else
22
- return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
23
29
  !value.blank?
24
30
  end
25
31
  end
26
32
  end
27
33
 
28
- private
29
- # Handle *? for method_missing.
30
- def attribute?(attribute_name)
31
- query_attribute(attribute_name)
32
- end
34
+ alias :attribute? :query_attribute
35
+ private :attribute?
33
36
  end
34
37
  end
35
38
  end
@@ -8,16 +8,14 @@ module ActiveEntity
8
8
  module ClassMethods # :nodoc:
9
9
  private
10
10
 
11
- def define_method_attribute(name)
11
+ def define_method_attribute(name, owner:)
12
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
- generated_attribute_methods, name
13
+ owner, name
14
14
  ) do |temp_method_name, attr_name_expr|
15
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
16
- def #{temp_method_name}
17
- name = #{attr_name_expr}
18
- _read_attribute(name) { |n| missing_attribute(n, caller) }
19
- end
20
- RUBY
15
+ owner <<
16
+ "def #{temp_method_name}" <<
17
+ " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
18
+ "end"
21
19
  end
22
20
  end
23
21
  end
@@ -27,17 +25,16 @@ module ActiveEntity
27
25
  # to a date object, like Date.new(2004, 12, 12)).
28
26
  def read_attribute(attr_name, &block)
29
27
  name = attr_name.to_s
30
- if self.class.attribute_alias?(name)
31
- name = self.class.attribute_alias(name)
32
- end
28
+ name = self.class.attribute_aliases[name] || name
33
29
 
34
- _read_attribute(name, &block)
30
+ name = @primary_key if name == "id" && @primary_key
31
+ @attributes.fetch_value(name, &block)
35
32
  end
36
33
 
37
34
  # This method exists to avoid the expensive primary_key check internally, without
38
35
  # breaking compatibility with the read_attribute API
39
36
  def _read_attribute(attr_name, &block) # :nodoc
40
- @attributes.fetch_value(attr_name.to_s, &block)
37
+ @attributes.fetch_value(attr_name, &block)
41
38
  end
42
39
 
43
40
  alias :attribute :_read_attribute
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+
3
5
  module ActiveEntity
4
6
  module AttributeMethods
5
7
  module TimeZoneConversion
@@ -12,16 +12,14 @@ module ActiveEntity
12
12
  module ClassMethods # :nodoc:
13
13
  private
14
14
 
15
- def define_method_attribute=(name)
15
+ def define_method_attribute=(name, owner:)
16
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
- generated_attribute_methods, name, writer: true,
18
- ) do |temp_method_name, attr_name_expr|
19
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- def #{temp_method_name}(value)
21
- name = #{attr_name_expr}
22
- _write_attribute(name, value)
23
- end
24
- RUBY
17
+ owner, name, writer: true,
18
+ ) do |temp_method_name, attr_name_expr|
19
+ owner <<
20
+ "def #{temp_method_name}(value)" <<
21
+ " _write_attribute(#{attr_name_expr}, value)" <<
22
+ "end"
25
23
  end
26
24
  end
27
25
  end
@@ -31,32 +29,25 @@ module ActiveEntity
31
29
  # turned into +nil+.
32
30
  def write_attribute(attr_name, value)
33
31
  name = attr_name.to_s
34
- if self.class.attribute_alias?(name)
35
- name = self.class.attribute_alias(name)
36
- end
32
+ name = self.class.attribute_aliases[name] || name
37
33
 
38
- _write_attribute(name, value)
34
+ name = @primary_key if name == "id" && @primary_key
35
+ @attributes.write_from_user(name, value)
39
36
  end
40
37
 
41
38
  # This method exists to avoid the expensive primary_key check internally, without
42
39
  # breaking compatibility with the write_attribute API
43
40
  def _write_attribute(attr_name, value) # :nodoc:
44
- return if readonly_attribute?(attr_name) && attr_readonly_enabled?
45
-
46
- @attributes.write_from_user(attr_name.to_s, value)
47
- value
41
+ @attributes.write_from_user(attr_name, value)
48
42
  end
49
43
 
44
+ alias :attribute= :_write_attribute
45
+ private :attribute=
46
+
50
47
  private
51
- def write_attribute_without_type_cast(attr_name, value)
52
- name = attr_name.to_s
53
- @attributes.write_cast_value(name, value)
54
- value
55
- end
56
48
 
57
- # Handle *= for method_missing.
58
- def attribute=(attribute_name, value)
59
- _write_attribute(attribute_name, value)
49
+ def write_attribute_without_type_cast(attr_name, value)
50
+ @attributes.write_cast_value(attr_name, value)
60
51
  end
61
52
  end
62
53
  end