activerecord 4.1.0 → 4.2.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -33,7 +33,13 @@ module ActiveRecord
33
33
  reload
34
34
  end
35
35
 
36
- @proxy ||= CollectionProxy.create(klass, self)
36
+ if owner.new_record?
37
+ # Cache the proxy separately before the owner has an id
38
+ # or else a post-save proxy will still lack the id
39
+ @new_record_proxy ||= CollectionProxy.create(klass, self)
40
+ else
41
+ @proxy ||= CollectionProxy.create(klass, self)
42
+ end
37
43
  end
38
44
 
39
45
  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -55,9 +61,9 @@ module ActiveRecord
55
61
 
56
62
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
57
63
  def ids_writer(ids)
58
- pk_column = reflection.primary_key_column
64
+ pk_type = reflection.primary_key_type
59
65
  ids = Array(ids).reject { |id| id.blank? }
60
- ids.map! { |i| pk_column.type_cast(i) }
66
+ ids.map! { |i| pk_type.type_cast_from_user(i) }
61
67
  replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
62
68
  end
63
69
 
@@ -134,20 +140,19 @@ module ActiveRecord
134
140
  end
135
141
 
136
142
  def create(attributes = {}, &block)
137
- create_record(attributes, &block)
143
+ _create_record(attributes, &block)
138
144
  end
139
145
 
140
146
  def create!(attributes = {}, &block)
141
- create_record(attributes, true, &block)
147
+ _create_record(attributes, true, &block)
142
148
  end
143
149
 
144
150
  # Add +records+ to this association. Returns +self+ so method calls may
145
151
  # be chained. Since << flattens its argument list and inserts each record,
146
152
  # +push+ and +concat+ behave identically.
147
153
  def concat(*records)
148
- load_target if owner.new_record?
149
-
150
154
  if owner.new_record?
155
+ load_target
151
156
  concat_records(records)
152
157
  else
153
158
  transaction { concat_records(records) }
@@ -170,8 +175,8 @@ module ActiveRecord
170
175
  end
171
176
 
172
177
  # Removes all records from the association without calling callbacks
173
- # on the associated records. It honors the `:dependent` option. However
174
- # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
178
+ # on the associated records. It honors the +:dependent+ option. However
179
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
175
180
  # deletion strategy for the association is applied.
176
181
  #
177
182
  # You can force a particular deletion strategy by passing a parameter.
@@ -183,11 +188,11 @@ module ActiveRecord
183
188
  #
184
189
  # See delete for more info.
185
190
  def delete_all(dependent = nil)
186
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
191
+ if dependent && ![:nullify, :delete_all].include?(dependent)
187
192
  raise ArgumentError, "Valid values are :nullify or :delete_all"
188
193
  end
189
194
 
190
- dependent = if dependent.present?
195
+ dependent = if dependent
191
196
  dependent
192
197
  elsif options[:dependent] == :destroy
193
198
  :delete_all
@@ -195,7 +200,7 @@ module ActiveRecord
195
200
  options[:dependent]
196
201
  end
197
202
 
198
- delete(:all, dependent: dependent).tap do
203
+ delete_or_nullify_all_records(dependent).tap do
199
204
  reset
200
205
  loaded!
201
206
  end
@@ -245,19 +250,12 @@ module ActiveRecord
245
250
  # are actually removed from the database, that depends precisely on
246
251
  # +delete_records+. They are in any case removed from the collection.
247
252
  def delete(*records)
253
+ return if records.empty?
248
254
  _options = records.extract_options!
249
255
  dependent = _options[:dependent] || options[:dependent]
250
256
 
251
- if records.first == :all
252
- if loaded? || dependent == :destroy
253
- delete_or_destroy(load_target, dependent)
254
- else
255
- delete_records(:all, dependent)
256
- end
257
- else
258
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
259
- delete_or_destroy(records, dependent)
260
- end
257
+ records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
258
+ delete_or_destroy(records, dependent)
261
259
  end
262
260
 
263
261
  # Deletes the +records+ and removes them from this association calling
