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
@@ -12,7 +12,13 @@ module ActiveEntity
12
12
  end
13
13
 
14
14
  module ClassMethods
15
- # Defines an attribute with a type on this model.
15
+ # Defines an attribute with a type on this model. It will override the
16
+ # type of existing attributes if needed. This allows control over how
17
+ # values are converted to and from SQL when assigned to a model. It also
18
+ # changes the behavior of values passed to
19
+ # {ActiveEntity::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
20
+ # your domain objects across much of Active Entity, without having to
21
+ # rely on implementation details or monkey patching.
16
22
  #
17
23
  # +name+ The name of the methods to define attribute methods for, and the
18
24
  # column which this will persist to.
@@ -29,11 +35,66 @@ module ActiveEntity
29
35
  # is not passed, the previous default value (if any) will be used.
30
36
  # Otherwise, the default will be +nil+.
31
37
  #
32
- # +array+ Specifies that the type should be an array (see the
38
+ # +array+ (PostgreSQL only) specifies that the type should be an array (see the
33
39
  # examples below).
34
40
  #
41
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
42
+ # examples below).
43
+ #
44
+ # When using a symbol for +cast_type+, extra options are forwarded to the
45
+ # constructor of the type object.
46
+ #
35
47
  # ==== Examples
36
48
  #
49
+ # The type detected by Active Entity can be overridden.
50
+ #
51
+ # # db/schema.rb
52
+ # create_table :store_listings, force: true do |t|
53
+ # t.decimal :price_in_cents
54
+ # end
55
+ #
56
+ # # app/models/store_listing.rb
57
+ # class StoreListing < ActiveEntity::Base
58
+ # end
59
+ #
60
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
61
+ #
62
+ # # before
63
+ # store_listing.price_in_cents # => BigDecimal(10.1)
64
+ #
65
+ # class StoreListing < ActiveEntity::Base
66
+ # attribute :price_in_cents, :integer
67
+ # end
68
+ #
69
+ # # after
70
+ # store_listing.price_in_cents # => 10
71
+ #
72
+ # A default can also be provided.
73
+ #
74
+ # # db/schema.rb
75
+ # create_table :store_listings, force: true do |t|
76
+ # t.string :my_string, default: "original default"
77
+ # end
78
+ #
79
+ # StoreListing.new.my_string # => "original default"
80
+ #
81
+ # # app/models/store_listing.rb
82
+ # class StoreListing < ActiveEntity::Base
83
+ # attribute :my_string, :string, default: "new default"
84
+ # end
85
+ #
86
+ # StoreListing.new.my_string # => "new default"
87
+ #
88
+ # class Product < ActiveEntity::Base
89
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
90
+ # end
91
+ #
92
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
93
+ # sleep 1
94
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
95
+ #
96
+ # \Attributes do not need to be backed by a database column.
97
+ #
37
98
  # # app/models/my_model.rb
38
99
  # class MyModel < ActiveEntity::Base
39
100
  # attribute :my_string, :string
@@ -54,6 +115,16 @@ module ActiveEntity
54
115
  # my_float_range: 1.0..3.5
55
116
  # }
56
117
  #
118
+ # Passing options to the type constructor
119
+ #
120
+ # # app/models/my_model.rb
121
+ # class MyModel < ActiveEntity::Base
122
+ # attribute :small_int, :integer, limit: 2
123
+ # end
124
+ #
125
+ # MyModel.create(small_int: 65537)
126
+ # # => Error: 65537 is out of range for the limit of two bytes
127
+ #
57
128
  # ==== Creating Custom Types
58
129
  #
59
130
  # Users may also define their own custom types, as long as they respond
@@ -121,6 +192,8 @@ module ActiveEntity
121
192
  # is not passed, the previous default value (if any) will be used.
122
193
  # Otherwise, the default will be +nil+. A proc can also be passed, and
123
194
  # will be called once each time a new value is needed.
195
+ #
196
+ # +cast+ or +deserialize+.
124
197
  def define_attribute(
125
198
  name,
126
199
  cast_type,
@@ -158,6 +231,7 @@ module ActiveEntity
158
231
  _default_attributes.fetch(name.to_s) { nil },
159
232
  )
