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
@@ -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,13 @@ module ActiveRecord
33
34
  reload
34
35
  end
35
36
 
36
- proxy
37
+ if owner.new_record?
38
+ # Cache the proxy separately before the owner has an id
39
+ # or else a post-save proxy will still lack the id
40
+ @new_record_proxy ||= CollectionProxy.new(klass, self)
41
+ else
42
+ @proxy ||= CollectionProxy.new(klass, self)
43
+ end
37
44
  end
38
45
 
39
46
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -43,37 +50,26 @@ module ActiveRecord
43
50
 
44
51
  # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
45
52
  def ids_reader
46
- if owner.new_record? || loaded? || options[:finder_sql]
53
+ if loaded? || options[:finder_sql]
47
54
  load_target.map do |record|
48
55
  record.send(reflection.association_primary_key)
49
56
  end
50
57
  else
51
58
  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)
59
+ scope.pluck(column)
64
60
  end
65
61
  end
66
62
 
67
63
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
68
64
  def ids_writer(ids)
69
65
  pk_column = reflection.primary_key_column
70
- ids = Array.wrap(ids).reject { |id| id.blank? }
66
+ ids = Array(ids).reject { |id| id.blank? }
71
67
  ids.map! { |i| pk_column.type_cast(i) }
72
68
  replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
73
69
  end
74
70
 
75
71
  def reset
76
- @loaded = false
72
+ super
77
73
  @target = []
78
74
  end
79
75
 
@@ -81,7 +77,7 @@ module ActiveRecord
81
77
  if block_given?
82
78
  load_target.select.each { |e| yield e }
83
79
  else
84
- scoped.select(select)
80
+ scope.select(select)
85
81
  end
86
82
  end
87
83
 
@@ -91,8 +87,20 @@ module ActiveRecord
91
87
  else
92
88
  if options[:finder_sql]
93
89
  find_by_scan(*args)
90
+ elsif options[:inverse_of] && loaded?
91
+ args_flatten = args.flatten
92
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
93
+
94
+ result = find_by_scan(*args)
95
+
96
+ result_size = Array(result).size
97
+ if !result || result_size != args_flatten.size
98
+ scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
99
+ else
100
+ result
101
+ end
94
102
  else
95
- scoped.find(*args)
103
+ scope.find(*args)
96
104
  end
97
105
  end
98
106
  end
@@ -105,26 +113,27 @@ module ActiveRecord
105
113
  first_or_last(:last, *args)
106
114
  end
107
115
 
108
- def build(attributes = {}, options = {}, &block)
116
+ def build(attributes = {}, &block)
109
117
  if attributes.is_a?(Array)
110
- attributes.collect { |attr| build(attr, options, &block) }
118
+ attributes.collect { |attr| build(attr, &block) }
111
119
  else
112
- add_to_target(build_record(attributes, options)) do |record|
120
+ add_to_target(build_record(attributes)) do |record|
113
121
  yield(record) if block_given?
114
122
  end
115
123
  end
116
124
  end
117
125
 
118
- def create(attributes = {}, options = {}, &block)
119
- create_record(attributes, options, &block)
126
+ def create(attributes = {}, &block)
127
+ _create_record(attributes, &block)
120
128
  end
121
129
 
122
- def create!(attributes = {}, options = {}, &block)
123
- create_record(attributes, options, true, &block)
130
+ def create!(attributes = {}, &block)
131
+ _create_record(attributes, true, &block)
124
132
  end
125
133
 
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.
134
+ # Add +records+ to this association. Returns +self+ so method calls may
135
+ # be chained. Since << flattens its argument list and inserts each record,
136
+ # +push+ and +concat+ behave identically.
128
137
  def concat(*records)
129
138
  load_target if owner.new_record?
130
139
 
@@ -150,23 +159,16 @@ module ActiveRecord
150
159
  end
151
160
  end
152
161
 
153
- # Remove all records from this association
162
+ # Remove all records from this association.
154
163
  #
155
164
  # See delete for more info.
156
165
  def delete_all
157
- delete(load_target).tap do
166
+ delete(:all).tap do
158
167
  reset
159
168
  loaded!
160
169
  end
161
170
  end
162
171
 
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
172
  # Destroy all the records from this association.
171
173
  #
172
174
  # See destroy for more info.
@@ -177,21 +179,10 @@ module ActiveRecord
177
179
  end
178
180
  end
179
181
 
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
182
  # Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
190
183
  # association, it will be used for the query. Otherwise, construct options and pass them with
191
184
  # scope to the target class's +count+.
192
185
  def count(column_name = nil, count_options = {})
193
- return 0 if owner.new_record?
194
-
195
186
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
196
187
 
197
188
  if options[:counter_sql] || options[:finder_sql]
@@ -201,13 +192,14 @@ module ActiveRecord
201
192
 
202
193
  reflection.klass.count_by_sql(custom_counter_sql)