@@ -266,6 +264,7 @@ module ActiveRecord
266
264
  # Note that this method removes records from the database ignoring the
267
265
  # +:dependent+ option.
268
266
  def destroy(*records)
267
+ return if records.empty?
269
268
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
270
269
  delete_or_destroy(records, :destroy)
271
270
  end
@@ -359,7 +358,10 @@ module ActiveRecord
359
358
  if owner.new_record?
360
359
  replace_records(other_array, original_target)
361
360
  else
362
- transaction { replace_records(other_array, original_target) }
361
+ replace_common_records_in_memory(other_array, original_target)
362
+ if other_array != original_target
363
+ transaction { replace_records(other_array, original_target) }
364
+ end
363
365
  end
364
366
  end
365
367
 
@@ -368,7 +370,7 @@ module ActiveRecord
368
370
  if record.new_record?
369
371
  include_in_memory?(record)
370
372
  else
371
- loaded? ? target.include?(record) : scope.exists?(record)
373
+ loaded? ? target.include?(record) : scope.exists?(record.id)
372
374
  end
373
375
  else
374
376
  false
@@ -384,11 +386,18 @@ module ActiveRecord
384
386
  target
385
387
  end
386
388
 
387
- def add_to_target(record, skip_callbacks = false)
389
+ def add_to_target(record, skip_callbacks = false, &block)
390
+ if association_scope.distinct_value
391
+ index = @target.index(record)
392
+ end
393
+ replace_on_target(record, index, skip_callbacks, &block)
394
+ end
395
+
396
+ def replace_on_target(record, index, skip_callbacks)
388
397
  callback(:before_add, record) unless skip_callbacks
389
398
  yield(record) if block_given?
390
399
 
391
- if association_scope.distinct_value && index = @target.index(record)
400
+ if index
392
401
  @target[index] = record
393
402
  else
394
403
  @target << record
@@ -411,9 +420,29 @@ module ActiveRecord
411
420
  end
412
421
 
413
422
  private
423
+ def get_records
424
+ if reflection.scope_chain.any?(&:any?) ||
425
+ scope.eager_loading? ||
426
+ klass.current_scope ||
427
+ klass.default_scopes.any?
428
+
429
+ return scope.to_a
430
+ end
431
+
432
+ conn = klass.connection
433
+ sc = reflection.association_scope_cache(conn, owner) do
434
+ StatementCache.create(conn) { |params|
435
+ as = AssociationScope.create { params.bind }
436
+ target_scope.merge as.scope(self, conn)
437
+ }
438
+ end
439
+
440
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
441
+ sc.execute binds, klass, klass.connection
442
+ end
414
443
 
415
444
  def find_target
416
- records = scope.to_a
445
+ records = get_records
417
446
  records.each { |record| set_inverse_instance(record) }
418
447
  records
419
448
  end
@@ -448,13 +477,13 @@ module ActiveRecord
448
477
  persisted + memory
449
478
  end
450
479
 
451
- def create_record(attributes, raise = false, &block)
480
+ def _create_record(attributes, raise = false, &block)
452
481
  unless owner.persisted?
453
482
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
454
483
  end
455
484
 
456
485
  if attributes.is_a?(Array)
457
- attributes.collect { |attr| create_record(attr, raise, &block) }
486
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
458
487
  else
459
488
  transaction do
460
489
  add_to_target(build_record(attributes)) do |record|
@@ -513,6 +542,14 @@ module ActiveRecord
513
542
  target
514
543
  end
515
544
 
545
+ def replace_common_records_in_memory(new_target, original_target)
546
+ common_records = new_target & original_target
547
+ common_records.each do |record|
548
+ skip_callbacks = true
549
+ replace_on_target(record, @target.index(record), skip_callbacks)
550
+ end
551
+ end
552
+
516
553
  def concat_records(records, should_raise = false)
517
554
  result = true
518
555
 
@@ -355,14 +355,15 @@ module ActiveRecord
355
355
  @association.replace(other_array)
356
356
  end
357
357
 
