activerecord 3.2.22.4 → 4.0.13

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2799 -617
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +23 -32
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +40 -34
  7. data/lib/active_record/association_relation.rb +22 -0
  8. data/lib/active_record/associations/alias_tracker.rb +4 -2
  9. data/lib/active_record/associations/association.rb +60 -46
  10. data/lib/active_record/associations/association_scope.rb +46 -40
  11. data/lib/active_record/associations/belongs_to_association.rb +17 -4
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +73 -56
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +130 -96
  21. data/lib/active_record/associations/collection_proxy.rb +916 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +15 -13
  23. data/lib/active_record/associations/has_many_association.rb +35 -8
  24. data/lib/active_record/associations/has_many_through_association.rb +37 -17
  25. data/lib/active_record/associations/has_one_association.rb +42 -19
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_association.rb +39 -22
  28. data/lib/active_record/associations/join_dependency/join_base.rb +2 -2
  29. data/lib/active_record/associations/join_dependency/join_part.rb +21 -8
  30. data/lib/active_record/associations/join_dependency.rb +30 -9
  31. data/lib/active_record/associations/join_helper.rb +1 -11
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +2 -2
  35. data/lib/active_record/associations/preloader/has_many_through.rb +6 -2
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/preloader.rb +20 -43
  39. data/lib/active_record/associations/singular_association.rb +11 -11
  40. data/lib/active_record/associations/through_association.rb +3 -3
  41. data/lib/active_record/associations.rb +223 -282
  42. data/lib/active_record/attribute_assignment.rb +134 -154
  43. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  44. data/lib/active_record/attribute_methods/dirty.rb +36 -29
  45. data/lib/active_record/attribute_methods/primary_key.rb +45 -31
  46. data/lib/active_record/attribute_methods/query.rb +5 -4
  47. data/lib/active_record/attribute_methods/read.rb +67 -90
  48. data/lib/active_record/attribute_methods/serialization.rb +133 -70
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +51 -45
  50. data/lib/active_record/attribute_methods/write.rb +34 -39
  51. data/lib/active_record/attribute_methods.rb +268 -108
  52. data/lib/active_record/autosave_association.rb +80 -73
  53. data/lib/active_record/base.rb +54 -451
  54. data/lib/active_record/callbacks.rb +60 -22
  55. data/lib/active_record/coders/yaml_column.rb +18 -21
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +347 -197
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +146 -138
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -19
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +19 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +151 -142
  62. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +499 -217
  64. data/lib/active_record/connection_adapters/abstract/transaction.rb +208 -0
  65. data/lib/active_record/connection_adapters/abstract_adapter.rb +209 -44
  66. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +169 -61
  67. data/lib/active_record/connection_adapters/column.rb +67 -36
  68. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +28 -29
  70. data/lib/active_record/connection_adapters/mysql_adapter.rb +200 -73
  71. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +98 -0
  72. data/lib/active_record/connection_adapters/postgresql/cast.rb +160 -0
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +240 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +374 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +183 -0
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +508 -0
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +544 -899
  79. data/lib/active_record/connection_adapters/schema_cache.rb +76 -16
  80. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +595 -16
  81. data/lib/active_record/connection_handling.rb +98 -0
  82. data/lib/active_record/core.rb +472 -0
  83. data/lib/active_record/counter_cache.rb +107 -108
  84. data/lib/active_record/dynamic_matchers.rb +115 -63
  85. data/lib/active_record/errors.rb +36 -18
  86. data/lib/active_record/explain.rb +15 -63
  87. data/lib/active_record/explain_registry.rb +30 -0
  88. data/lib/active_record/explain_subscriber.rb +8 -4
  89. data/lib/active_record/fixture_set/file.rb +55 -0
  90. data/lib/active_record/fixtures.rb +159 -155
  91. data/lib/active_record/inheritance.rb +93 -59
  92. data/lib/active_record/integration.rb +8 -8
  93. data/lib/active_record/locale/en.yml +8 -1
  94. data/lib/active_record/locking/optimistic.rb +39 -43
  95. data/lib/active_record/locking/pessimistic.rb +4 -4
  96. data/lib/active_record/log_subscriber.rb +19 -9
  97. data/lib/active_record/migration/command_recorder.rb +102 -33
  98. data/lib/active_record/migration/join_table.rb +15 -0
  99. data/lib/active_record/migration.rb +411 -173
  100. data/lib/active_record/model_schema.rb +81 -94
  101. data/lib/active_record/nested_attributes.rb +173 -131
  102. data/lib/active_record/null_relation.rb +67 -0
  103. data/lib/active_record/persistence.rb +254 -106
  104. data/lib/active_record/query_cache.rb +18 -36
  105. data/lib/active_record/querying.rb +19 -15
  106. data/lib/active_record/railtie.rb +113 -38
  107. data/lib/active_record/railties/console_sandbox.rb +3 -4
  108. data/lib/active_record/railties/controller_runtime.rb +4 -3
  109. data/lib/active_record/railties/databases.rake +115 -368
  110. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  111. data/lib/active_record/readonly_attributes.rb +7 -3
  112. data/lib/active_record/reflection.rb +110 -61
  113. data/lib/active_record/relation/batches.rb +29 -29
  114. data/lib/active_record/relation/calculations.rb +155 -125
  115. data/lib/active_record/relation/delegation.rb +94 -18
  116. data/lib/active_record/relation/finder_methods.rb +151 -203
  117. data/lib/active_record/relation/merger.rb +188 -0
  118. data/lib/active_record/relation/predicate_builder.rb +85 -42
  119. data/lib/active_record/relation/query_methods.rb +793 -146
  120. data/lib/active_record/relation/spawn_methods.rb +43 -150
  121. data/lib/active_record/relation.rb +293 -173
  122. data/lib/active_record/result.rb +48 -7
  123. data/lib/active_record/runtime_registry.rb +17 -0
  124. data/lib/active_record/sanitization.rb +41 -54
  125. data/lib/active_record/schema.rb +19 -12
  126. data/lib/active_record/schema_dumper.rb +41 -41
  127. data/lib/active_record/schema_migration.rb +46 -0
  128. data/lib/active_record/scoping/default.rb +56 -52
  129. data/lib/active_record/scoping/named.rb +78 -103
  130. data/lib/active_record/scoping.rb +54 -124
  131. data/lib/active_record/serialization.rb +6 -2
  132. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  133. data/lib/active_record/statement_cache.rb +26 -0
  134. data/lib/active_record/store.rb +131 -15
  135. data/lib/active_record/tasks/database_tasks.rb +204 -0
  136. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  137. data/lib/active_record/tasks/mysql_database_tasks.rb +144 -0
  138. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  140. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  141. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  142. data/lib/active_record/test_case.rb +67 -38
  143. data/lib/active_record/timestamp.rb +16 -11
  144. data/lib/active_record/transactions.rb +73 -51
  145. data/lib/active_record/validations/associated.rb +19 -13
  146. data/lib/active_record/validations/presence.rb +65 -0
  147. data/lib/active_record/validations/uniqueness.rb +110 -57
  148. data/lib/active_record/validations.rb +18 -17
  149. data/lib/active_record/version.rb +7 -6
  150. data/lib/active_record.rb +63 -45
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +45 -8
  152. data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +4 -0
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  154. data/lib/rails/generators/active_record/model/model_generator.rb +5 -4
  155. data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
  156. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  157. data/lib/rails/generators/active_record.rb +3 -5
  158. metadata +43 -29
  159. data/examples/associations.png +0 -0
  160. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  161. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  162. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  163. data/lib/active_record/dynamic_finder_match.rb +0 -68
  164. data/lib/active_record/dynamic_scope_match.rb +0 -23
  165. data/lib/active_record/fixtures/file.rb +0 -65
  166. data/lib/active_record/identity_map.rb +0 -162
  167. data/lib/active_record/observer.rb +0 -121
  168. data/lib/active_record/session_store.rb +0 -360
  169. data/lib/rails/generators/active_record/migration.rb +0 -15
  170. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  171. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  172. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  173. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -18,14 +18,8 @@ module ActiveRecord
