tallty_duck_record 1.0.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 (79) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +41 -0
  3. data/README.md +82 -0
  4. data/Rakefile +28 -0
  5. data/lib/core_ext/array_without_blank.rb +46 -0
  6. data/lib/duck_record.rb +65 -0
  7. data/lib/duck_record/associations.rb +130 -0
  8. data/lib/duck_record/associations/association.rb +271 -0
  9. data/lib/duck_record/associations/belongs_to_association.rb +71 -0
  10. data/lib/duck_record/associations/builder/association.rb +127 -0
  11. data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
  12. data/lib/duck_record/associations/builder/collection_association.rb +45 -0
  13. data/lib/duck_record/associations/builder/embeds_many.rb +9 -0
  14. data/lib/duck_record/associations/builder/embeds_one.rb +9 -0
  15. data/lib/duck_record/associations/builder/has_many.rb +11 -0
  16. data/lib/duck_record/associations/builder/has_one.rb +20 -0
  17. data/lib/duck_record/associations/builder/singular_association.rb +33 -0
  18. data/lib/duck_record/associations/collection_association.rb +476 -0
  19. data/lib/duck_record/associations/collection_proxy.rb +1160 -0
  20. data/lib/duck_record/associations/embeds_association.rb +92 -0
  21. data/lib/duck_record/associations/embeds_many_association.rb +203 -0
  22. data/lib/duck_record/associations/embeds_many_proxy.rb +892 -0
  23. data/lib/duck_record/associations/embeds_one_association.rb +48 -0
  24. data/lib/duck_record/associations/foreign_association.rb +11 -0
  25. data/lib/duck_record/associations/has_many_association.rb +17 -0
  26. data/lib/duck_record/associations/has_one_association.rb +39 -0
  27. data/lib/duck_record/associations/singular_association.rb +73 -0
  28. data/lib/duck_record/attribute.rb +213 -0
  29. data/lib/duck_record/attribute/user_provided_default.rb +30 -0
  30. data/lib/duck_record/attribute_assignment.rb +118 -0
  31. data/lib/duck_record/attribute_decorators.rb +89 -0
  32. data/lib/duck_record/attribute_methods.rb +325 -0
  33. data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
  34. data/lib/duck_record/attribute_methods/dirty.rb +107 -0
  35. data/lib/duck_record/attribute_methods/read.rb +78 -0
  36. data/lib/duck_record/attribute_methods/serialization.rb +66 -0
  37. data/lib/duck_record/attribute_methods/write.rb +70 -0
  38. data/lib/duck_record/attribute_mutation_tracker.rb +108 -0
  39. data/lib/duck_record/attribute_set.rb +98 -0
  40. data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
  41. data/lib/duck_record/attributes.rb +262 -0
  42. data/lib/duck_record/base.rb +300 -0
  43. data/lib/duck_record/callbacks.rb +324 -0
  44. data/lib/duck_record/coders/json.rb +13 -0
  45. data/lib/duck_record/coders/yaml_column.rb +48 -0
  46. data/lib/duck_record/core.rb +262 -0
  47. data/lib/duck_record/define_callbacks.rb +23 -0
  48. data/lib/duck_record/enum.rb +139 -0
  49. data/lib/duck_record/errors.rb +71 -0
  50. data/lib/duck_record/inheritance.rb +130 -0
  51. data/lib/duck_record/locale/en.yml +46 -0
  52. data/lib/duck_record/model_schema.rb +71 -0
  53. data/lib/duck_record/nested_attributes.rb +555 -0
  54. data/lib/duck_record/nested_validate_association.rb +262 -0
  55. data/lib/duck_record/persistence.rb +39 -0
  56. data/lib/duck_record/readonly_attributes.rb +36 -0
  57. data/lib/duck_record/reflection.rb +650 -0
  58. data/lib/duck_record/serialization.rb +26 -0
  59. data/lib/duck_record/translation.rb +22 -0
  60. data/lib/duck_record/type.rb +77 -0
  61. data/lib/duck_record/type/array.rb +36 -0
  62. data/lib/duck_record/type/array_without_blank.rb +36 -0
  63. data/lib/duck_record/type/date.rb +7 -0
  64. data/lib/duck_record/type/date_time.rb +7 -0
  65. data/lib/duck_record/type/decimal_without_scale.rb +13 -0
  66. data/lib/duck_record/type/internal/abstract_json.rb +33 -0
  67. data/lib/duck_record/type/internal/timezone.rb +15 -0
  68. data/lib/duck_record/type/json.rb +6 -0
  69. data/lib/duck_record/type/registry.rb +97 -0
  70. data/lib/duck_record/type/serialized.rb +63 -0
  71. data/lib/duck_record/type/text.rb +9 -0
  72. data/lib/duck_record/type/time.rb +19 -0
  73. data/lib/duck_record/type/unsigned_integer.rb +15 -0
  74. data/lib/duck_record/validations.rb +67 -0
  75. data/lib/duck_record/validations/subset.rb +74 -0
  76. data/lib/duck_record/validations/uniqueness_on_real_record.rb +248 -0
  77. data/lib/duck_record/version.rb +3 -0
  78. data/lib/tasks/acts_as_record_tasks.rake +4 -0
  79. metadata +181 -0