358
- # Deletes all the records from the collection. For +has_many+ associations,
359
- # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
360
- # option. Returns an array with the deleted records.
358
+ # Deletes all the records from the collection according to the strategy
359
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
360
+ # then it will follow the default strategy.
361
361
  #
362
- # If no <tt>:dependent</tt> option is given, then it will follow the
363
- # default strategy. The default strategy is <tt>:nullify</tt>. This
364
- # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
365
- # the default strategy is +delete_all+.
362
+ # For +has_many :through+ associations, the default deletion strategy is
363
+ # +:delete_all+.
364
+ #
365
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
366
+ # This sets the foreign keys to +NULL+.
366
367
  #
367
368
  # class Person < ActiveRecord::Base
368
369
  # has_many :pets # dependent: :nullify option by default
@@ -393,9 +394,9 @@ module ActiveRecord
393
394
  # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
394
395
  # # ]
395
396
  #
396
- # If it is set to <tt>:destroy</tt> all the objects from the collection
397
- # are removed by calling their +destroy+ method. See +destroy+ for more
398
- # information.
397
+ # Both +has_many+ and +has_many :through+ dependencies default to the
398
+ # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
399
+ # Records are not instantiated and callbacks will not be fired.
399
400
  #
400
401
  # class Person < ActiveRecord::Base
401
402
  # has_many :pets, dependent: :destroy
@@ -410,11 +411,6 @@ module ActiveRecord
410
411
  # # ]
411
412
  #
412
413
  # 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
414
  #
419
415
  # Pet.find(1, 2, 3)
420
416
  # # => ActiveRecord::RecordNotFound
@@ -435,11 +431,6 @@ module ActiveRecord
435
431
  # # ]
436
432
  #
437
433
  # person.pets.delete_all
438
- # # => [
439
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
440
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
441
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
442
- # # ]
443
434
  #
444
435
  # Pet.find(1, 2, 3)
445
436
  # # => ActiveRecord::RecordNotFound
@@ -448,8 +439,9 @@ module ActiveRecord
448
439
  end
449
440
 
450
441
  # Deletes the records of the collection directly from the database
451
- # ignoring the +:dependent+ option. It invokes +before_remove+,
452
- # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
442
+ # ignoring the +:dependent+ option. Records are instantiated and it
443
+ # invokes +before_remove+, +after_remove+ , +before_destroy+ and
444
+ # +after_destroy+ callbacks.
453
445
  #
454
446
  # class Person < ActiveRecord::Base
455
447
  # has_many :pets
@@ -788,7 +780,7 @@ module ActiveRecord
788
780
  # person.pets.count # => 0
789
781
  # person.pets.any? # => true
790
782
  #
791
- # You can also pass a block to define criteria. The behavior
783
+ # You can also pass a +block+ to define criteria. The behavior
792
784
  # is the same, it returns true if the collection based on the
793
785
  # criteria is not empty.
794
786
  #
@@ -822,7 +814,7 @@ module ActiveRecord
822
814
  # person.pets.count # => 2
823
815
  # person.pets.many? # => true
824
816
  #
825
- # You can also pass a block to define criteria. The
817
+ # You can also pass a +block+ to define criteria. The
826
818
  # behavior is the same, it returns true if the collection
827
819
  # based on the criteria has more than one record.
828
820
  #
@@ -846,7 +838,7 @@ module ActiveRecord
846
838
  @association.many?(&block)
847
839
  end
848
840
 
849
- # Returns +true+ if the given object is present in the collection.
841
+ # Returns +true+ if the given +record+ is present in the collection.
850
842
  #
851
843
  # class Person < ActiveRecord::Base
852
844
  # has_many :pets
@@ -860,6 +852,10 @@ module ActiveRecord
860
852
  !!@association.include?(record)
861
853
  end
862
854
 
855
+ def arel
856
+ scope.arel
857
+ end
858
+
863
859
  def proxy_association
864
860
  @association
865
861
  end
@@ -880,7 +876,7 @@ module ActiveRecord
880
876
 
881
877
  # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