203
194
  else
204
- if options[:uniq]
195
+ relation = scope
196
+ if association_scope.distinct_value
205
197
  # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
206
198
  column_name ||= reflection.klass.primary_key
207
- count_options.merge!(:distinct => true)
199
+ relation = relation.distinct
208
200
  end
209
201
 
210
- value = scoped.count(column_name, count_options)
202
+ value = relation.count(column_name, count_options)
211
203
 
212
204
  limit = options[:limit]
213
205
  offset = options[:offset]
@@ -228,7 +220,27 @@ module ActiveRecord
228
220
  # are actually removed from the database, that depends precisely on
229
221
  # +delete_records+. They are in any case removed from the collection.
230
222
  def delete(*records)
231
- delete_or_destroy(records, options[:dependent])
223
+ dependent = options[:dependent]
224
+
225
+ if records.first == :all
226
+
227
+ if dependent && dependent == :destroy
228
+ message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
229
+ 'It means if the :dependent option is :destroy then the associated ' \
230
+ 'records would be deleted without loading and invoking callbacks.'
231
+
232
+ ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
233
+ end
234
+
235
+ if (loaded? || dependent == :destroy) && dependent != :delete_all
236
+ delete_or_destroy(load_target, dependent)
237
+ else
238
+ delete_records(:all, dependent)
239
+ end
240
+ else
241
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
242
+ delete_or_destroy(records, dependent)
243
+ end
232
244
  end
233
245
 
234
246
  # Destroy +records+ and remove them from this association calling
@@ -252,11 +264,15 @@ module ActiveRecord
252
264
  # This method is abstract in the sense that it relies on
253
265
  # +count_records+, which is a method descendants have to provide.
254
266
  def size
255
- if !find_target? || (loaded? && !options[:uniq])
256
- target.size
257
- elsif !loaded? && options[:group]
267
+ if !find_target? || loaded?
268
+ if association_scope.distinct_value
269
+ target.uniq.size
270
+ else
271
+ target.size
272
+ end
273
+ elsif !loaded? && !association_scope.group_values.empty?
258
274
  load_target.size
259
- elsif !loaded? && !options[:uniq] && target.is_a?(Array)
275
+ elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
260
276
  unsaved_records = target.select { |r| r.new_record? }
261
277
  unsaved_records.size + count_records
262
278
  else
@@ -273,13 +289,24 @@ module ActiveRecord
273
289
  load_target.size
274
290
  end
275
291
 
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>.
292
+ # Returns true if the collection is empty.
293
+ #
294
+ # If the collection has been loaded or the <tt>:counter_sql</tt> option
295
+ # is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the
296
+ # collection has not been loaded, it is equivalent to
297
+ # <tt>collection.exists?</tt>. If the collection has not already been
298
+ # loaded and you are going to fetch the records anyway it is better to
299
+ # check <tt>collection.length.zero?</tt>.
279
300
  def empty?
280
- size.zero?
301
+ if loaded? || options[:counter_sql]
302
+ size.zero?
303
+ else
304
+ @target.blank? && !scope.exists?
305
+ end
281
306
  end
282
307
 
308
+ # Returns true if the collections is not empty.
309
+ # Equivalent to +!collection.empty?+.
283
310
  def any?
284
311
  if block_given?
285
312
  load_target.any? { |*block_args| yield(*block_args) }
@@ -288,7 +315,8 @@ module ActiveRecord
288
315
  end
289
316
  end
290
317
 
291
- # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
318
+ # Returns true if the collection has more than 1 record.
319
+ # Equivalent to +collection.size > 1+.
292
320
  def many?
293
321
  if block_given?
294
322
  load_target.many? { |*block_args| yield(*block_args) }
@@ -297,17 +325,18 @@ module ActiveRecord
297
325
  end
298
326
  end
299
327
 
300
- def uniq(collection = load_target)
328
+ def distinct
301
329
  seen = {}
302
- collection.find_all do |record|
330
+ load_target.find_all do |record|
303
331
  seen[record.id] = true unless seen.key?(record.id)
304
332
  end
305
333
  end
334
+ alias uniq distinct
306
335
 
307
- # Replace this collection with +other_array+
308
- # This will perform a diff and delete/add only records that have changed.
336
+ # Replace this collection with +other_array+. This will perform a diff
337
+ # and delete/add only records that have changed.
309
338
  def replace(other_array)
310
- other_array.each { |val| raise_on_type_mismatch(val) }
339
+ other_array.each { |val| raise_on_type_mismatch!(val) }
311
340
  original_target = load_target.dup
312
341
 
313
342
  if owner.new_record?
@@ -323,7 +352,7 @@ module ActiveRecord
323
352
  include_in_memory?(record)
324
353
  else
325
354
  load_target if options[:finder_sql]
326
- loaded? ? target.include?(record) : scoped.exists?(record)
355
+ loaded? ? target.include?(record) : scope.exists?(record)
327
356
  end