@@ -0,0 +1,92 @@
1
+ require "active_support/core_ext/array/wrap"
2
+
3
+ module DuckRecord
4
+ module Associations
5
+ # = Active Record Associations
6
+ #
7
+ # This is the root class of all associations ('+ Foo' signifies an included module Foo):
8
+ #
9
+ # Association
10
+ # SingularAssociation
11
+ # HasOneAssociation + ForeignAssociation
12
+ # HasOneThroughAssociation + ThroughAssociation
13
+ # BelongsToAssociation
14
+ # BelongsToPolymorphicAssociation
15
+ # CollectionAssociation
16
+ # HasManyAssociation + ForeignAssociation
17
+ # HasManyThroughAssociation + ThroughAssociation
18
+ class EmbedsAssociation #:nodoc:
19
+ attr_reader :owner, :target, :reflection
20
+
21
+ delegate :options, to: :reflection
22
+
23
+ def initialize(owner, reflection)
24
+ reflection.check_validity!
25
+
26
+ @owner, @reflection = owner, reflection
27
+
28
+ reset
29
+ end
30
+
31
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
32
+ def reset
33
+ @target = nil
34
+ end
35
+
36
+ # Has the \target been already \loaded?
37
+ def loaded?
38
+ !!@target
39
+ end
40
+
41
+ # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
42
+ def target=(target)
43
+ @target = target
44
+ end
45
+
46
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
47
+ # polymorphic_type field on the owner.
48
+ def klass
49
+ reflection.klass
50
+ end
51
+
52
+ # We can't dump @reflection since it contains the scope proc
53
+ def marshal_dump
54
+ ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
55
+ [@reflection.name, ivars]
56
+ end
57
+
58
+ def marshal_load(data)
59
+ reflection_name, ivars = data
60
+ ivars.each { |name, val| instance_variable_set(name, val) }
61
+ @reflection = @owner.class._reflect_on_association(reflection_name)
62
+ end
63
+
64
+ def initialize_attributes(record, attributes = nil) #:nodoc:
65
+ attributes ||= {}
66
+ record.assign_attributes(attributes)
67
+ end
68
+
69
+ private
70
+
71
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
72
+ # the kind of the class of the associated objects. Meant to be used as
73
+ # a sanity check when you are about to assign an associated record.
74
+ def raise_on_type_mismatch!(record)
75
+ unless record.is_a?(reflection.klass)
76
+ fresh_class = reflection.class_name.safe_constantize
77
+ unless fresh_class && record.is_a?(fresh_class)
78
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
79
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
80
+ raise DuckRecord::AssociationTypeMismatch, message
81
+ end
82
+ end
83
+ end
84
+
85
+ def build_record(attributes)
86
+ reflection.build_association(attributes) do |record|
87
+ initialize_attributes(record, attributes)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,203 @@
1
+ module DuckRecord
2
+ module Associations
3
+ # = Active Record Association Collection
4
+ #
5
+ # CollectionAssociation is an abstract class that provides common stuff to
6
+ # ease the implementation of association proxies that represent
7
+ # collections. See the class hierarchy in Association.
8
+ #
9
+ # CollectionAssociation:
10
+ # HasManyAssociation => has_many
11
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
12
+ #
13
+ # The CollectionAssociation class provides common methods to the collections
14
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
15
+ # the +:through association+ option.
16
+ #
17
+ # You need to be careful with assumptions regarding the target: The proxy
18
+ # does not fetch records from the database until it needs them, but new
19
+ # ones created with +build+ are added to the target. So, the target may be
20
+ # non-empty and still lack children waiting to be read from the database.
21
+ # If you look directly to the database you cannot assume that's the entire
22
+ # collection because new records may have been added to the target, etc.
23
+ #
24
+ # If you need to work on all current children, new and existing records,
25
+ # +load_target+ and the +loaded+ flag are your friends.
26
+ class EmbedsManyAssociation < EmbedsAssociation #:nodoc:
27
+ # Implements the reader method, e.g. foo.items for Foo.has_many :items
28
+ def reader
29
+ @_reader ||= EmbedsManyProxy.new(klass, self)
30
+ end
31
+
32
+ # Implements the writer method, e.g. foo.items= for Foo.has_many :items
33
+ def writer(records)
34
+ replace(records)
35
+ end
36
+
37
+ def reset
38
+ super
39
+ @target = []
40
+ end
41
+
42
+ def build(attributes = {}, &block)
43
+ if attributes.is_a?(klass)
44
+ add_to_target(attributes) do |record|
45
+ yield(record) if block_given?
46
+ end
47
+ elsif attributes.is_a?(Array)
48
+ attributes.collect { |attr| build(attr, &block) }
49
+ else
50
+ add_to_target(build_record(attributes)) do |record|
51
+ yield(record) if block_given?
52
+ end
53
+ end
54
+ end
55
+
56
+ # Add +records+ to this association. Returns +self+ so method calls may
57
+ # be chained. Since << flattens its argument list and inserts each record,
58
+ # +push+ and +concat+ behave identically.
59
+ def concat(*records)
60
+ records.flatten.each do |r|
61
+ begin
62
+ build(r)
63
+ rescue
64
+ raise_on_type_mismatch!(r)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Removes all records from the association without calling callbacks
70
+ # on the associated records. It honors the +:dependent+ option. However
71
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
72
+ # deletion strategy for the association is applied.
73
+ #
74
+ # You can force a particular deletion strategy by passing a parameter.
75
+ #
76
+ # Example:
77
+ #
78
+ # @author.books.delete_all(:nullify)
79
+ # @author.books.delete_all(:delete_all)
80
+ #
81
+ # See delete for more info.
82
+ def delete_all
83
+ @target.clear
84
+ end
85
+
86
+ # Removes +records+ from this association calling +before_remove+ and
87
+ # +after_remove+ callbacks.
88
+ #
89
+ # This method is abstract in the sense that +delete_records+ has to be
90
+ # provided by descendants. Note this method does not imply the records
91
+ # are actually removed from the database, that depends precisely on
92
+ # +delete_records+. They are in any case removed from the collection.
93
+ def delete(*records)
94
+ return if records.empty?
95
+ @target = @target - records
96
+ end
97
+
98
+ # Deletes the +records+ and removes them from this association calling
99
+ # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
100
+ #
101
+ # Note that this method removes records from the database ignoring the
102
+ # +:dependent+ option.
103
+ def destroy(*records)
104
+ return if records.empty?
105
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
106
+ delete_or_destroy(records, :destroy)
107
+ end
108
+
109
+ # Returns the size of the collection by executing a SELECT COUNT(*)
110
+ # query if the collection hasn't been loaded, and calling
111
+ # <tt>collection.size</tt> if it has.
112
+ #
113
+ # If the collection has been already loaded +size+ and +length+ are
114
+ # equivalent. If not and you are going to need the records anyway
115
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
116
+ #
117
+ # This method is abstract in the sense that it relies on
118
+ # +count_records+, which is a method descendants have to provide.
119
+ def size
120
+ @target.size
121
+ end
122
+
123
+ def uniq
124
+ @target.uniq!
125
+ end
126
+
127
+ # Returns true if the collection is empty.
128
+ #
129
+ # If the collection has been loaded
130
+ # it is equivalent to <tt>collection.size.zero?</tt>. If the
131
+ # collection has not been loaded, it is equivalent to
132
+ # <tt>collection.exists?</tt>. If the collection has not already been
133
+ # loaded and you are going to fetch the records anyway it is better to
134
+ # check <tt>collection.length.zero?</tt>.
135
+ def empty?
136
+ @target.blank?
137
+ end
138
+
139
+ # Replace this collection with +other_array+. This will perform a diff
140
+ # and delete/add only records that have changed.
141
+ def replace(other_array)
142
+ delete_all
143
+ other_array.each do |item|
144
+ begin
145
+ build(item)
146
+ rescue
147
+ raise_on_type_mismatch!(item)
148
+ end
149
+ end
150
+ end
151
+
152
+ def include?(record)
153
+ @target.include?(record)
154
+ end
155
+
156
+ def add_to_target(record, skip_callbacks = false, &block)
157
+ index = @target.index(record)
158
+
159
+ replace_on_target(record, index, skip_callbacks, &block)
160
+ end
161
+
162
+ def replace_on_target(record, index, skip_callbacks)
163
+ callback(:before_add, record) unless skip_callbacks
164
+
165
+ begin
166
+ if index
167
+ record_was = target[index]
168
+ target[index] = record
169
+ else
170
+ target << record
171
+ end
172
+
173
+ yield(record) if block_given?
174
+ rescue
175
+ if index
176
+ target[index] = record_was
177
+ else
178
+ target.delete(record)
179
+ end
180
+
181
+ raise
182
+ end
183
+
184
+ callback(:after_add, record) unless skip_callbacks
185
+
186
+ record
187
+ end
188
+
189
+ private
190
+
191
+ def callback(method, record)
192
+ callbacks_for(method).each do |callback|
193
+ callback.call(method, owner, record)
194
+ end
195
+ end
196
+
197
+ def callbacks_for(callback_name)
198
+ full_callback_name = "#{callback_name}_for_#{reflection.name}"
199
+ owner.class.send(full_callback_name)
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,892 @@
1
+ module DuckRecord
2
+ module Associations
3
+ # Association proxies in Active Record are middlemen between the object that
4
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
5
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
6
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
7
+ # ActiveRecord::Reflection::AssociationReflection.
8
+ #
9
+ # For example, given
10
+ #
11
+ # class Blog < ActiveRecord::Base
12
+ # has_many :posts
13
+ # end
14
+ #
15
+ # blog = Blog.first
16
+ #
17
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
18
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
19
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
20
+ #
21
+ # This class delegates unknown methods to <tt>@target</tt> via
22
+ # <tt>method_missing</tt>.
23
+ #
24
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
25
+ #
26
+ # blog.posts.count
27
+ #
28
+ # is computed directly through SQL and does not trigger by itself the
29
+ # instantiation of the actual post records.
30
+ class EmbedsManyProxy
31
+ include Enumerable
32
+
33
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
34
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
35
+ :to_sentence, :to_formatted_s,
36
+ :shuffle, :split, :index, to: :records
37
+
38
+ delegate :target, :loaded?, to: :@association
39
+
40
+ attr_reader :klass
41
+ alias :model :klass
42
+
43
+ def initialize(klass, association) #:nodoc:
44
+ @klass = klass
45
+ @association = association
46
+ end
47
+
48
+ ##
49
+ # :method: first
50
+ #
51
+ # :call-seq:
52
+ # first(limit = nil)
53
+ #
54
+ # Returns the first record, or the first +n+ records, from the collection.
55
+ # If the collection is empty, the first form returns +nil+, and the second
56
+ # form returns an empty array.
57
+ #
58
+ # class Person < ActiveRecord::Base
59
+ # has_many :pets
60
+ # end
61
+ #
62
+ # person.pets
63
+ # # => [
64
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
65
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
66
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
67
+ # # ]
68
+ #
69
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
70
+ #
71
+ # person.pets.first(2)
72
+ # # => [
73
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
74
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
75
+ # # ]
76
+ #
77
+ # another_person_without.pets # => []
78
+ # another_person_without.pets.first # => nil
79
+ # another_person_without.pets.first(3) # => []
80
+
81
+ ##
82
+ # :method: second
83
+ #
84
+ # :call-seq:
85
+ # second()
86
+ #
87
+ # Same as #first except returns only the second record.
88
+
89
+ ##
90
+ # :method: third
91
+ #
92
+ # :call-seq:
93
+ # third()
94
+ #
95
+ # Same as #first except returns only the third record.
96
+
97
+ ##
98
+ # :method: fourth
99
+ #
100
+ # :call-seq:
101
+ # fourth()
102
+ #
103
+ # Same as #first except returns only the fourth record.
104
+
105
+ ##
106
+ # :method: fifth
107
+ #
108
+ # :call-seq:
109
+ # fifth()
110
+ #
111
+ # Same as #first except returns only the fifth record.
112
+
113
+ ##
114
+ # :method: forty_two
115
+ #
116
+ # :call-seq:
117
+ # forty_two()
118
+ #
119
+ # Same as #first except returns only the forty second record.
120
+ # Also known as accessing "the reddit".
121
+
122
+ ##
123
+ # :method: third_to_last
124
+ #
125
+ # :call-seq:
126
+ # third_to_last()
127
+ #
128
+ # Same as #first except returns only the third-to-last record.
129
+
130
+ ##
131
+ # :method: second_to_last
132
+ #
133
+ # :call-seq:
134
+ # second_to_last()
135
+ #
136
+ # Same as #first except returns only the second-to-last record.
137
+
138
+ # Returns a new object of the collection type that has been instantiated
139
+ # with +attributes+ and linked to this object, but have not yet been saved.
140
+ # You can pass an array of attributes hashes, this will return an array
141
+ # with the new objects.
142
+ #
143
+ # class Person
144
+ # has_many :pets
145
+ # end
146
+ #
147
+ # person.pets.build
148
+ # # => #<Pet id: nil, name: nil, person_id: 1>
149
+ #
150
+ # person.pets.build(name: 'Fancy-Fancy')
151
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
152
+ #
153
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
154
+ # # => [
155
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
156
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
157
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
158
+ # # ]
159
+ #
160
+ # person.pets.size # => 5 # size of the collection
161
+ # person.pets.count # => 0 # count from database
162
+ def build(attributes = {}, &block)
163
+ proxy_association.build(attributes, &block)
164
+ end
165
+ alias_method :new, :build
166
+
167
+ # Add one or more records to the collection by setting their foreign keys
168
+ # to the association's primary key. Since #<< flattens its argument list and
169
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
170
+ # so method calls may be chained.
171
+ #
172
+ # class Person < ActiveRecord::Base
173
+ # has_many :pets
174
+ # end
175
+ #
176
+ # person.pets.size # => 0
177
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
178
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
179
+ # person.pets.size # => 3
180
+ #
181
+ # person.id # => 1
182
+ # person.pets
183
+ # # => [
184
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
185
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
186
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
187
+ # # ]
188
+ #
189
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
190
+ # person.pets.size # => 5
191
+ def concat(*records)
192
+ proxy_association.concat(*records)
193
+ end
194
+
195
+ # Replaces this collection with +other_array+. This will perform a diff
196
+ # and delete/add only records that have changed.
197
+ #
198
+ # class Person < ActiveRecord::Base
199
+ # has_many :pets
200
+ # end
201
+ #
202
+ # person.pets
203
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
204
+ #
205
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
206
+ #
207
+ # person.pets.replace(other_pets)
208
+ #
209
+ # person.pets
210
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
211
+ #
212
+ # If the supplied array has an incorrect association type, it raises
213
+ # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
214
+ #
215
+ # person.pets.replace(["doo", "ggie", "gaga"])
216
+ # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
217
+ def replace(other_array)
218
+ proxy_association.replace(other_array)
219
+ end
220
+
221
+ # Deletes all the records from the collection according to the strategy
222
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
223
+ # then it will follow the default strategy.
224
+ #
225
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
226
+ # +:delete_all+.
227
+ #
228
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
229
+ # This sets the foreign keys to +NULL+.
230
+ #
231
+ # class Person < ActiveRecord::Base
232
+ # has_many :pets # dependent: :nullify option by default
233
+ # end
234
+ #
235
+ # person.pets.size # => 3
236
+ # person.pets
237
+ # # => [
238
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
239
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
240
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
241
+ # # ]
242
+ #
243
+ # person.pets.delete_all
244
+ # # => [
245
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
246
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
247
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
248
+ # # ]
249
+ #
250
+ # person.pets.size # => 0
251
+ # person.pets # => []
252
+ #
253
+ # Pet.find(1, 2, 3)
254
+ # # => [
255
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
256
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
257
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
258
+ # # ]
259
+ #
260
+ # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
261
+ # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
262
+ # Records are not instantiated and callbacks will not be fired.
263
+ #
264
+ # class Person < ActiveRecord::Base
265
+ # has_many :pets, dependent: :destroy
266
+ # end
267
+ #
268
+ # person.pets.size # => 3
269
+ # person.pets
270
+ # # => [
271
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
272
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
273
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
274
+ # # ]
275
+ #
276
+ # person.pets.delete_all
277
+ #
278
+ # Pet.find(1, 2, 3)
279
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
280
+ #
281
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
282
+ # *without* calling their +destroy+ method.
283
+ #
284
+ # class Person < ActiveRecord::Base
285
+ # has_many :pets, dependent: :delete_all
286
+ # end
287
+ #
288
+ # person.pets.size # => 3
289
+ # person.pets
290
+ # # => [
291
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
292
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
293
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
294
+ # # ]
295
+ #
296
+ # person.pets.delete_all
297
+ #
298
+ # Pet.find(1, 2, 3)
299
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
300
+ def delete_all
301
+ proxy_association.delete_all
302
+ end
303
+
304
+ # Deletes the records of the collection directly from the database
305
+ # ignoring the +:dependent+ option. Records are instantiated and it
306
+ # invokes +before_remove+, +after_remove+ , +before_destroy+ and
307
+ # +after_destroy+ callbacks.
308
+ #
309
+ # class Person < ActiveRecord::Base
310
+ # has_many :pets
311
+ # end
312
+ #
313
+ # person.pets.size # => 3
314
+ # person.pets
315
+ # # => [
316
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
317
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
318
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
319
+ # # ]
320
+ #
321
+ # person.pets.destroy_all
322
+ #
323
+ # person.pets.size # => 0
324
+ # person.pets # => []
325
+ #
326
+ # Pet.find(1) # => Couldn't find Pet with id=1
327
+ def destroy_all
328
+ proxy_association.destroy_all
329
+ end
330
+
331
+ # Deletes the +records+ supplied from the collection according to the strategy
332
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
333
+ # then it will follow the default strategy. Returns an array with the
334
+ # deleted records.
335
+ #
336
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
337
+ # +:delete_all+.
338
+ #
339
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
340
+ # This sets the foreign keys to +NULL+.
341
+ #
342
+ # class Person < ActiveRecord::Base
343
+ # has_many :pets # dependent: :nullify option by default
344
+ # end
345
+ #
346
+ # person.pets.size # => 3
347
+ # person.pets
348
+ # # => [
349
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
350
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
351
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
352
+ # # ]
353
+ #
354
+ # person.pets.delete(Pet.find(1))
355
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
356
+ #
357
+ # person.pets.size # => 2
358
+ # person.pets
359
+ # # => [
360
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
361
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
362
+ # # ]
363
+ #
364
+ # Pet.find(1)
365
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
366
+ #
367
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
368
+ # their +destroy+ method. See +destroy+ for more information.
369
+ #
370
+ # class Person < ActiveRecord::Base
371
+ # has_many :pets, dependent: :destroy
372
+ # end
373
+ #
374
+ # person.pets.size # => 3
375
+ # person.pets
376
+ # # => [
377
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
378
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
379
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
380
+ # # ]
381
+ #
382
+ # person.pets.delete(Pet.find(1), Pet.find(3))
383
+ # # => [
384
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
385
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
386
+ # # ]
387
+ #
388
+ # person.pets.size # => 1
389
+ # person.pets
390
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
391
+ #
392
+ # Pet.find(1, 3)
393
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
394
+ #
395
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
396
+ # *without* calling their +destroy+ method.
397
+ #
398
+ # class Person < ActiveRecord::Base
399
+ # has_many :pets, dependent: :delete_all
400
+ # end
401
+ #
402
+ # person.pets.size # => 3
403
+ # person.pets
404
+ # # => [
405
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
406
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
407
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
408
+ # # ]
409
+ #
410
+ # person.pets.delete(Pet.find(1))
411
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
412
+ #
413
+ # person.pets.size # => 2
414
+ # person.pets
415
+ # # => [
416
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
417
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
418
+ # # ]
419
+ #
420
+ # Pet.find(1)
421
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
422
+ #
423
+ # You can pass +Integer+ or +String+ values, it finds the records
424
+ # responding to the +id+ and executes delete on them.
425
+ #
426
+ # class Person < ActiveRecord::Base
427
+ # has_many :pets
428
+ # end
429
+ #
430
+ # person.pets.size # => 3
431
+ # person.pets
432
+ # # => [
433
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
434
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
435
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
436
+ # # ]
437
+ #
438
+ # person.pets.delete("1")
439
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
440
+ #
441
+ # person.pets.delete(2, 3)
442
+ # # => [
443
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
444
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
445
+ # # ]
446
+ def delete(*records)
447
+ proxy_association.delete(*records)
448
+ end
449
+
450
+ # Destroys the +records+ supplied and removes them from the collection.
451
+ # This method will _always_ remove record from the database ignoring
452
+ # the +:dependent+ option. Returns an array with the removed records.
453
+ #
454
+ # class Person < ActiveRecord::Base
455
+ # has_many :pets
456
+ # end
457
+ #
458
+ # person.pets.size # => 3
459
+ # person.pets
460
+ # # => [
461
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
462
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
463
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
464
+ # # ]
465
+ #
466
+ # person.pets.destroy(Pet.find(1))
467
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
468
+ #
469
+ # person.pets.size # => 2
470
+ # person.pets
471
+ # # => [
472
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
473
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
474
+ # # ]
475
+ #
476
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
477
+ # # => [
478
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
479
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
480
+ # # ]
481
+ #
482
+ # person.pets.size # => 0
483
+ # person.pets # => []
484
+ #
485
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
486
+ #
487
+ # You can pass +Integer+ or +String+ values, it finds the records
488
+ # responding to the +id+ and then deletes them from the database.
489
+ #
490
+ # person.pets.size # => 3
491
+ # person.pets
492
+ # # => [
493
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
494
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
495
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
496
+ # # ]
497
+ #
498
+ # person.pets.destroy("4")
499
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
500
+ #
501
+ # person.pets.size # => 2
502
+ # person.pets
503
+ # # => [
504
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
505
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
506
+ # # ]
507
+ #
508
+ # person.pets.destroy(5, 6)
509
+ # # => [
510
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
511
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
512
+ # # ]
513
+ #
514
+ # person.pets.size # => 0
515
+ # person.pets # => []
516
+ #
517
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
518
+ def destroy(*records)
519
+ proxy_association.destroy(*records)
520
+ end
521
+
522
+ ##
523
+ # :method: distinct
524
+ #
525
+ # :call-seq:
526
+ # distinct(value = true)
527
+ #
528
+ # Specifies whether the records should be unique or not.
529
+ #
530
+ # class Person < ActiveRecord::Base
531
+ # has_many :pets
532
+ # end
533
+ #
534
+ # person.pets.select(:name)
535
+ # # => [
536
+ # # #<Pet name: "Fancy-Fancy">,
537
+ # # #<Pet name: "Fancy-Fancy">
538
+ # # ]
539
+ #
540
+ # person.pets.select(:name).distinct
541
+ # # => [#<Pet name: "Fancy-Fancy">]
542
+ #
543
+ # person.pets.select(:name).distinct.distinct(false)
544
+ # # => [
545
+ # # #<Pet name: "Fancy-Fancy">,
546
+ # # #<Pet name: "Fancy-Fancy">
547
+ # # ]
548
+
549
+ #--
550
+ def uniq
551
+ proxy_association.uniq
552
+ end
553
+
554
+ ##
555
+ # :method: count
556
+ #
557
+ # :call-seq:
558
+ # count(column_name = nil, &block)
559
+ #
560
+ # Count all records.
561
+ #
562
+ # class Person < ActiveRecord::Base
563
+ # has_many :pets
564
+ # end
565
+ #
566
+ # # This will perform the count using SQL.
567
+ # person.pets.count # => 3
568
+ # person.pets
569
+ # # => [
570
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
571
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
572
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
573
+ # # ]
574
+ #
575
+ # Passing a block will select all of a person's pets in SQL and then
576
+ # perform the count using Ruby.
577
+ #
578
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
579
+
580
+ # Returns the size of the collection. If the collection hasn't been loaded,
581
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
582
+ #
583
+ # If the collection has been already loaded +size+ and +length+ are
584
+ # equivalent. If not and you are going to need the records anyway
585
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
586
+ #
587
+ # class Person < ActiveRecord::Base
588
+ # has_many :pets
589
+ # end
590
+ #
591
+ # person.pets.size # => 3
592
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
593
+ #
594
+ # person.pets # This will execute a SELECT * FROM query
595
+ # # => [
596
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
597
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
598
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
599
+ # # ]
600
+ #
601
+ # person.pets.size # => 3
602
+ # # Because the collection is already loaded, this will behave like
603
+ # # collection.size and no SQL count query is executed.
604
+ def size
605
+ proxy_association.size
606
+ end
607
+
608
+ ##
609
+ # :method: length
610
+ #
611
+ # :call-seq:
612
+ # length()
613
+ #
614
+ # Returns the size of the collection calling +size+ on the target.
615
+ # If the collection has been already loaded, +length+ and +size+ are
616
+ # equivalent. If not and you are going to need the records anyway this
617
+ # method will take one less query. Otherwise +size+ is more efficient.
618
+ #
619
+ # class Person < ActiveRecord::Base
620
+ # has_many :pets
621
+ # end
622
+ #
623
+ # person.pets.length # => 3
624
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
625
+ #
626
+ # # Because the collection is loaded, you can
627
+ # # call the collection with no additional queries:
628
+ # person.pets
629
+ # # => [
630
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
631
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
632
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
633
+ # # ]
634
+
635
+ # Returns +true+ if the collection is empty. If the collection has been
636
+ # loaded it is equivalent
637
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
638
+ # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
639
+ # not already been loaded and you are going to fetch the records anyway it
640
+ # is better to check <tt>collection.length.zero?</tt>.
641
+ #
642
+ # class Person < ActiveRecord::Base
643
+ # has_many :pets
644
+ # end
645
+ #
646
+ # person.pets.count # => 1
647
+ # person.pets.empty? # => false
648
+ #
649
+ # person.pets.delete_all
650
+ #
651
+ # person.pets.count # => 0
652
+ # person.pets.empty? # => true
653
+ def empty?
654
+ proxy_association.empty?
655
+ end
656
+
657
+ ##
658
+ # :method: any?
659
+ #
660
+ # :call-seq:
661
+ # any?()
662
+ #
663
+ # Returns +true+ if the collection is not empty.
664
+ #
665
+ # class Person < ActiveRecord::Base
666
+ # has_many :pets
667
+ # end
668
+ #
669
+ # person.pets.count # => 0
670
+ # person.pets.any? # => false
671
+ #
672
+ # person.pets << Pet.new(name: 'Snoop')
673
+ # person.pets.count # => 1
674
+ # person.pets.any? # => true
675
+ #
676
+ # You can also pass a +block+ to define criteria. The behavior
677
+ # is the same, it returns true if the collection based on the
678
+ # criteria is not empty.
679
+ #
680
+ # person.pets
681
+ # # => [#<Pet name: "Snoop", group: "dogs">]
682
+ #
683
+ # person.pets.any? do |pet|
684
+ # pet.group == 'cats'
685
+ # end
686
+ # # => false
687
+ #
688
+ # person.pets.any? do |pet|
689
+ # pet.group == 'dogs'
690
+ # end
691
+ # # => true
692
+
693
+ ##
694
+ # :method: many?
695
+ #
696
+ # :call-seq:
697
+ # many?()
698
+ #
699
+ # Returns true if the collection has more than one record.
700
+ # Equivalent to <tt>collection.size > 1</tt>.
701
+ #
702
+ # class Person < ActiveRecord::Base
703
+ # has_many :pets
704
+ # end
705
+ #
706
+ # person.pets.count # => 1
707
+ # person.pets.many? # => false
708
+ #
709
+ # person.pets << Pet.new(name: 'Snoopy')
710
+ # person.pets.count # => 2
711
+ # person.pets.many? # => true
712
+ #
713
+ # You can also pass a +block+ to define criteria. The
714
+ # behavior is the same, it returns true if the collection
715
+ # based on the criteria has more than one record.
716
+ #
717
+ # person.pets
718
+ # # => [
719
+ # # #<Pet name: "Gorby", group: "cats">,
720
+ # # #<Pet name: "Puff", group: "cats">,
721
+ # # #<Pet name: "Snoop", group: "dogs">
722
+ # # ]
723
+ #
724
+ # person.pets.many? do |pet|
725
+ # pet.group == 'dogs'
726
+ # end
727
+ # # => false
728
+ #
729
+ # person.pets.many? do |pet|
730
+ # pet.group == 'cats'
731
+ # end
732
+ # # => true
733
+
734
+ # Returns +true+ if the given +record+ is present in the collection.
735
+ #
736
+ # class Person < ActiveRecord::Base
737
+ # has_many :pets
738
+ # end
739
+ #
740
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
741
+ #
742
+ # person.pets.include?(Pet.find(20)) # => true
743
+ # person.pets.include?(Pet.find(21)) # => false
744
+ def include?(record)
745
+ !!proxy_association.include?(record)
746
+ end
747
+
748
+ def proxy_association
749
+ @association
750
+ end
751
+
752
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
753
+ # contain the same number of elements and if each element is equal
754
+ # to the corresponding element in the +other+ array, otherwise returns
755
+ # +false+.
756
+ #
757
+ # class Person < ActiveRecord::Base
758
+ # has_many :pets
759
+ # end
760
+ #
761
+ # person.pets
762
+ # # => [
763
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
764
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
765
+ # # ]
766
+ #
767
+ # other = person.pets.to_ary
768
+ #
769
+ # person.pets == other
770
+ # # => true
771
+ #
772
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
773
+ #
774
+ # person.pets == other
775
+ # # => false
776
+ def ==(other)
777
+ proxy_association.target == other
778
+ end
779
+
780
+ # Returns a new array of objects from the collection. If the collection
781
+ # hasn't been loaded, it fetches the records from the database.
782
+ #
783
+ # class Person < ActiveRecord::Base
784
+ # has_many :pets
785
+ # end
786
+ #
787
+ # person.pets
788
+ # # => [
789
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
790
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
791
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
792
+ # # ]
793
+ #
794
+ # other_pets = person.pets.to_ary
795
+ # # => [
796
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
797
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
798
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
799
+ # # ]
800
+ #
801
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
802
+ #
803
+ # other_pets
804
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
805
+ #
806
+ # person.pets
807
+ # # This is not affected by replace
808
+ # # => [
809
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
810
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
811
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
812
+ # # ]
813
+ def to_ary
814
+ proxy_association.target.dup
815
+ end
816
+ alias_method :to_a, :to_ary
817
+
818
+ def records # :nodoc:
819
+ proxy_association.target
820
+ end
821
+
822
+ # Adds one or more +records+ to the collection by setting their foreign keys
823
+ # to the association's primary key. Returns +self+, so several appends may be
824
+ # chained together.
825
+ #
826
+ # class Person < ActiveRecord::Base
827
+ # has_many :pets
828
+ # end
829
+ #
830
+ # person.pets.size # => 0
831
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
832
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
833
+ # person.pets.size # => 3
834
+ #
835
+ # person.id # => 1
836
+ # person.pets
837
+ # # => [
838
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
839
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
840
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
841
+ # # ]
842
+ def <<(*records)
843
+ proxy_association.concat(records) && self
844
+ end
845
+ alias_method :push, :<<
846
+ alias_method :append, :<<
847
+
848
+ def prepend(*_args)
849
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
850
+ end
851
+
852
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
853
+ # of an array with the deleted objects, so methods can be chained. See
854
+ # +delete_all+ for more information.
855
+ # Note that because +delete_all+ removes records by directly
856
+ # running an SQL query into the database, the +updated_at+ column of
857
+ # the object is not changed.
858
+ def clear
859
+ delete_all
860
+ self
861
+ end
862
+
863
+ # Unloads the association. Returns +self+.
864
+ #
865
+ # class Person < ActiveRecord::Base
866
+ # has_many :pets
867
+ # end
868
+ #
869
+ # person.pets # fetches pets from the database
870
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
871
+ #
872
+ # person.pets # uses the pets cache
873
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
874
+ #
875
+ # person.pets.reset # clears the pets cache
876
+ #
877
+ # person.pets # fetches pets from the database
878
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
879
+ def reset
880
+ proxy_association.reset
881
+ self
882
+ end
883
+
884
+ def inspect
885
+ entries = records.take(11).map!(&:inspect)
886
+ entries[10] = "..." if entries.size == 11
887
+
888
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
889
+ end
890
+ end
891
+ end
892
+ end