882
878
  # contain the same number of elements and if each element is equal
883
- # to the corresponding element in the other array, otherwise returns
879
+ # to the corresponding element in the +other+ array, otherwise returns
884
880
  # +false+.
885
881
  #
886
882
  # class Person < ActiveRecord::Base
@@ -41,6 +41,14 @@ module ActiveRecord
41
41
  end
42
42
  end
43
43
 
44
+ def empty?
45
+ if has_cached_counter?
46
+ size.zero?
47
+ else
48
+ super
49
+ end
50
+ end
51
+
44
52
  private
45
53
 
46
54
  # Returns the number of records in this collection.
@@ -58,7 +66,7 @@ module ActiveRecord
58
66
  # the loaded flag is set to true as well.
59
67
  def count_records
60
68
  count = if has_cached_counter?
61
- owner.send(:read_attribute, cached_counter_attribute_name)
69
+ owner._read_attribute cached_counter_attribute_name
62
70
  else
63
71
  scope.count
64
72
  end
@@ -71,20 +79,31 @@ module ActiveRecord
71
79
  [association_scope.limit_value, count].compact.min
72
80
  end
73
81
 
74
- def has_cached_counter?(reflection = reflection)
82
+ def has_cached_counter?(reflection = reflection())
75
83
  owner.attribute_present?(cached_counter_attribute_name(reflection))
76
84
  end
77
85
 
78
- def cached_counter_attribute_name(reflection = reflection)
86
+ def cached_counter_attribute_name(reflection = reflection())
79
87
  options[:counter_cache] || "#{reflection.name}_count"
80
88
  end
81
89
 
82
- def update_counter(difference, reflection = reflection)
90
+ def update_counter(difference, reflection = reflection())
91
+ update_counter_in_database(difference, reflection)
92
+ update_counter_in_memory(difference, reflection)
93
+ end
94
+
95
+ def update_counter_in_database(difference, reflection = reflection())
83
96
  if has_cached_counter?(reflection)
84
97
  counter = cached_counter_attribute_name(reflection)
85
98
  owner.class.update_counters(owner.id, counter => difference)
99
+ end
100
+ end
101
+
102
+ def update_counter_in_memory(difference, reflection = reflection())
103
+ if has_cached_counter?(reflection)
104
+ counter = cached_counter_attribute_name(reflection)
86
105
  owner[counter] += difference
87
- owner.changed_attributes.delete(counter) # eww
106
+ owner.send(:clear_attribute_changes, counter) # eww
88
107
  end
89
108
  end
90
109
 
@@ -98,30 +117,39 @@ module ActiveRecord
98
117
  # it will be decremented twice.
99
118
  #
100
119
  # Hence this method.
101
- def inverse_updates_counter_cache?(reflection = reflection)
120
+ def inverse_updates_counter_cache?(reflection = reflection())
102
121
  counter_name = cached_counter_attribute_name(reflection)
103
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
122
+ inverse_updates_counter_named?(counter_name, reflection)
123
+ end
124
+
125
+ def inverse_updates_counter_named?(counter_name, reflection = reflection())
126
+ reflection.klass._reflections.values.any? { |inverse_reflection|
127
+ inverse_reflection.belongs_to? &&
104
128
  inverse_reflection.counter_cache_column == counter_name
105
129
  }
106
130
  end
107
131
 
132
+ def delete_count(method, scope)
133
+ if method == :delete_all
134
+ scope.delete_all
135
+ else
136
+ scope.update_all(reflection.foreign_key => nil)
137
+ end
138
+ end
139
+
140
+ def delete_or_nullify_all_records(method)
141
+ count = delete_count(method, self.scope)
142
+ update_counter(-count)
143
+ end
144
+
108
145
  # Deletes the records according to the <tt>:dependent</tt> option.
109
146
  def delete_records(records, method)
110
147
  if method == :destroy
111
148
  records.each(&:destroy!)
112
149
  update_counter(-records.length) unless inverse_updates_counter_cache?
113
150
  else
