tallty_duck_record 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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