activeentity 0.0.1.beta14 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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