114
- if records == :all || !reflection.klass.primary_key
115
- scope = self.scope
116
- else
117
- scope = self.scope.where(reflection.klass.primary_key => records)
118
- end
119
-
120
- if method == :delete_all
121
- update_counter(-scope.delete_all)
122
- else
123
- update_counter(-scope.update_all(reflection.foreign_key => nil))
124
- end
151
+ scope = self.scope.where(reflection.klass.primary_key => records)
152
+ update_counter(-delete_count(method, scope))
125
153
  end
126
154
  end
127
155
 
@@ -132,6 +160,25 @@ module ActiveRecord
132
160
  false
133
161
  end
134
162
  end
163
+
164
+ def concat_records(records, *)
165
+ update_counter_if_success(super, records.length)
166
+ end
167
+
168
+ def _create_record(attributes, *)
169
+ if attributes.is_a?(Array)
170
+ super
171
+ else
172
+ update_counter_if_success(super, 1)
173
+ end
174
+ end
175
+
176
+ def update_counter_if_success(saved_successfully, difference)
177
+ if saved_successfully
178
+ update_counter_in_memory(difference)
179
+ end
180
+ saved_successfully
181
+ end
135
182
  end
136
183
  end
137
184
  end
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/string/filters'
1
2
 
2
3
  module ActiveRecord
3
4
  # = Active Record Has Many Through Association
@@ -12,17 +13,18 @@ module ActiveRecord
12
13
  @through_association = nil
13
14
  end
14
15
 
15
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
16
- # loaded and calling collection.size if it has. If it's more likely than not that the collection does
17
- # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
18
- # SELECT query if you use #length.
16
+ # Returns the size of the collection by executing a SELECT COUNT(*) query
17
+ # if the collection hasn't been loaded, and by calling collection.size if
18
+ # it has. If the collection will likely have a size greater than zero,
19
+ # and if fetching the collection will be needed afterwards, one less
20
+ # SELECT query will be generated by using #length instead.
19
21
  def size
20
22
  if has_cached_counter?
21
- owner.send(:read_attribute, cached_counter_attribute_name)
23
+ owner._read_attribute cached_counter_attribute_name(reflection)
22
24
  elsif loaded?
23
25
  target.size
24
26
  else
25
- count
27
+ super
26
28
  end
27
29
  end
28
30
 
@@ -62,7 +64,16 @@ module ActiveRecord
62
64
  end
63
65
 
64
66
  save_through_record(record)
65
- update_counter(1)
67
+ if has_cached_counter? && !through_reflection_updates_counter_cache?
68
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
69
+ Automatic updating of counter caches on through associations has been
70
+ deprecated, and will be removed in Rails 5. Instead, please set the
71
+ appropriate `counter_cache` options on the `has_many` and `belongs_to`
72
+ for your associations to #{through_reflection.name}.
73
+ MSG
74
+
75
+ update_counter_in_database(1)
76
+ end
66
77
  record
67
78
  end
68
79
 
@@ -72,23 +83,31 @@ module ActiveRecord
72
83
  @through_association ||= owner.association(through_reflection.name)
73
84
  end
74
85
 
75
- # We temporarily cache through record that has been build, because if we build a
76
- # through record in build_record and then subsequently call insert_record, then we
77
- # want to use the exact same object.
86
+ # The through record (built with build_record) is temporarily cached
87
+ # so that it may be reused if insert_record is subsequently called.
78
88
  #
79
- # However, after insert_record has been called, we clear the cache entry because
80
- # we want it to be possible to have multiple instances of the same record in an
81
- # association
89
+ # However, after insert_record has been called, the cache is cleared in
90
+ # order to allow multiple instances of the same record in an association.
82
91
  def build_through_record(record)
83
92
  @through_records[record.object_id] ||= begin
84
93
  ensure_mutable
85
94
 
86
- through_record = through_association.build
95
+ through_record = through_association.build(*options_for_through_record)
87
96
  through_record.send("#{source_reflection.name}=", record)
88
97
  through_record
89
98
  end
90
99
  end
91
100
 