18
18
  # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
19
19
  # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
20
20
  #
21
- # This class has most of the basic instance methods removed, and delegates
22
- # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
23
- # corner case, it even removes the +class+ method and that's why you get
24
- #
25
- # blog.posts.class # => Array
26
- #
27
- # though the object behind <tt>blog.posts</tt> is not an Array, but an
28
- # ActiveRecord::Associations::HasManyAssociation.
21
+ # This class delegates unknown methods to <tt>@target</tt> via
22
+ # <tt>method_missing</tt>.
29
23
  #
30
24
  # The <tt>@target</tt> object is not \loaded until needed. For example,
31
25
  #
@@ -33,97 +27,956 @@ module ActiveRecord
33
27
  #
34
28
  # is computed directly through SQL and does not trigger by itself the
35
29
  # instantiation of the actual post records.
36
- class CollectionProxy # :nodoc:
37
- alias :proxy_extend :extend
30
+ class CollectionProxy < Relation
31
+ delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
38
32
 
39
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
33
+ def initialize(klass, association) #:nodoc:
34
+ @association = association
35
+ super klass, klass.arel_table
36
+ self.default_scoped = true
37
+ merge! association.scope(nullify: false)
38
+ end
40
39
 
41
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
42
- :lock, :readonly, :having, :pluck, :to => :scoped
40
+ def target
41
+ @association.target
42
+ end
43
43
 