328
357
  else
329
358
  false
@@ -343,7 +372,7 @@ module ActiveRecord
343
372
  callback(:before_add, record)
344
373
  yield(record) if block_given?
345
374
 
346
- if options[:uniq] && index = @target.index(record)
375
+ if association_scope.distinct_value && index = @target.index(record)
347
376
  @target[index] = record
348
377
  else
349
378
  @target << record
@@ -355,6 +384,16 @@ module ActiveRecord
355
384
  record
356
385
  end
357
386
 
387
+ def scope(opts = {})
388
+ scope = super()
389
+ scope.none! if opts.fetch(:nullify, true) && null_scope?
390
+ scope
391
+ end
392
+
393
+ def null_scope?
394
+ owner.new_record? && !foreign_key_present?
395
+ end
396
+
358
397
  private
359
398
 
360
399
  def custom_counter_sql
@@ -379,10 +418,9 @@ module ActiveRecord
379
418
  if options[:finder_sql]
380
419
  reflection.klass.find_by_sql(custom_finder_sql)
381
420
  else
382
- scoped.all
421
+ scope.to_a
383
422
  end
384
423
 
385
- records = options[:uniq] ? uniq(records) : records
386
424
  records.each { |record| set_inverse_instance(record) }
387
425
  records
388
426
  end
@@ -402,12 +440,7 @@ module ActiveRecord
402
440
  return memory if persisted.empty?
403
441
 
404
442
  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)
443
+ if mem_record = memory.delete(record)
411
444
 
412
445
  ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
413
446
  mem_record[name] = record[name]
@@ -422,16 +455,16 @@ module ActiveRecord
422
455
  persisted + memory
423
456
  end
424
457
 
425
- def create_record(attributes, options, raise = false, &block)
458
+ def _create_record(attributes, raise = false, &block)
426
459
  unless owner.persisted?
427
460
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
428
461
  end
429
462
 
430
463
  if attributes.is_a?(Array)
431
- attributes.collect { |attr| create_record(attr, options, raise, &block) }
464
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
432
465
  else
433
466
  transaction do
434
- add_to_target(build_record(attributes, options)) do |record|
467
+ add_to_target(build_record(attributes)) do |record|
435
468
  yield(record) if block_given?
436
469
  insert_record(record, true, raise)
437
470
  end
@@ -445,12 +478,12 @@ module ActiveRecord
445
478
  end
446
479
 
447
480
  def create_scope
448
- scoped.scope_for_create.stringify_keys
481
+ scope.scope_for_create.stringify_keys
449
482
  end
450
483
 
451
484
  def delete_or_destroy(records, method)
452
485
  records = records.flatten
453
- records.each { |record| raise_on_type_mismatch(record) }
486
+ records.each { |record| raise_on_type_mismatch!(record) }
454
487
  existing_records = records.reject { |r| r.new_record? }
455
488
 
456
489
  if existing_records.empty?
@@ -491,9 +524,9 @@ module ActiveRecord
491
524
  result = true
492
525
 
493
526
  records.flatten.each do |record|
494
- raise_on_type_mismatch(record)
495
- add_to_target(record) do |r|
496
- result &&= insert_record(record) unless owner.new_record?
527
+ raise_on_type_mismatch!(record)
528
+ add_to_target(record) do |rec|
529
+ result &&= insert_record(rec) unless owner.new_record?
497
530
  end
498
531
  end
499
532
 
@@ -552,17 +585,18 @@ module ActiveRecord
552
585
  end
553
586
  end
554
587
 
555
- # If using a custom finder_sql, #find scans the entire collection.
588
+ # If using a custom finder_sql or if the :inverse_of option has been
589
+ # specified, then #find scans the entire collection.
556
590
  def find_by_scan(*args)
557
591
  expects_array = args.first.kind_of?(Array)
558
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
592
+ ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
559
593
 
560
594
  if ids.size == 1
561
595
  id = ids.first
562
- record = load_target.detect { |r| id == r.id }
596
+ record = load_target.detect { |r| id == r.id.to_s }
563
597
  expects_array ? [ record ] : record
564
598
  else
565
- load_target.select { |r| ids.include?(r.id) }
599
+ load_target.select { |r| ids.include?(r.id.to_s) }
566
600
  end
567
601
  end
568
602
 
@@ -570,7 +604,7 @@ module ActiveRecord
570
604
  def first_or_last(type, *args)
571
605
  args.shift if args.first.is_a?(Hash) && args.first.empty?
572
606
 
573
- collection = fetch_first_or_last_using_find?(args) ? scoped : load_target
607
+ collection = fetch_first_or_last_using_find?(args) ? scope : load_target
574
608
  collection.send(type, *args).tap do |record|
575
609
  set_inverse_instance record if record.is_a? ActiveRecord::Base
576
610
  end