activerecord 3.2.22.5 → 4.0.0.beta1

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