44
- delegate :target, :load_target, :loaded?, :to => :@association
44
+ def load_target
45
+ @association.load_target
46
+ end
45
47
 
46
- delegate :select, :find, :first, :last,
47
- :build, :create, :create!,
48
- :concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
49
- :sum, :count, :size, :length, :empty?,
50
- :any?, :many?, :include?,
51
- :to => :@association
48
+ # Returns +true+ if the association has been loaded, otherwise +false+.
49
+ #
50
+ # person.pets.loaded? # => false
51
+ # person.pets
52
+ # person.pets.loaded? # => true
53
+ def loaded?
54
+ @association.loaded?
55
+ end
52
56
 
53
- def initialize(association)
54
- @association = association
55
- Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
57
+ # Works in two ways.
58
+ #
59
+ # *First:* Specify a subset of fields to be selected from the result set.
60
+ #
61
+ # class Person < ActiveRecord::Base
62
+ # has_many :pets
63
+ # end
64
+ #
65
+ # person.pets
66
+ # # => [
67
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
68
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
69
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
70
+ # # ]
71
+ #
72
+ # person.pets.select(:name)
73
+ # # => [
74
+ # # #<Pet id: nil, name: "Fancy-Fancy">,
75
+ # # #<Pet id: nil, name: "Spook">,
76
+ # # #<Pet id: nil, name: "Choo-Choo">
77
+ # # ]
78
+ #
79
+ # person.pets.select([:id, :name])
80
+ # # => [
81
+ # # #<Pet id: 1, name: "Fancy-Fancy">,
82
+ # # #<Pet id: 2, name: "Spook">,
83
+ # # #<Pet id: 3, name: "Choo-Choo">
84
+ # # ]
85
+ #
86
+ # Be careful because this also means you're initializing a model
87
+ # object with only the fields that you've selected. If you attempt
88
+ # to access a field that is not in the initialized record you'll
89
+ # receive:
90
+ #
91
+ # person.pets.select(:name).first.person_id
92
+ # # => ActiveModel::MissingAttributeError: missing attribute: person_id
93
+ #
94
+ # *Second:* You can pass a block so it can be used just like Array#select.
95
+ # This builds an array of objects from the database for the scope,
96
+ # converting them into an array and iterating through them using
97
+ # Array#select.
98
+ #
99
+ # person.pets.select { |pet| pet.name =~ /oo/ }
100
+ # # => [
101
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
102
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
103
+ # # ]
104
+ #
105
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
106
+ # # => [
107
+ # # #<Pet id: 2, name: "Spook">,
108
+ # # #<Pet id: 3, name: "Choo-Choo">
109
+ # # ]
110
+ def select(select = nil, &block)
111
+ @association.select(select, &block)
112
+ end
113
+
114
+ # Finds an object in the collection responding to the +id+. Uses the same
115
+ # rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
116
+ # error if the object can not be found.
117
+ #
118
+ # class Person < ActiveRecord::Base
119
+ # has_many :pets
120
+ # end
121
+ #
122
+ # person.pets
123
+ # # => [
124
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
125
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
126
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
127
+ # # ]
128
+ #
129
+ # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
130
+ # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
131
+ #
132
+ # person.pets.find(2) { |pet| pet.name.downcase! }
133
+ # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
134
+ #
135
+ # person.pets.find(2, 3)
136
+ # # => [
137
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
138
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
139
+ # # ]
140
+ def find(*args, &block)
141
+ @association.find(*args, &block)
142
+ end
143
+
144
+ # Returns the first record, or the first +n+ records, from the collection.
145
+ # If the collection is empty, the first form returns +nil+, and the second
146
+ # form returns an empty array.
147
+ #
148
+ # class Person < ActiveRecord::Base
149
+ # has_many :pets
150
+ # end
151
+ #
152
+ # person.pets
153
+ # # => [
154
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
155
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
156
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
157
+ # # ]
158
+ #
159
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
160
+ #
161
+ # person.pets.first(2)
162
+ # # => [
163
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
164
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
165
+ # # ]
166
+ #
167
+ # another_person_without.pets # => []
168
+ # another_person_without.pets.first # => nil
169
+ # another_person_without.pets.first(3) # => []
170
+ def first(*args)
171
+ @association.first(*args)
172
+ end
173
+
174
+ # Returns the last record, or the last +n+ records, from the collection.
175
+ # If the collection is empty, the first form returns +nil+, and the second
176
+ # form returns an empty array.
177
+ #
178
+ # class Person < ActiveRecord::Base
179
+ # has_many :pets
180
+ # end
181
+ #
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.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
190
+ #
191
+ # person.pets.last(2)
192
+ # # => [
193
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
194
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
195
+ # # ]
196
+ #
197
+ # another_person_without.pets # => []
198
+ # another_person_without.pets.last # => nil
199
+ # another_person_without.pets.last(3) # => []
200
+ def last(*args)
201
+ @association.last(*args)
56
202
  end