101
+ def options_for_through_record
102
+ [through_scope_attributes]
103
+ end
104
+
105
+ def through_scope_attributes
106
+ scope.where_values_hash(through_association.reflection.name.to_s).
107
+ except!(through_association.reflection.foreign_key,
108
+ through_association.reflection.klass.inheritance_column)
109
+ end
110
+
92
111
  def save_through_record(record)
93
112
  build_through_record(record).save!
94
113
  ensure
@@ -102,9 +121,9 @@ module ActiveRecord
102
121
 
103
122
  inverse = source_reflection.inverse_of
104
123
  if inverse
105
- if inverse.macro == :has_many
124
+ if inverse.collection?
106
125
  record.send(inverse.name) << build_through_record(record)
107
- elsif inverse.macro == :has_one
126
+ elsif inverse.has_one?
108
127
  record.send("#{inverse.name}=", build_through_record(record))
109
128
  end
110
129
  end
@@ -113,7 +132,7 @@ module ActiveRecord
113
132
  end
114
133
 
115
134
  def target_reflection_has_associated_record?
116
- !(through_reflection.macro == :belongs_to && owner[through_reflection.foreign_key].blank?)
135
+ !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?)
117
136
  end
118
137
 
119
138
  def update_through_counter?(method)
@@ -127,13 +146,13 @@ module ActiveRecord
127
146
  end
128
147
  end
129
148
 
149
+ def delete_or_nullify_all_records(method)
150
+ delete_records(load_target, method)
151
+ end
152
+
130
153
  def delete_records(records, method)
131
154
  ensure_not_nested
132
155
 
133
- # This is unoptimised; it will load all the target records
134
- # even when we just want to delete everything.
135
- records = load_target if records == :all
136
-
137
156
  scope = through_association.scope
138
157
  scope.where! construct_join_attributes(*records)
139
158
 
@@ -142,8 +161,8 @@ module ActiveRecord
142
161
  if scope.klass.primary_key
143
162
  count = scope.destroy_all.length
144
163
  else
145
- scope.to_a.each do |record|
146
- record.run_callbacks :destroy
164
+ scope.each do |record|
165
+ record._run_destroy_callbacks
147
166
  end
148
167
 
149
168
  arel = scope.arel
@@ -167,7 +186,7 @@ module ActiveRecord
167
186
  klass.decrement_counter counter, records.map(&:id)
168
187
  end
169
188
 
170
- if through_reflection.macro == :has_many && update_through_counter?(method)
189
+ if through_reflection.collection? && update_through_counter?(method)
171
190
  update_counter(-count, through_reflection)
172
191
  end
173
192
 
@@ -177,14 +196,18 @@ module ActiveRecord
177
196
  def through_records_for(record)
178
197
  attributes = construct_join_attributes(record)
179
198
  candidates = Array.wrap(through_association.target)
180
- candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
199
+ candidates.find_all do |c|
200
+ attributes.all? do |key, value|
201
+ c.public_send(key) == value
202
+ end
203
+ end
181
204
  end
182
205
 
183
206
  def delete_through_records(records)
184
207
  records.each do |record|
185
208
  through_records = through_records_for(record)
186
209
 
187
- if through_reflection.macro == :has_many
210
+ if through_reflection.collection?
188
211
  through_records.each { |r| through_association.target.delete(r) }
189
212
  else
190
213
  if through_records.include?(through_association.target)
@@ -198,13 +221,18 @@ module ActiveRecord
198
221
 
199
222
  def find_target
200
223
  return [] unless target_reflection_has_associated_record?
201
- scope.to_a
224
+ get_records
202
225
  end
203
226
 
204
227
  # NOTE - not sure that we can actually cope with inverses here
205
228
  def invertible_for?(record)
206
229
  false
207
230
  end
231
+
232
+ def through_reflection_updates_counter_cache?
233
+ counter_name = cached_counter_attribute_name
234
+ inverse_updates_counter_named?(counter_name, through_reflection)
235
+ end
208
236
  end
209
237
  end
210
238
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Belongs To Has One Association
4
3
  module Associations