160
233
  end
234
+
161
235
  _default_attributes[name] = default_attribute
162
236
  end
163
237
  end
@@ -1,22 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
3
  require "active_support/benchmarkable"
5
4
  require "active_support/dependencies"
6
5
  require "active_support/descendants_tracker"
7
6
  require "active_support/time"
8
- require "active_support/core_ext/module/attribute_accessors"
9
- require "active_support/core_ext/array/extract_options"
10
- require "active_support/core_ext/hash/deep_merge"
11
- require "active_support/core_ext/hash/slice"
12
- require "active_support/core_ext/string/behavior"
13
- require "active_support/core_ext/kernel/singleton_class"
14
- require "active_support/core_ext/module/introspection"
15
- require "active_support/core_ext/object/duplicable"
16
7
  require "active_support/core_ext/class/subclasses"
17
8
  require "active_entity/attribute_decorators"
18
- require "active_entity/define_callbacks"
19
- require "active_entity/errors"
20
9
  require "active_entity/attributes"
21
10
 
22
11
  module ActiveEntity #:nodoc:
@@ -28,7 +17,7 @@ module ActiveEntity #:nodoc:
28
17
  # Active Entity objects. The mapping that binds a given Active Entity class to a certain
29
18
  # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
30
19
  #
31
- # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
20
+ # See the mapping rules in table_name and the full example in link:files/activeentity/README_rdoc.html for more insight.
32
21
  #
33
22
  # == Creation
34
23
  #
@@ -290,10 +279,10 @@ module ActiveEntity #:nodoc:
290
279
  include Validations
291
280
  include Attributes
292
281
  include AttributeDecorators
293
- include DefineCallbacks
294
282
  include AttributeMethods
283
+ include Callbacks
295
284
  include Associations
296
- include ValidateEmbeddedAssociation
285
+ include ValidateEmbedsAssociation
297
286
  include NestedAttributes
298
287
  include Reflection
299
288
  include Serialization
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveEntity
4
- module DefineCallbacks
4
+ module Callbacks
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ CALLBACKS = [
8
+ :after_initialize, :before_validation, :after_validation
9
+ ]
10
+
7
11
  module ClassMethods # :nodoc:
8
12
  include ActiveModel::Callbacks
9
13
  end
@@ -58,11 +58,9 @@ module ActiveEntity
58
58
  def inspect # :nodoc:
59
59
  if self == Base
60
60
  super
61
- elsif abstract_class?
62
- "#{super}(abstract)"
63
61
  else
64
62
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
65
- "#{super}(#{attr_list})"
63
+ "#{super}#{ "(abstract)" if abstract_class? }(#{attr_list})"
66
64
  end
67
65
  end
68
66
 
@@ -81,7 +79,6 @@ module ActiveEntity
81
79
  # # Instantiates a single new object
82
80
  # User.new(first_name: 'Jamie')
83
81
  def initialize(attributes = nil)
84
- self.class.define_attribute_methods
85
82
  @attributes = self.class._default_attributes.deep_dup
86
83
 
87
84
  init_internals
@@ -95,6 +92,42 @@ module ActiveEntity
95
92
  enable_attr_readonly!
96
93
  end
97
94
 
95
+ # Initialize an empty model object from +coder+. +coder+ should be
96
+ # the result of previously encoding an Active Entity model, using
97
+ # #encode_with.
98
+ #
99
+ # class Post < ActiveEntity::Base
100
+ # end
101
+ #
102
+ # old_post = Post.new(title: "hello world")
103
+ # coder = {}
104
+ # old_post.encode_with(coder)
105
+ #
106
+ # post = Post.allocate
107
+ # post.init_with(coder)
108
+ # post.title # => 'hello world'
109
+ def init_with(coder, &block)
110
+ attributes = self.class.yaml_encoder.decode(coder)
111
+ init_with_attributes(attributes, coder["new_record"], &block)
112
+ end
113
+
114
+ ##
115
+ # Initialize an empty model object from +attributes+.
116
+ # +attributes+ should be an attributes object, and unlike the
117
+ # `initialize` method, no assignment calls are made per attribute.
118
+ def init_with_attributes(attributes) # :nodoc:
119
+ @attributes = attributes
120
+
121
+ init_internals
122
+
123
+ yield self if block_given?
124
+
125
+ _run_find_callbacks
126
+ _run_initialize_callbacks
127
+
128
+ self
129
+ end
130
+
98
131
  ##