57
203
 
204
+ # Returns a new object of the collection type that has been instantiated
205
+ # with +attributes+ and linked to this object, but have not yet been saved.
206
+ # You can pass an array of attributes hashes, this will return an array
207
+ # with the new objects.
208
+ #
209
+ # class Person
210
+ # has_many :pets
211
+ # end
212
+ #
213
+ # person.pets.build
214
+ # # => #<Pet id: nil, name: nil, person_id: 1>
215
+ #
216
+ # person.pets.build(name: 'Fancy-Fancy')
217
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
218
+ #
219
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
220
+ # # => [
221
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
222
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
223
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
224
+ # # ]
225
+ #
226
+ # person.pets.size # => 5 # size of the collection
227
+ # person.pets.count # => 0 # count from database
228
+ def build(attributes = {}, &block)
229
+ @association.build(attributes, &block)
230
+ end
58
231
  alias_method :new, :build
59
232
 
60
- def proxy_association
61
- @association
233
+ # Returns a new object of the collection type that has been instantiated with
234
+ # attributes, linked to this object and that has already been saved (if it
235
+ # passes the validations).
236
+ #
237
+ # class Person
238
+ # has_many :pets
239
+ # end
240
+ #
241
+ # person.pets.create(name: 'Fancy-Fancy')
242
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
243
+ #
244
+ # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
245
+ # # => [
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 # => 3
251
+ # person.pets.count # => 3
252
+ #
253
+ # person.pets.find(1, 2, 3)
254
+ # # => [
255
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
256
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
257
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
258
+ # # ]
259
+ def create(attributes = {}, &block)
260
+ @association.create(attributes, &block)
62
261
  end
63
262
 
64
- def scoped
65
- association = @association
66
- association.scoped.extending do
67
- define_method(:proxy_association) { association }
68
- end
263
+ # Like +create+, except that if the record is invalid, raises an exception.
264
+ #
265
+ # class Person
266
+ # has_many :pets
267
+ # end
268
+ #
269
+ # class Pet
270
+ # validates :name, presence: true
271
+ # end
272
+ #
273
+ # person.pets.create!(name: nil)
274
+ # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
275
+ def create!(attributes = {}, &block)
276
+ @association.create!(attributes, &block)
69
277
  end
70
278
 
71
- def respond_to?(name, include_private = false)
72
- super ||
73
- (load_target && target.respond_to?(name, include_private)) ||
74
- proxy_association.klass.respond_to?(name, include_private)
279
+ # Add one or more records to the collection by setting their foreign keys
280
+ # to the association's primary key. Since << flattens its argument list and
281
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
282
+ # so method calls may be chained.
283
+ #
284
+ # class Person < ActiveRecord::Base
285
+ # has_many :pets
286
+ # end
287
+ #
288
+ # person.pets.size # => 0
289
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
290
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
291
+ # person.pets.size # => 3
292
+ #
293
+ # person.id # => 1
294
+ # person.pets
295
+ # # => [
296
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
297
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
298
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
299
+ # # ]
300
+ #
301
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
302
+ # person.pets.size # => 5
303
+ def concat(*records)
304
+ @association.concat(*records)
75
305
  end
76
306
 
77
- def method_missing(method, *args, &block)
78
- match = DynamicFinderMatch.match(method)
79
- if match && match.instantiator?
80
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |record|
81
- proxy_association.send :set_owner_attributes, record
82
- proxy_association.send :add_to_target, record
83
- yield(record) if block_given?
84
- end.tap do |record|
85
- proxy_association.send :set_inverse_instance, record
86
- end
307
+ # Replaces this collection with +other_array+. This will perform a diff
308
+ # and delete/add only records that have changed.
309
+ #
310
+ # class Person < ActiveRecord::Base
311
+ # has_many :pets
312
+ # end
313
+ #
314
+ # person.pets
315
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
316
+ #
317
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
318
+ #
319
+ # person.pets.replace(other_pets)
320
+ #
321
+ # person.pets
322
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
323
+ #
324
+ # If the supplied array has an incorrect association type, it raises
325
+ # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
326
+ #
327
+ # person.pets.replace(["doo", "ggie", "gaga"])
328
+ # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
329
+ def replace(other_array)
330
+ @association.replace(other_array)
331
+ end
87
332
 
