activeentity 0.0.1.beta1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +42 -0
  3. data/README.md +145 -0
  4. data/Rakefile +29 -0
  5. data/lib/active_entity.rb +73 -0
  6. data/lib/active_entity/aggregations.rb +276 -0
  7. data/lib/active_entity/associations.rb +146 -0
  8. data/lib/active_entity/associations/embedded/association.rb +134 -0
  9. data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
  10. data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
  11. data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
  12. data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
  13. data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
  14. data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
  15. data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
  16. data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
  17. data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
  18. data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
  19. data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
  20. data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
  21. data/lib/active_entity/attribute_assignment.rb +85 -0
  22. data/lib/active_entity/attribute_decorators.rb +90 -0
  23. data/lib/active_entity/attribute_methods.rb +330 -0
  24. data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
  25. data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
  26. data/lib/active_entity/attribute_methods/query.rb +35 -0
  27. data/lib/active_entity/attribute_methods/read.rb +47 -0
  28. data/lib/active_entity/attribute_methods/serialization.rb +90 -0
  29. data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
  30. data/lib/active_entity/attribute_methods/write.rb +63 -0
  31. data/lib/active_entity/attributes.rb +165 -0
  32. data/lib/active_entity/base.rb +303 -0
  33. data/lib/active_entity/coders/json.rb +15 -0
  34. data/lib/active_entity/coders/yaml_column.rb +50 -0
  35. data/lib/active_entity/core.rb +281 -0
  36. data/lib/active_entity/define_callbacks.rb +17 -0
  37. data/lib/active_entity/enum.rb +234 -0
  38. data/lib/active_entity/errors.rb +80 -0
  39. data/lib/active_entity/gem_version.rb +17 -0
  40. data/lib/active_entity/inheritance.rb +278 -0
  41. data/lib/active_entity/integration.rb +78 -0
  42. data/lib/active_entity/locale/en.yml +45 -0
  43. data/lib/active_entity/model_schema.rb +115 -0
  44. data/lib/active_entity/nested_attributes.rb +592 -0
  45. data/lib/active_entity/readonly_attributes.rb +47 -0
  46. data/lib/active_entity/reflection.rb +441 -0
  47. data/lib/active_entity/serialization.rb +25 -0
  48. data/lib/active_entity/store.rb +242 -0
  49. data/lib/active_entity/translation.rb +24 -0
  50. data/lib/active_entity/type.rb +73 -0
  51. data/lib/active_entity/type/date.rb +9 -0
  52. data/lib/active_entity/type/date_time.rb +9 -0
  53. data/lib/active_entity/type/decimal_without_scale.rb +15 -0
  54. data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
  55. data/lib/active_entity/type/internal/timezone.rb +17 -0
  56. data/lib/active_entity/type/json.rb +30 -0
  57. data/lib/active_entity/type/modifiers/array.rb +72 -0
  58. data/lib/active_entity/type/registry.rb +92 -0
  59. data/lib/active_entity/type/serialized.rb +71 -0
  60. data/lib/active_entity/type/text.rb +11 -0
  61. data/lib/active_entity/type/time.rb +21 -0
  62. data/lib/active_entity/type/type_map.rb +62 -0
  63. data/lib/active_entity/type/unsigned_integer.rb +17 -0
  64. data/lib/active_entity/validate_embedded_association.rb +305 -0
  65. data/lib/active_entity/validations.rb +50 -0
  66. data/lib/active_entity/validations/absence.rb +25 -0
  67. data/lib/active_entity/validations/associated.rb +60 -0
  68. data/lib/active_entity/validations/length.rb +26 -0
  69. data/lib/active_entity/validations/presence.rb +68 -0
  70. data/lib/active_entity/validations/subset.rb +76 -0
  71. data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
  72. data/lib/active_entity/version.rb +10 -0
  73. data/lib/tasks/active_entity_tasks.rake +6 -0
  74. metadata +155 -0
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity::Associations::Embedded::Builder # :nodoc:
4
+ class EmbeddedIn < SingularAssociation #:nodoc:
5
+ def self.macro
6
+ :embedded_in
7
+ end
8
+
9
+ def self.valid_options(options)
10
+ super + [:default]
11
+ end
12
+
13
+ def self.define_callbacks(model, reflection)
14
+ super
15
+ add_default_callbacks(model, reflection) if reflection.options[:default]
16
+ end
17
+
18
+ def self.add_default_callbacks(model, reflection)
19
+ model.before_validation lambda { |o|
20
+ o.association(reflection.name).default(&reflection.options[:default])
21
+ }
22
+ end
23
+
24
+ def self.define_validations(model, reflection)
25
+ if reflection.options.key?(:required)
26
+ reflection.options[:optional] = !reflection.options.delete(:required)
27
+ end
28
+
29
+ required = !reflection.options[:optional]
30
+
31
+ super
32
+
33
+ if required
34
+ model.validates_presence_of reflection.name, message: :required
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity::Associations::Embedded::Builder # :nodoc:
4
+ class EmbedsMany < CollectionAssociation #:nodoc:
5
+ def self.macro
6
+ :embeds_many
7
+ end
8
+
9
+ def self.valid_options(options)
10
+ super + [:inverse_of, :index_errors]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity::Associations::Embedded::Builder # :nodoc:
4
+ class EmbedsOne < SingularAssociation #:nodoc:
5
+ def self.macro
6
+ :embeds_one
7
+ end
8
+
9
+ def self.define_validations(model, reflection)
10
+ super
11
+ if reflection.options[:required]
12
+ model.validates_presence_of reflection.name, message: :required
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class is inherited by the has_one and belongs_to association classes
4
+
5
+ module ActiveEntity::Associations::Embedded::Builder # :nodoc:
6
+ class SingularAssociation < Association #:nodoc:
7
+ def self.valid_options(options)
8
+ super + [:inverse_of, :required]
9
+ end
10
+
11
+ def self.define_accessors(model, reflection)
12
+ super
13
+ mixin = model.generated_association_methods
14
+ name = reflection.name
15
+
16
+ define_constructors(mixin, name) if reflection.constructable?
17
+ end
18
+
19
+ # Defines the (build|create)_association methods for belongs_to or has_one association
20
+ def self.define_constructors(mixin, name)
21
+ mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
22
+ def build_#{name}(*args, &block)
23
+ association(:#{name}).build(*args, &block)
24
+ end
25
+ CODE
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ # = Active Entity Association Collection
7
+ #
8
+ # CollectionAssociation is an abstract class that provides common stuff to
9
+ # ease the implementation of association proxies that represent
10
+ # collections. See the class hierarchy in Association.
11
+ #
12
+ # CollectionAssociation:
13
+ # HasManyAssociation => has_many
14
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
15
+ #
16
+ # The CollectionAssociation class provides common methods to the collections
17
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
18
+ # the +:through association+ option.
19
+ #
20
+ # You need to be careful with assumptions regarding the target: The proxy
21
+ # does not fetch records from the database until it needs them, but new
22
+ # ones created with +build+ are added to the target. So, the target may be
23
+ # non-empty and still lack children waiting to be read from the database.
24
+ # If you look directly to the database you cannot assume that's the entire
25
+ # collection because new records may have been added to the target, etc.
26
+ #
27
+ # If you need to work on all current children, new and existing records,
28
+ # +load_target+ and the +loaded+ flag are your friends.
29
+ class CollectionAssociation < Association #:nodoc:
30
+ def initialize(owner, reflection)
31
+ super
32
+
33
+ @target = []
34
+ end
35
+
36
+ # Implements the reader method, e.g. foo.items for Foo.has_many :items
37
+ def reader
38
+ @proxy ||= CollectionProxy.new(klass, self)
39
+ end
40
+
41
+ # Implements the writer method, e.g. foo.items= for Foo.has_many :items
42
+ def writer(records)
43
+ replace(records)
44
+ end
45
+
46
+ def find(&block)
47
+ target.find(&block)
48
+ end
49
+
50
+ def build(attributes = {}, &block)
51
+ if attributes.is_a?(Array)
52
+ attributes.collect { |attr| build(attr, &block) }
53
+ else
54
+ add_to_target(build_record(attributes, &block))
55
+ end
56
+ end
57
+
58
+ # Add +records+ to this association. Returns +self+ so method calls may
59
+ # be chained. Since << flattens its argument list and inserts each record,
60
+ # +push+ and +concat+ behave identically.
61
+ def concat(*records)
62
+ records = records.flatten
63
+ concat_records(records)
64
+ end
65
+
66
+ # Removes all records from the association without calling callbacks
67
+ # on the associated records. It honors the +:dependent+ option. However
68
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
69
+ # deletion strategy for the association is applied.
70
+ #
71
+ # You can force a particular deletion strategy by passing a parameter.
72
+ #
73
+ # Example:
74
+ #
75
+ # @author.books.delete_all(:nullify)
76
+ # @author.books.delete_all(:delete_all)
77
+ #
78
+ # See delete for more info.
79
+ def delete_all
80
+ target.clear
81
+ end
82
+ alias destroy_all delete_all
83
+
84
+ # Removes +records+ from this association calling +before_remove+ and
85
+ # +after_remove+ callbacks.
86
+ #
87
+ # This method is abstract in the sense that +delete_records+ has to be
88
+ # provided by descendants. Note this method does not imply the records
89
+ # are actually removed from the database, that depends precisely on
90
+ # +delete_records+. They are in any case removed from the collection.
91
+ def delete(*records)
92
+ records.each do |record|
93
+ target.delete(record)
94
+ end
95
+ end
96
+ alias destroy delete
97
+
98
+ # Returns the size of the collection by executing a SELECT COUNT(*)
99
+ # query if the collection hasn't been loaded, and calling
100
+ # <tt>collection.size</tt> if it has.
101
+ #
102
+ # If the collection has been already loaded +size+ and +length+ are
103
+ # equivalent. If not and you are going to need the records anyway
104
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
105
+ #
106
+ # This method is abstract in the sense that it relies on
107
+ # +count_records+, which is a method descendants have to provide.
108
+ def size
109
+ target.size
110
+ end
111
+
112
+ # Returns true if the collection is empty.
113
+ #
114
+ # If the collection has been loaded
115
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
116
+ # collection has not been loaded, it is equivalent to
117
+ # <tt>collection.exists?</tt>. If the collection has not already been
118
+ # loaded and you are going to fetch the records anyway it is better to
119
+ # check <tt>collection.length.zero?</tt>.
120
+ def empty?
121
+ size.zero?
122
+ end
123
+
124
+ # Replace this collection with +other_array+. This will perform a diff
125
+ # and delete/add only records that have changed.
126
+ def replace(other_array)
127
+ other_array.each { |val| raise_on_type_mismatch!(val) }
128
+
129
+ target.replace other_array
130
+ end
131
+
132
+ def include?(record)
133
+ if record.is_a?(reflection.klass)
134
+ target.include?(record)
135
+ else
136
+ false
137
+ end
138
+ end
139
+
140
+ def add_to_target(record, skip_callbacks = false, &block)
141
+ # index = @target.index(record)
142
+ # replace_on_target(record, index, skip_callbacks, &block)
143
+ replace_on_target(record, nil, skip_callbacks, &block)
144
+ end
145
+
146
+ private
147
+
148
+ def concat_records(records)
149
+ records.each do |record|
150
+ raise_on_type_mismatch!(record)
151
+ add_to_target(record)
152
+ end
153
+
154
+ records
155
+ end
156
+
157
+ def replace_on_target(record, index, skip_callbacks)
158
+ callback(:before_add, record) unless skip_callbacks
159
+
160
+ set_inverse_instance(record)
161
+
162
+ yield(record) if block_given?
163
+
164
+ if index
165
+ target[index] = record
166
+ else
167
+ target << record
168
+ end
169
+
170
+ callback(:after_add, record) unless skip_callbacks
171
+
172
+ record
173
+ end
174
+
175
+ def callback(method, record)
176
+ callbacks_for(method).each do |callback|
177
+ callback.call(method, owner, record)
178
+ end
179
+ end
180
+
181
+ def callbacks_for(callback_name)
182
+ full_callback_name = "#{callback_name}_for_#{reflection.name}"
183
+ owner.class.send(full_callback_name)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,310 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveEntity
4
+ module Associations
5
+ module Embedded
6
+ # Association proxies in Active Entity are middlemen between the object that
7
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
8
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
9
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
10
+ # ActiveEntity::Reflection::AssociationReflection.
11
+ #
12
+ # For example, given
13
+ #
14
+ # class Blog < ActiveEntity::Base
15
+ # has_many :posts
16
+ # end
17
+ #
18
+ # blog = Blog.first
19
+ #
20
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
21
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
22
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
23
+ #
24
+ # This class delegates unknown methods to <tt>@target</tt> via
25
+ # <tt>method_missing</tt>.
26
+ #
27
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
28
+ #
29
+ # blog.posts.count
30
+ #
31
+ # is computed directly through SQL and does not trigger by itself the
32
+ # instantiation of the actual post records.
33
+ class CollectionProxy
34
+ include Enumerable
35
+
36
+ attr_reader :klass
37
+ alias :model :klass
38
+
39
+ delegate :to_xml, :encode_with, :length, :each, :join,
40
+ :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of,
41
+ :find, :last, :take, :blank?, :present?, :empty?, :any?, :one?, :many?, :include?,
42
+ :to_sentence, :to_formatted_s, :as_json,
43
+ :shuffle, :split, :slice, :index, :rindex, to: :records
44
+
45
+ def initialize(klass, association)
46
+ @klass = klass
47
+
48
+ @association = association
49
+
50
+ extensions = association.extensions
51
+ extend(*extensions) if extensions.any?
52
+ end
53
+
54
+ # Initializes new record from relation while maintaining the current
55
+ # scope.
56
+ #
57
+ # Expects arguments in the same format as {ActiveEntity::Base.new}[rdoc-ref:Core.new].
58
+ #
59
+ # users = User.where(name: 'DHH')
60
+ # user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
61
+ #
62
+ # You can also pass a block to new with the new record as argument:
63
+ #
64
+ # user = users.new { |user| user.name = 'Oscar' }
65
+ # user.name # => Oscar
66
+ def new(attributes = nil, &block)
67
+ klass.new(attributes, &block)
68
+ end
69
+
70
+ alias build new
71
+
72
+ def pretty_print(q)
73
+ q.pp(records)
74
+ end
75
+
76
+ def inspect
77
+ entries = records.take([size, 11].compact.min).map!(&:inspect)
78
+
79
+ entries[10] = "..." if entries.size == 11
80
+
81
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
82
+ end
83
+
84
+ # Returns +true+ if the association has been loaded, otherwise +false+.
85
+ #
86
+ # person.pets.loaded? # => false
87
+ # person.pets
88
+ # person.pets.loaded? # => true
89
+ def loaded?
90
+ @association.loaded?
91
+ end
92
+
93
+ # Returns a new object of the collection type that has been instantiated
94
+ # with +attributes+ and linked to this object, but have not yet been saved.
95
+ # You can pass an array of attributes hashes, this will return an array
96
+ # with the new objects.
97
+ #
98
+ # class Person
99
+ # has_many :pets
100
+ # end
101
+ #
102
+ # person.pets.build
103
+ # # => #<Pet id: nil, name: nil, person_id: 1>
104
+ #
105
+ # person.pets.build(name: 'Fancy-Fancy')
106
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
107
+ #
108
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
109
+ # # => [
110
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
111
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
112
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
113
+ # # ]
114
+ #
115
+ # person.pets.size # => 5 # size of the collection
116
+ # person.pets.count # => 0 # count from database
117
+ def build(attributes = {}, &block)
118
+ @association.build(attributes, &block)
119
+ end
120
+ alias_method :new, :build
121
+
122
+ # Add one or more records to the collection by setting their foreign keys
123
+ # to the association's primary key. Since #<< flattens its argument list and
124
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
125
+ # so method calls may be chained.
126
+ #
127
+ # class Person < ActiveEntity::Base
128
+ # has_many :pets
129
+ # end
130
+ #
131
+ # person.pets.size # => 0
132
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
133
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
134
+ # person.pets.size # => 3
135
+ #
136
+ # person.id # => 1
137
+ # person.pets
138
+ # # => [
139
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
140
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
141
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
142
+ # # ]
143
+ #
144
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
145
+ # person.pets.size # => 5
146
+ def concat(*records)
147
+ @association.concat(*records)
148
+ end
149
+
150
+ # Replaces this collection with +other_array+. This will perform a diff
151
+ # and delete/add only records that have changed.
152
+ #
153
+ # class Person < ActiveEntity::Base
154
+ # has_many :pets
155
+ # end
156
+ #
157
+ # person.pets
158
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
159
+ #
160
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
161
+ #
162
+ # person.pets.replace(other_pets)
163
+ #
164
+ # person.pets
165
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
166
+ #
167
+ # If the supplied array has an incorrect association type, it raises
168
+ # an <tt>ActiveEntity::AssociationTypeMismatch</tt> error:
169
+ #
170
+ # person.pets.replace(["doo", "ggie", "gaga"])
171
+ # # => ActiveEntity::AssociationTypeMismatch: Pet expected, got String
172
+ def replace(other_array)
173
+ @association.replace(other_array)
174
+ end
175
+
176
+ def delete_all
177
+ @association.delete_all
178
+ end
179
+ alias destroy_all delete_all
180
+
181
+ def delete(*records)
182
+ @association.delete(*records)
183
+ end
184
+ alias destroy delete
185
+
186
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
187
+ # of an array with the deleted objects, so methods can be chained. See
188
+ # +delete_all+ for more information.
189
+ # Note that because +delete_all+ removes records by directly
190
+ # running an SQL query into the database, the +updated_at+ column of
191
+ # the object is not changed.
192
+ def clear
193
+ delete_all
194
+ self
195
+ end
196
+
197
+ def proxy_association
198
+ @association
199
+ end
200
+
201
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
202
+ # contain the same number of elements and if each element is equal
203
+ # to the corresponding element in the +other+ array, otherwise returns
204
+ # +false+.
205
+ #
206
+ # class Person < ActiveEntity::Base
207
+ # has_many :pets
208
+ # end
209
+ #
210
+ # person.pets
211
+ # # => [
212
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
213
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
214
+ # # ]
215
+ #
216
+ # other = person.pets.to_ary
217
+ #
218
+ # person.pets == other
219
+ # # => true
220
+ #
221
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
222
+ #
223
+ # person.pets == other
224
+ # # => false
225
+ def ==(other)
226
+ records == other
227
+ end
228
+
229
+ ##
230
+ # :method: to_ary
231
+ #
232
+ # :call-seq:
233
+ # to_ary()
234
+ #
235
+ # Returns a new array of objects from the collection. If the collection
236
+ # hasn't been loaded, it fetches the records from the database.
237
+ #
238
+ # class Person < ActiveEntity::Base
239
+ # has_many :pets
240
+ # end
241
+ #
242
+ # person.pets
243
+ # # => [
244
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
245
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
246
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
247
+ # # ]
248
+ #
249
+ # other_pets = person.pets.to_ary
250
+ # # => [
251
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
252
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
253
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
254
+ # # ]
255
+ #
256
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
257
+ #
258
+ # other_pets
259
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
260
+ #
261
+ # person.pets
262
+ # # This is not affected by replace
263
+ # # => [
264
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
265
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
266
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
267
+ # # ]
268
+ # Converts relation objects to Array.
269
+ def to_ary
270
+ records.dup
271
+ end
272
+ alias to_a to_ary
273
+
274
+ def records # :nodoc:
275
+ @association.target
276
+ end
277
+
278
+ # Adds one or more +records+ to the collection by setting their foreign keys
279
+ # to the association's primary key. Returns +self+, so several appends may be
280
+ # chained together.
281
+ #
282
+ # class Person < ActiveEntity::Base
283
+ # has_many :pets
284
+ # end
285
+ #
286
+ # person.pets.size # => 0
287
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
288
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
289
+ # person.pets.size # => 3
290
+ #
291
+ # person.id # => 1
292
+ # person.pets
293
+ # # => [
294
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
295
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
296
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
297
+ # # ]
298
+ def <<(*records)
299
+ proxy_association.concat(records) && self
300
+ end
301
+ alias_method :push, :<<
302
+ alias_method :append, :<<
303
+
304
+ def prepend(*_args)
305
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end