99
132
  # :method: clone
100
133
  # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
@@ -147,6 +180,33 @@ module ActiveEntity
147
180
  coder["active_entity_yaml_version"] = 2
148
181
  end
149
182
 
183
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
184
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
185
+ #
186
+ # Note that new records are different from any other record by definition, unless the
187
+ # other record is the receiver itself. Besides, if you fetch existing records with
188
+ # +select+ and leave the ID out, you're on your own, this predicate will return false.
189
+ #
190
+ # Note also that destroying a record preserves its ID in the model instance, so deleted
191
+ # models are still comparable.
192
+ def ==(comparison_object)
193
+ super ||
194
+ comparison_object.instance_of?(self.class) &&
195
+ !id.nil? &&
196
+ comparison_object.id == id
197
+ end
198
+ alias :eql? :==
199
+
200
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
201
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
202
+ def hash
203
+ if id
204
+ self.class.hash ^ id.hash
205
+ else
206
+ super
207
+ end
208
+ end
209
+
150
210
  # Clone and freeze the attributes hash such that associations are still
151
211
  # accessible, even on destroyed records, but cloned models will not be
152
212
  # frozen.
@@ -169,6 +229,14 @@ module ActiveEntity
169
229
  end
170
230
  end
171
231
 
232
+ def present? # :nodoc:
233
+ true
234
+ end
235
+
236
+ def blank? # :nodoc:
237
+ false
238
+ end
239
+
172
240
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
173
241
  # attributes will be marked as read only since they cannot be saved.
174
242
  def readonly?
@@ -184,24 +252,22 @@ module ActiveEntity
184
252
  def inspect
185
253
  # We check defined?(@attributes) not to issue warnings if the object is
186
254
  # allocated but not initialized.
187
- inspection =
188
- if defined?(@attributes) && @attributes
189
- self.class.attribute_names.collect do |name|
190
- if has_attribute?(name)
191
- attr = _read_attribute(name)
192
- value =
193
- if attr.nil?
194
- attr.inspect
195
- else
196
- attr = format_for_inspect(attr)
197
- inspection_filter.filter_param(name, attr)
198
- end
199
- "#{name}: #{value}"
255
+ inspection = if defined?(@attributes) && @attributes
256
+ self.class.attribute_names.collect do |name|
257
+ if has_attribute?(name)
258
+ attr = _read_attribute(name)
259
+ value = if attr.nil?
260
+ attr.inspect
261
+ else
262
+ attr = format_for_inspect(attr)
263
+ inspection_filter.filter_param(name, attr)
200
264
  end
201
- end.compact.join(", ")
202
- else
203
- "not initialized"
204
- end
265
+ "#{name}: #{value}"
266
+ end
267
+ end.compact.join(", ")
268
+ else
269
+ "not initialized"
270
+ end
205
271
 
206
272
  "#<#{self.class} #{inspection}>"
207
273
  end
@@ -236,14 +302,6 @@ module ActiveEntity
236
302
  Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
237
303
  end
238
304
 
239
- def present? # :nodoc:
240
- true
241
- end
242
-
243
- def blank? # :nodoc:
244
- false
245
- end
246
-
247
305
  private
248
306
 
249
307
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
@@ -261,27 +319,27 @@ module ActiveEntity
261
319
  def init_internals
262
320
  @readonly = false
263
321
  @marked_for_destruction = false
264
- end
265
322
 
266
- def initialize_internals_callback
323
+ self.class.define_attribute_methods
267
324
  end
268
325
 
269
- def thaw
270
- if frozen?
271
- @attributes = @attributes.dup
272
- end
326
+ def initialize_internals_callback
273
327
  end
274
328
 
275
329
  def custom_inspect_method_defined?
276
330
  self.class.instance_method(:inspect).owner != ActiveEntity::Base.instance_method(:inspect).owner
277
331
  end
278
332
 