88
- elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
89
- if load_target
90
- if target.respond_to?(method)
91
- target.send(method, *args, &block)
92
- else
93
- begin
94
- super
95
- rescue NoMethodError => e
96
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
97
- end
98
- end
99
- end
333
+ # Deletes all the records from the collection. For +has_many+ associations,
334
+ # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
335
+ # option. Returns an array with the deleted records.
336
+ #
337
+ # If no <tt>:dependent</tt> option is given, then it will follow the
338
+ # default strategy. The default strategy is <tt>:nullify</tt>. This
339
+ # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
340
+ # the default strategy is +delete_all+.
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_all
355
+ # # => [
356
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
357
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
358
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
359
+ # # ]
360
+ #
361
+ # person.pets.size # => 0
362
+ # person.pets # => []
363
+ #
364
+ # Pet.find(1, 2, 3)
365
+ # # => [
366
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
367
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
368
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
369
+ # # ]
370
+ #
371
+ # If it is set to <tt>:destroy</tt> all the objects from the collection
372
+ # are removed by calling their +destroy+ method. See +destroy+ for more
373
+ # information.
374
+ #
375
+ # class Person < ActiveRecord::Base
376
+ # has_many :pets, dependent: :destroy
377
+ # end
378
+ #
379
+ # person.pets.size # => 3
380
+ # person.pets
381
+ # # => [
382
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
383
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
384
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
385
+ # # ]
386
+ #
387
+ # person.pets.delete_all
388
+ # # => [
389
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
390
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
391
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
392
+ # # ]
393
+ #
394
+ # Pet.find(1, 2, 3)
395
+ # # => ActiveRecord::RecordNotFound
396
+ #
397
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
398
+ # *without* calling their +destroy+ method.
399
+ #
400
+ # class Person < ActiveRecord::Base
401
+ # has_many :pets, dependent: :delete_all
402
+ # end
403
+ #
404
+ # person.pets.size # => 3
405
+ # person.pets
406
+ # # => [
407
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
408
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
409
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
410
+ # # ]
411
+ #
412
+ # person.pets.delete_all
413
+ # # => [
414
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
415
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
416
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
417
+ # # ]
418
+ #
419
+ # Pet.find(1, 2, 3)
420
+ # # => ActiveRecord::RecordNotFound
421
+ def delete_all
422
+ @association.delete_all
423
+ end
100
424
 
101
- else
102
- scoped.readonly(nil).send(method, *args, &block)
103
- end
425
+ # Deletes the records of the collection directly from the database.
426
+ # This will _always_ remove the records ignoring the +:dependent+
427
+ # option.
428
+ #
429
+ # class Person < ActiveRecord::Base
430
+ # has_many :pets
431
+ # end
432
+ #
433
+ # person.pets.size # => 3
434
+ # person.pets
435
+ # # => [
436
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
437
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
438
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
439
+ # # ]
440
+ #
441
+ # person.pets.destroy_all
442
+ #
443
+ # person.pets.size # => 0
444
+ # person.pets # => []
445
+ #
446
+ # Pet.find(1) # => Couldn't find Pet with id=1
447
+ def destroy_all
448
+ @association.destroy_all
104
449
  end
105
450
 
106
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
107
- # removal above doesn't catch it. Loads the \target if needed.
108
- def ===(other)
109
- other === load_target
451
+ # Deletes the +records+ supplied and removes them from the collection. For
452
+ # +has_many+ associations, the deletion is done according to the strategy
453
+ # specified by the <tt>:dependent</tt> option. Returns an array with the
454
+ # deleted records.
455
+ #
456
+ # If no <tt>:dependent</tt> option is given, then it will follow the default
457
+ # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
458
+ # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
459
+ # strategy is +delete_all+.
460
+ #
461
+ # class Person < ActiveRecord::Base
462
+ # has_many :pets # dependent: :nullify option by default
463
+ # end
464
+ #
465
+ # person.pets.size # => 3
466
+ # person.pets
467
+ # # => [
468
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
469
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
470
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
471
+ # # ]
472
+ #
473
+ # person.pets.delete(Pet.find(1))
474
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
475
+ #
476
+ # person.pets.size # => 2
477
+ # person.pets
478
+ # # => [
479
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
480
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
481
+ # # ]
482
+ #
483
+ # Pet.find(1)
484
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
485
+ #
486
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
487
+ # their +destroy+ method. See +destroy+ for more information.
488
+ #
489
+ # class Person < ActiveRecord::Base
490
+ # has_many :pets, dependent: :destroy
491
+ # end
492
+ #
493
+ # person.pets.size # => 3
494
+ # person.pets
495
+ # # => [
496
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
497
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
498
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
499
+ # # ]
500
+ #
501
+ # person.pets.delete(Pet.find(1), Pet.find(3))
502
+ # # => [
503
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
504
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
505
+ # # ]
506
+ #
507
+ # person.pets.size # => 1
508
+ # person.pets
509
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
510
+ #
511
+ # Pet.find(1, 3)
512
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
513
+ #
514
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
515
+ # *without* calling their +destroy+ method.
516
+ #
517
+ # class Person < ActiveRecord::Base
518
+ # has_many :pets, dependent: :delete_all
519
+ # end
520
+ #
521
+ # person.pets.size # => 3
522
+ # person.pets
523
+ # # => [
524
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
525
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
526
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
527
+ # # ]
528
+ #
529
+ # person.pets.delete(Pet.find(1))
530
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
531
+ #
532
+ # person.pets.size # => 2
533
+ # person.pets
534
+ # # => [
535
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
536
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
537
+ # # ]
538
+ #
539
+ # Pet.find(1)
540
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
541
+ #
542
+ # You can pass +Fixnum+ or +String+ values, it finds the records
543
+ # responding to the +id+ and executes delete on them.
544
+ #
545
+ # class Person < ActiveRecord::Base
546
+ # has_many :pets
547
+ # end
548
+ #
549
+ # person.pets.size # => 3
550
+ # person.pets
551
+ # # => [
552
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
553
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
554
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
555
+ # # ]
556
+ #
557
+ # person.pets.delete("1")
558
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
559
+ #
560
+ # person.pets.delete(2, 3)
561
+ # # => [
562
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
563
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
564
+ # # ]
565
+ def delete(*records)
566
+ @association.delete(*records)
110
567
  end
111
568
 