333
+ class InspectionMask < DelegateClass(::String)
334
+ def pretty_print(pp)
335
+ pp.text __getobj__
336
+ end
337
+ end
338
+ private_constant :InspectionMask
339
+
279
340
  def inspection_filter
280
341
  @inspection_filter ||= begin
281
- mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
282
- def mask.pretty_print(pp)
283
- pp.text __getobj__
284
- end
342
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
285
343
  ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
286
344
  end
287
345
  end
@@ -27,6 +27,32 @@ module ActiveEntity
27
27
  # conversation.status.nil? # => true
28
28
  # conversation.status # => nil
29
29
  #
30
+ # Scopes based on the allowed values of the enum field will be provided
31
+ # as well. With the above example:
32
+ #
33
+ # Conversation.active
34
+ # Conversation.not_active
35
+ # Conversation.archived
36
+ # Conversation.not_archived
37
+ #
38
+ # Of course, you can also query them directly if the scopes don't fit your
39
+ # needs:
40
+ #
41
+ # Conversation.where(status: [:active, :archived])
42
+ # Conversation.where.not(status: :active)
43
+ #
44
+ # Defining scopes can be disabled by setting +:_scopes+ to +false+.
45
+ #
46
+ # class Conversation < ActiveEntity::Base
47
+ # enum status: [ :active, :archived ], _scopes: false
48
+ # end
49
+ #
50
+ # You can set the default value from the database declaration, like:
51
+ #
52
+ # create_table :conversations do |t|
53
+ # t.column :status, :integer, default: 0
54
+ # end
55
+ #
30
56
  # Good practice is to let the first declared status be the default.
31
57
  #
32
58
  # Finally, it's also possible to explicitly map the relation between attribute and
@@ -97,12 +123,12 @@ module ActiveEntity
97
123
  end
98
124
 
99
125
  def cast(value)
100
- return if value.blank?
101
-
102
126
  if mapping.has_key?(value)
103
127
  value.to_s
104
128
  elsif mapping.has_value?(value)
105
129
  mapping.key(value)
130
+ elsif value.blank?
131
+ nil
106
132
  else
107
133
  assert_valid_value(value)
108
134
  end
@@ -124,6 +150,7 @@ module ActiveEntity
124
150
  end
125
151
 
126
152
  private
153
+
127
154
  attr_reader :name, :mapping, :subtype
128
155
  end
129
156
 
@@ -182,6 +209,7 @@ module ActiveEntity
182
209
  end
183
210
 
184
211
  private
212
+
185
213
  def _enum_methods_module
186
214
  @_enum_methods_module ||= begin
187
215
  mod = Module.new
@@ -212,8 +240,6 @@ module ActiveEntity
212
240
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
213
241
  if klass_method && dangerous_class_method?(method_name)
214
242
  raise_conflict_error(enum_name, method_name, type: "class")
215
- # elsif klass_method && method_defined_within?(method_name, Relation)
216
- # raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
217
243
  elsif !klass_method && dangerous_attribute_method?(method_name)
218
244
  raise_conflict_error(enum_name, method_name)
219
245
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
@@ -7,13 +7,6 @@ module ActiveEntity
7
7
  class ActiveEntityError < StandardError
8
8
  end
9
9
 
10
- # Raised when the single-table inheritance mechanism fails to locate the subclass
11
- # (for example due to improper usage of column that
12
- # {ActiveEntity::Base.inheritance_attribute}[rdoc-ref:ModelSchema::ClassMethods#inheritance_attribute]
13
- # points to).
14
- class SubclassNotFound < ActiveEntityError
15
- end
16
-
17
10
  # Raised when an object assigned to an association has an incorrect type.
18
11
  #
19
12
  # class Ticket < ActiveEntity::Base
@@ -41,10 +34,6 @@ module ActiveEntity
41
34
  class ConfigurationError < ActiveEntityError
42
35
  end
43
36
 
44
- # Raised on attempt to update record that is instantiated as read only.
45
- class ReadOnlyRecord < ActiveEntityError
46
- end
47
-
48
37
  # Raised when attribute has a name reserved by Active Entity (when attribute
49
38
  # has name of one of Active Entity instance methods).
50
39
  class DangerousAttributeError < ActiveEntityError