569
+ # Destroys the +records+ supplied and removes them from the collection.
570
+ # This method will _always_ remove record from the database ignoring
571
+ # the +:dependent+ option. Returns an array with the removed records.
572
+ #
573
+ # class Person < ActiveRecord::Base
574
+ # has_many :pets
575
+ # end
576
+ #
577
+ # person.pets.size # => 3
578
+ # person.pets
579
+ # # => [
580
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
581
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
582
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
583
+ # # ]
584
+ #
585
+ # person.pets.destroy(Pet.find(1))
586
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
587
+ #
588
+ # person.pets.size # => 2
589
+ # person.pets
590
+ # # => [
591
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
592
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
593
+ # # ]
594
+ #
595
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
596
+ # # => [
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 # => 0
602
+ # person.pets # => []
603
+ #
604
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
605
+ #
606
+ # You can pass +Fixnum+ or +String+ values, it finds the records
607
+ # responding to the +id+ and then deletes them from the database.
608
+ #
609
+ # person.pets.size # => 3
610
+ # person.pets
611
+ # # => [
612
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
613
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
614
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
615
+ # # ]
616
+ #
617
+ # person.pets.destroy("4")
618
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
619
+ #
620
+ # person.pets.size # => 2
621
+ # person.pets
622
+ # # => [
623
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
624
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
625
+ # # ]
626
+ #
627
+ # person.pets.destroy(5, 6)
628
+ # # => [
629
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
630
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
631
+ # # ]
632
+ #
633
+ # person.pets.size # => 0
634
+ # person.pets # => []
635
+ #
636
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
637
+ def destroy(*records)
638
+ @association.destroy(*records)
639
+ end
640
+
641
+ # Specifies whether the records should be unique or not.
642
+ #
643
+ # class Person < ActiveRecord::Base
644
+ # has_many :pets
645
+ # end
646
+ #
647
+ # person.pets.select(:name)
648
+ # # => [
649
+ # # #<Pet name: "Fancy-Fancy">,
650
+ # # #<Pet name: "Fancy-Fancy">
651
+ # # ]
652
+ #
653
+ # person.pets.select(:name).distinct
654
+ # # => [#<Pet name: "Fancy-Fancy">]
655
+ def distinct
656
+ @association.distinct
657
+ end
658
+ alias uniq distinct
659
+
660
+ # Count all records using SQL.
661
+ #
662
+ # class Person < ActiveRecord::Base
663
+ # has_many :pets
664
+ # end
665
+ #
666
+ # person.pets.count # => 3
667
+ # person.pets
668
+ # # => [
669
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
670
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
671
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
672
+ # # ]
673
+ def count(column_name = nil, options = {})
674
+ @association.count(column_name, options)
675
+ end
676
+
677
+ # Returns the size of the collection. If the collection hasn't been loaded,
678
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
679
+ #
680
+ # If the collection has been already loaded +size+ and +length+ are
681
+ # equivalent. If not and you are going to need the records anyway
682
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
683
+ #
684
+ # class Person < ActiveRecord::Base
685
+ # has_many :pets
686
+ # end
687
+ #
688
+ # person.pets.size # => 3
689
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
690
+ #
691
+ # person.pets # This will execute a SELECT * FROM query
692
+ # # => [
693
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
694
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
695
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
696
+ # # ]
697
+ #
698
+ # person.pets.size # => 3
699
+ # # Because the collection is already loaded, this will behave like
700
+ # # collection.size and no SQL count query is executed.
701
+ def size
702
+ @association.size
703
+ end
704
+
705
+ # Returns the size of the collection calling +size+ on the target.
706
+ # If the collection has been already loaded, +length+ and +size+ are
707
+ # equivalent. If not and you are going to need the records anyway this
708
+ # method will take one less query. Otherwise +size+ is more efficient.
709
+ #
710
+ # class Person < ActiveRecord::Base
711
+ # has_many :pets
712
+ # end
713
+ #
714
+ # person.pets.length # => 3
715
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
716
+ #
717
+ # # Because the collection is loaded, you can
718
+ # # call the collection with no additional queries:
719
+ # person.pets
720
+ # # => [
721
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
722
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
723
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
724
+ # # ]
725
+ def length
726
+ @association.length
727
+ end
728
+
729
+ # Returns +true+ if the collection is empty. If the collection has been
730
+ # loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
731
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
732
+ # it is equivalent to <tt>collection.exists?</tt>. If the collection has
733
+ # not already been loaded and you are going to fetch the records anyway it
734
+ # is better to check <tt>collection.length.zero?</tt>.
735
+ #
736
+ # class Person < ActiveRecord::Base
737
+ # has_many :pets
738
+ # end
739
+ #
740
+ # person.pets.count # => 1
741
+ # person.pets.empty? # => false
742
+ #
743
+ # person.pets.delete_all
744
+ #
745
+ # person.pets.count # => 0
746
+ # person.pets.empty? # => true
747
+ def empty?
748
+ @association.empty?
749
+ end
750
+
751
+ # Returns +true+ if the collection is not empty.
752
+ #
753
+ # class Person < ActiveRecord::Base
754
+ # has_many :pets
755
+ # end
756
+ #
757
+ # person.pets.count # => 0
758
+ # person.pets.any? # => false
759
+ #
760
+ # person.pets << Pet.new(name: 'Snoop')
761
+ # person.pets.count # => 0
762
+ # person.pets.any? # => true
763
+ #
764
+ # You can also pass a block to define criteria. The behavior
765
+ # is the same, it returns true if the collection based on the
766
+ # criteria is not empty.
767
+ #
768
+ # person.pets
769
+ # # => [#<Pet name: "Snoop", group: "dogs">]
770
+ #
771
+ # person.pets.any? do |pet|
772
+ # pet.group == 'cats'
773
+ # end
774
+ # # => false
775
+ #
776
+ # person.pets.any? do |pet|
777
+ # pet.group == 'dogs'
778
+ # end
779
+ # # => true
780
+ def any?(&block)
781
+ @association.any?(&block)
782
+ end
783
+
784
+ # Returns true if the collection has more than one record.
785
+ # Equivalent to <tt>collection.size > 1</tt>.
786
+ #
787
+ # class Person < ActiveRecord::Base
788
+ # has_many :pets
789
+ # end
790
+ #
791
+ # person.pets.count #=> 1
792
+ # person.pets.many? #=> false
793
+ #
794
+ # person.pets << Pet.new(name: 'Snoopy')
795
+ # person.pets.count #=> 2
796
+ # person.pets.many? #=> true
797
+ #
798
+ # You can also pass a block to define criteria. The
799
+ # behavior is the same, it returns true if the collection
800
+ # based on the criteria has more than one record.
801
+ #
802
+ # person.pets
803
+ # # => [
804
+ # # #<Pet name: "Gorby", group: "cats">,
805
+ # # #<Pet name: "Puff", group: "cats">,
806
+ # # #<Pet name: "Snoop", group: "dogs">
807
+ # # ]
808
+ #
809
+ # person.pets.many? do |pet|
810
+ # pet.group == 'dogs'
811
+ # end
812
+ # # => false
813
+ #
814
+ # person.pets.many? do |pet|
815
+ # pet.group == 'cats'
816
+ # end
817
+ # # => true
818
+ def many?(&block)
819
+ @association.many?(&block)
820
+ end
821
+
822
+ # Returns +true+ if the given object is present in the collection.
823
+ #
824
+ # class Person < ActiveRecord::Base
825
+ # has_many :pets
826
+ # end
827
+ #
828
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
829
+ #
830
+ # person.pets.include?(Pet.find(20)) # => true
831
+ # person.pets.include?(Pet.find(21)) # => false
832
+ def include?(record)
833
+ !!@association.include?(record)
834
+ end
835
+
836
+ def arel
837
+ scope.arel
838
+ end
839
+
840
+ def proxy_association
841
+ @association
842
+ end
843
+
844
+ # We don't want this object to be put on the scoping stack, because
845
+ # that could create an infinite loop where we call an @association
846
+ # method, which gets the current scope, which is this object, which
847
+ # delegates to @association, and so on.
848
+ def scoping
849
+ @association.scope.scoping { yield }
850
+ end
851
+
852
+ # Returns a <tt>Relation</tt> object for the records in this association
853
+ def scope
854
+ @association.scope
855
+ end
856
+ alias spawn scope
857
+
858
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
859
+ # contain the same number of elements and if each element is equal
860
+ # to the corresponding element in the other array, otherwise returns
861
+ # +false+.
862
+ #
863
+ # class Person < ActiveRecord::Base
864
+ # has_many :pets
865
+ # end
866
+ #
867
+ # person.pets
868
+ # # => [
869
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
870
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
871
+ # # ]
872
+ #
873
+ # other = person.pets.to_ary
874
+ #
875
+ # person.pets == other
876
+ # # => true
877
+ #
878
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
879
+ #
880
+ # person.pets == other
881
+ # # => false
882
+ def ==(other)
883
+ load_target == other
884
+ end
885
+
886
+ # Returns a new array of objects from the collection. If the collection
887
+ # hasn't been loaded, it fetches the records from the database.
888
+ #
889
+ # class Person < ActiveRecord::Base
890
+ # has_many :pets
891
+ # end
892
+ #
893
+ # person.pets
894
+ # # => [
895
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
896
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
897
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
898
+ # # ]
899
+ #
900
+ # other_pets = person.pets.to_ary
901
+ # # => [
902
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
903
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
904
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
905
+ # # ]
906
+ #
907
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
908
+ #
909
+ # other_pets
910
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
911
+ #
912
+ # person.pets
913
+ # # This is not affected by replace
914
+ # # => [
915
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
916
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
917
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
918
+ # # ]
112
919
  def to_ary
113
920
  load_target.dup
114
921
  end
115
922
  alias_method :to_a, :to_ary
116
923
 
924
+ # Adds one or more +records+ to the collection by setting their foreign keys
925
+ # to the association's primary key. Returns +self+, so several appends may be
926
+ # chained together.
927
+ #
928
+ # class Person < ActiveRecord::Base
929
+ # has_many :pets
930
+ # end
931
+ #
932
+ # person.pets.size # => 0
933
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
934
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
935
+ # person.pets.size # => 3
936
+ #
937
+ # person.id # => 1
938
+ # person.pets
939
+ # # => [
940
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
941
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
942
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
943
+ # # ]
117
944
  def <<(*records)
118
945
  proxy_association.concat(records) && self
119
946
  end
120
947
  alias_method :push, :<<
948
+ alias_method :append, :<<
949
+
950
+ def prepend(*args)
951
+ raise NoMethodError, "prepend on association is not defined. Please use << or append"
952
+ end
121
953
 
954
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
955
+ # of an array with the deleted objects, so methods can be chained. See
956
+ # +delete_all+ for more information.
122
957
  def clear
123
958
  delete_all
124
959
  self
125
960
  end
126
961
 
962
+ # Reloads the collection from the database. Returns +self+.
963
+ # Equivalent to <tt>collection(true)</tt>.
964
+ #
965
+ # class Person < ActiveRecord::Base
966
+ # has_many :pets
967
+ # end
968
+ #
969
+ # person.pets # fetches pets from the database
970
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
971
+ #
972
+ # person.pets # uses the pets cache
973
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
974
+ #
975
+ # person.pets.reload # fetches pets from the database
976
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
977
+ #
978
+ # person.pets(true) # fetches pets from the database
979
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
127
980
  def reload
128
981
  proxy_association.reload
129
982
  self