activerecord 4.1.16 → 4.2.11.3

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 +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  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/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- valid = super + [:order, :as]
8
+ valid = super + [:as, :foreign_type]
9
9
  valid += [:through, :source, :source_type] if options[:through]
10
10
  valid
11
11
  end
@@ -16,7 +16,7 @@ module ActiveRecord::Associations::Builder
16
16
 
17
17
  private
18
18
 
19
- def self.add_before_destroy_callbacks(model, reflection)
19
+ def self.add_destroy_callbacks(model, reflection)
20
20
  super unless reflection.options[:through]
21
21
  end
22
22
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord::Associations::Builder
4
4
  class SingularAssociation < Association #:nodoc:
5
5
  def valid_options
6
- super + [:remote, :dependent, :primary_key, :inverse_of]
6
+ super + [:dependent, :primary_key, :inverse_of, :required]
7
7
  end
8
8
 
9
9
  def self.define_accessors(model, reflection)
@@ -27,5 +27,12 @@ module ActiveRecord::Associations::Builder
27
27
  end
28
28
  CODE
29
29
  end
30
+
31
+ def self.define_validations(model, reflection)
32
+ super
33
+ if reflection.options[:required]
34
+ model.validates_presence_of reflection.name
35
+ end
36
+ end
30
37
  end
31
38
  end
@@ -61,10 +61,21 @@ module ActiveRecord
61
61
 
62
62
  # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
63
63
  def ids_writer(ids)
64
- pk_column = reflection.primary_key_column
65
- ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_column.type_cast(i) }
67
- replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
64
+ pk_column = reflection.association_primary_key
65
+ pk_type = klass.type_for_attribute(pk_column)
66
+ ids = Array(ids).reject(&:blank?).map do |i|
67
+ pk_type.type_cast_from_user(i)
68
+ end
69
+
70
+ objs = klass.where(pk_column => ids).index_by do |r|
71
+ r.send(pk_column)
72
+ end.values_at(*ids).compact
73
+
74
+ if objs.size == ids.size
75
+ replace(objs.index_by { |r| r.send(pk_column) }.values_at(*ids))
76
+ else
77
+ klass.all.raise_record_not_found_exception!(ids, objs.size, ids.size)
78
+ end
68
79
  end
69
80
 
70
81
  def reset
@@ -161,9 +172,8 @@ module ActiveRecord
161
172
  # be chained. Since << flattens its argument list and inserts each record,
162
173
  # +push+ and +concat+ behave identically.
163
174
  def concat(*records)
164
- load_target if owner.new_record?
165
-
166
175
  if owner.new_record?
176
+ load_target
167
177
  concat_records(records)
168
178
  else
169
179
  transaction { concat_records(records) }
@@ -186,8 +196,8 @@ module ActiveRecord
186
196
  end
187
197
 
188
198
  # Removes all records from the association without calling callbacks
189
- # on the associated records. It honors the `:dependent` option. However
190
- # if the `:dependent` value is `:destroy` then in that case the `:delete_all`
199
+ # on the associated records. It honors the +:dependent+ option. However
200
+ # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
191
201
  # deletion strategy for the association is applied.
192
202
  #
193
203
  # You can force a particular deletion strategy by passing a parameter.
@@ -199,11 +209,11 @@ module ActiveRecord
199
209
  #
200
210
  # See delete for more info.
201
211
  def delete_all(dependent = nil)
202
- if dependent.present? && ![:nullify, :delete_all].include?(dependent)
212
+ if dependent && ![:nullify, :delete_all].include?(dependent)
203
213
  raise ArgumentError, "Valid values are :nullify or :delete_all"
204
214
  end
205
215
 
206
- dependent = if dependent.present?
216
+ dependent = if dependent
207
217
  dependent
208
218
  elsif options[:dependent] == :destroy
209
219
  :delete_all
@@ -211,7 +221,7 @@ module ActiveRecord
211
221
  options[:dependent]
212
222
  end
213
223
 
214
- delete(:all, dependent: dependent).tap do
224
+ delete_or_nullify_all_records(dependent).tap do
215
225
  reset
216
226
  loaded!
217
227
  end
@@ -261,19 +271,12 @@ module ActiveRecord
261
271
  # are actually removed from the database, that depends precisely on
262
272
  # +delete_records+. They are in any case removed from the collection.
263
273
  def delete(*records)
274
+ return if records.empty?
264
275
  _options = records.extract_options!
265
276
  dependent = _options[:dependent] || options[:dependent]
266
277
 
267
- if records.first == :all
268
- if (loaded? || dependent == :destroy) && dependent != :delete_all
269
- delete_or_destroy(load_target, dependent)
270
- else
271
- delete_records(:all, dependent)
272
- end
273
- else
274
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
275
- delete_or_destroy(records, dependent)
276
- end
278
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
279
+ delete_or_destroy(records, dependent)
277
280
  end
278
281
 
279
282
  # Deletes the +records+ and removes them from this association calling
@@ -282,7 +285,8 @@ module ActiveRecord
282
285
  # Note that this method removes records from the database ignoring the
283
286
  # +:dependent+ option.
284
287
  def destroy(*records)
285
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
288
+ return if records.empty?
289
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
286
290
  delete_or_destroy(records, :destroy)
287
291
  end
288
292
 
@@ -375,7 +379,10 @@ module ActiveRecord
375
379
  if owner.new_record?
376
380
  replace_records(other_array, original_target)
377
381
  else
378
- transaction { replace_records(other_array, original_target) }
382
+ replace_common_records_in_memory(other_array, original_target)
383
+ if other_array != original_target
384
+ transaction { replace_records(other_array, original_target) }
385
+ end
379
386
  end
380
387
  end
381
388
 
@@ -384,7 +391,7 @@ module ActiveRecord
384
391
  if record.new_record?
385
392
  include_in_memory?(record)
386
393
  else
387
- loaded? ? target.include?(record) : scope.exists?(record)
394
+ loaded? ? target.include?(record) : scope.exists?(record.id)
388
395
  end
389
396
  else
390
397
  false
@@ -400,11 +407,18 @@ module ActiveRecord
400
407
  target
401
408
  end
402
409
 
403
- def add_to_target(record, skip_callbacks = false)
410
+ def add_to_target(record, skip_callbacks = false, &block)
411
+ if association_scope.distinct_value
412
+ index = @target.index(record)
413
+ end
414
+ replace_on_target(record, index, skip_callbacks, &block)
415
+ end
416
+
417
+ def replace_on_target(record, index, skip_callbacks)
404
418
  callback(:before_add, record) unless skip_callbacks
405
419
  yield(record) if block_given?
406
420
 
407
- if association_scope.distinct_value && index = @target.index(record)
421
+ if index
408
422
  @target[index] = record
409
423
  else
410
424
  @target << record
@@ -427,9 +441,23 @@ module ActiveRecord
427
441
  end
428
442
 
429
443
  private
444
+ def get_records
445
+ return scope.to_a if skip_statement_cache?
446
+
447
+ conn = klass.connection
448
+ sc = reflection.association_scope_cache(conn, owner) do
449
+ StatementCache.create(conn) { |params|
450
+ as = AssociationScope.create { params.bind }
451
+ target_scope.merge as.scope(self, conn)
452
+ }
453
+ end
454
+
455
+ binds = AssociationScope.get_bind_values(owner, reflection.chain)
456
+ sc.execute binds, klass, klass.connection
457
+ end
430
458
 
431
459
  def find_target
432
- records = scope.to_a
460
+ records = get_records
433
461
  records.each { |record| set_inverse_instance(record) }
434
462
  records
435
463
  end
@@ -529,6 +557,14 @@ module ActiveRecord
529
557
  target
530
558
  end
531
559
 
560
+ def replace_common_records_in_memory(new_target, original_target)
561
+ common_records = new_target & original_target
562
+ common_records.each do |record|
563
+ skip_callbacks = true
564
+ replace_on_target(record, @target.index(record), skip_callbacks)
565
+ end
566
+ end
567
+
532
568
  def concat_records(records, should_raise = false)
533
569
  result = true
534
570
 
@@ -29,6 +29,7 @@ module ActiveRecord
29
29
  # instantiation of the actual post records.
30
30
  class CollectionProxy < Relation
31
31
  delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
32
+ delegate :find_nth, to: :scope
32
33
 
33
34
  def initialize(klass, association) #:nodoc:
34
35
  @association = association
@@ -359,14 +360,15 @@ module ActiveRecord
359
360
  @association.replace(other_array)
360
361
  end
361
362
 
362
- # Deletes all the records from the collection. For +has_many+ associations,
363
- # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
364
- # option. Returns an array with the deleted records.
363
+ # Deletes all the records from the collection according to the strategy
364
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
365
+ # then it will follow the default strategy.
365
366
  #
366
- # If no <tt>:dependent</tt> option is given, then it will follow the
367
- # default strategy. The default strategy is <tt>:nullify</tt>. This
368
- # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
369
- # the default strategy is +delete_all+.
367
+ # For +has_many :through+ associations, the default deletion strategy is
368
+ # +:delete_all+.
369
+ #
370
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
371
+ # This sets the foreign keys to +NULL+.
370
372
  #
371
373
  # class Person < ActiveRecord::Base
372
374
  # has_many :pets # dependent: :nullify option by default
@@ -397,9 +399,9 @@ module ActiveRecord
397
399
  # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
398
400
  # # ]
399
401
  #
400
- # If it is set to <tt>:destroy</tt> all the objects from the collection
401
- # are removed by calling their +destroy+ method. See +destroy+ for more
402
- # information.
402
+ # Both +has_many+ and +has_many :through+ dependencies default to the
403
+ # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
404
+ # Records are not instantiated and callbacks will not be fired.
403
405
  #
404
406
  # class Person < ActiveRecord::Base
405
407
  # has_many :pets, dependent: :destroy
@@ -414,11 +416,6 @@ module ActiveRecord
414
416
  # # ]
415
417
  #
416
418
  # person.pets.delete_all
417
- # # => [
418
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
419
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
420
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
421
- # # ]
422
419
  #
423
420
  # Pet.find(1, 2, 3)
424
421
  # # => ActiveRecord::RecordNotFound
@@ -439,11 +436,6 @@ module ActiveRecord
439
436
  # # ]
440
437
  #
441
438
  # person.pets.delete_all
442
- # # => [
443
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
444
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
445
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
446
- # # ]
447
439
  #
448
440
  # Pet.find(1, 2, 3)
449
441
  # # => ActiveRecord::RecordNotFound
@@ -452,8 +444,9 @@ module ActiveRecord
452
444
  end
453
445
 
454
446
  # Deletes the records of the collection directly from the database
455
- # ignoring the +:dependent+ option. It invokes +before_remove+,
456
- # +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
447
+ # ignoring the +:dependent+ option. Records are instantiated and it
448
+ # invokes +before_remove+, +after_remove+ , +before_destroy+ and
449
+ # +after_destroy+ callbacks.
457
450
  #
458
451
  # class Person < ActiveRecord::Base
459
452
  # has_many :pets
@@ -477,15 +470,16 @@ module ActiveRecord
477
470
  @association.destroy_all
478
471
  end
479
472
 
480
- # Deletes the +records+ supplied and removes them from the collection. For
481
- # +has_many+ associations, the deletion is done according to the strategy
482
- # specified by the <tt>:dependent</tt> option. Returns an array with the
473
+ # Deletes the +records+ supplied from the collection according to the strategy
474
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
475
+ # then it will follow the default strategy. Returns an array with the
483
476
  # deleted records.
484
477
  #
485
- # If no <tt>:dependent</tt> option is given, then it will follow the default
486
- # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
487
- # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
488
- # strategy is +delete_all+.
478
+ # For +has_many :through+ associations, the default deletion strategy is
479
+ # +:delete_all+.
480
+ #
481
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
482
+ # This sets the foreign keys to +NULL+.
489
483
  #
490
484
  # class Person < ActiveRecord::Base
491
485
  # has_many :pets # dependent: :nullify option by default
@@ -568,7 +562,7 @@ module ActiveRecord
568
562
  # Pet.find(1)
569
563
  # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
570
564
  #
571
- # You can pass +Fixnum+ or +String+ values, it finds the records
565
+ # You can pass +Integer+ or +String+ values, it finds the records
572
566
  # responding to the +id+ and executes delete on them.
573
567
  #
574
568
  # class Person < ActiveRecord::Base
@@ -632,7 +626,7 @@ module ActiveRecord
632
626
  #
633
627
  # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
634
628
  #
635
- # You can pass +Fixnum+ or +String+ values, it finds the records
629
+ # You can pass +Integer+ or +String+ values, it finds the records
636
630
  # responding to the +id+ and then deletes them from the database.
637
631
  #
638
632
  # person.pets.size # => 3
@@ -792,7 +786,7 @@ module ActiveRecord
792
786
  # person.pets.count # => 0
793
787
  # person.pets.any? # => true
794
788
  #
795
- # You can also pass a block to define criteria. The behavior
789
+ # You can also pass a +block+ to define criteria. The behavior
796
790
  # is the same, it returns true if the collection based on the
797
791
  # criteria is not empty.
798
792
  #
@@ -826,7 +820,7 @@ module ActiveRecord
826
820
  # person.pets.count # => 2
827
821
  # person.pets.many? # => true
828
822
  #
829
- # You can also pass a block to define criteria. The
823
+ # You can also pass a +block+ to define criteria. The
830
824
  # behavior is the same, it returns true if the collection
831
825
  # based on the criteria has more than one record.
832
826
  #
@@ -850,7 +844,7 @@ module ActiveRecord
850
844
  @association.many?(&block)
851
845
  end
852
846
 
853
- # Returns +true+ if the given object is present in the collection.
847
+ # Returns +true+ if the given +record+ is present in the collection.
854
848
  #
855
849
  # class Person < ActiveRecord::Base
856
850
  # has_many :pets
@@ -888,7 +882,7 @@ module ActiveRecord
888
882
 
889
883
  # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
890
884
  # contain the same number of elements and if each element is equal
891
- # to the corresponding element in the other array, otherwise returns
885
+ # to the corresponding element in the +other+ array, otherwise returns
892
886
  # +false+.
893
887
  #
894
888
  # class Person < ActiveRecord::Base
@@ -0,0 +1,11 @@
1
+ module ActiveRecord::Associations
2
+ module ForeignAssociation
3
+ def foreign_key_present?
4
+ if reflection.klass.primary_key
5
+ owner.attribute_present?(reflection.active_record_primary_key)
6
+ else
7
+ false
8
+ end
9
+ end
10
+ end
11
+ end
@@ -6,6 +6,7 @@ module ActiveRecord
6
6
  # If the association has a <tt>:through</tt> option further specialization
7
7
  # is provided by its child HasManyThroughAssociation.
8
8
  class HasManyAssociation < CollectionAssociation #:nodoc:
9
+ include ForeignAssociation
9
10
 
10
11
  def handle_dependency
11
12
  case options[:dependent]
@@ -41,6 +42,14 @@ module ActiveRecord
41
42
  end
42
43
  end
43
44
 
45
+ def empty?
46
+ if has_cached_counter?
47
+ size.zero?
48
+ else
49
+ super
50
+ end
51
+ end
52
+
44
53
  private
45
54
 
46
55
  # Returns the number of records in this collection.
@@ -58,7 +67,7 @@ module ActiveRecord
58
67
  # the loaded flag is set to true as well.
59
68
  def count_records
60
69
  count = if has_cached_counter?
61
- owner.send(:read_attribute, cached_counter_attribute_name)
70
+ owner._read_attribute cached_counter_attribute_name
62
71
  else
63
72
  scope.count
64
73
  end
@@ -71,20 +80,42 @@ module ActiveRecord
71
80
  [association_scope.limit_value, count].compact.min
72
81
  end
73
82
 
83
+
84
+ # Returns whether a counter cache should be used for this association.
85
+ #
86
+ # The counter_cache option must be given on either the owner or inverse
87
+ # association, and the column must be present on the owner.
74
88
  def has_cached_counter?(reflection = reflection())
75
- owner.attribute_present?(cached_counter_attribute_name(reflection))
89
+ if reflection.options[:counter_cache] || (inverse = inverse_which_updates_counter_cache(reflection)) && inverse.options[:counter_cache]
90
+ owner.attribute_present?(cached_counter_attribute_name(reflection))
91
+ end
76
92
  end
77
93
 
78
94
  def cached_counter_attribute_name(reflection = reflection())
79
- options[:counter_cache] || "#{reflection.name}_count"
95
+ if reflection.options[:counter_cache]
96
+ reflection.options[:counter_cache].to_s
97
+ else
98
+ "#{reflection.name}_count"
99
+ end
80
100
  end
81
101
 
82
102
  def update_counter(difference, reflection = reflection())
103
+ update_counter_in_database(difference, reflection)
104
+ update_counter_in_memory(difference, reflection)
105
+ end
106
+
107
+ def update_counter_in_database(difference, reflection = reflection())
83
108
  if has_cached_counter?(reflection)
84
109
  counter = cached_counter_attribute_name(reflection)
85
110
  owner.class.update_counters(owner.id, counter => difference)
111
+ end
112
+ end
113
+
114
+ def update_counter_in_memory(difference, reflection = reflection())
115
+ if counter_must_be_updated_by_has_many?(reflection)
116
+ counter = cached_counter_attribute_name(reflection)
86
117
  owner[counter] += difference
87
- owner.changed_attributes.delete(counter) # eww
118
+ owner.send(:clear_attribute_changes, counter) # eww
88
119
  end
89
120
  end
90
121
 
@@ -98,13 +129,41 @@ module ActiveRecord
98
129
  # it will be decremented twice.
99
130
  #
100
131
  # Hence this method.
101
- def inverse_updates_counter_cache?(reflection = reflection())
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
102
133
  counter_name = cached_counter_attribute_name(reflection)
103
- reflection.klass._reflections.values.any? { |inverse_reflection|
104
- :belongs_to == inverse_reflection.macro &&
134
+ inverse_which_updates_counter_named(counter_name, reflection)
135
+ end
136
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
137
+
138
+ def inverse_which_updates_counter_named(counter_name, reflection)
139
+ reflection.klass._reflections.values.find { |inverse_reflection|
140
+ inverse_reflection.belongs_to? &&
105
141
  inverse_reflection.counter_cache_column == counter_name
106
142
  }
107
143
  end
144
+ alias inverse_updates_counter_named? inverse_which_updates_counter_named
145
+
146
+ def inverse_updates_counter_in_memory?(reflection)
147
+ inverse = inverse_which_updates_counter_cache(reflection)
148
+ inverse && inverse == reflection.inverse_of
149
+ end
150
+
151
+ def counter_must_be_updated_by_has_many?(reflection)
152
+ !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
153
+ end
154
+
155
+ def delete_count(method, scope)
156
+ if method == :delete_all
157
+ scope.delete_all
158
+ else
159
+ scope.update_all(reflection.foreign_key => nil)
160
+ end
161
+ end
162
+
163
+ def delete_or_nullify_all_records(method)
164
+ count = delete_count(method, self.scope)
165
+ update_counter(-count)
166
+ end
108
167
 
109
168
  # Deletes the records according to the <tt>:dependent</tt> option.
110
169
  def delete_records(records, method)
@@ -112,26 +171,28 @@ module ActiveRecord
112
171
  records.each(&:destroy!)
113
172
  update_counter(-records.length) unless inverse_updates_counter_cache?
114
173
  else
115
- if records == :all || !reflection.klass.primary_key
116
- scope = self.scope
117
- else
118
- scope = self.scope.where(reflection.klass.primary_key => records)
119
- end
120
-
121
- if method == :delete_all
122
- update_counter(-scope.delete_all)
123
- else
124
- update_counter(-scope.update_all(reflection.foreign_key => nil))
125
- end
174
+ scope = self.scope.where(reflection.klass.primary_key => records)
175
+ update_counter(-delete_count(method, scope))
126
176
  end
127
177
  end
128
178
 
129
- def foreign_key_present?
130
- if reflection.klass.primary_key
131
- owner.attribute_present?(reflection.association_primary_key)
179
+ def concat_records(records, *)
180
+ update_counter_if_success(super, records.length)
181
+ end
182
+
183
+ def _create_record(attributes, *)
184
+ if attributes.is_a?(Array)
185
+ super
132
186
  else
133
- false
187
+ update_counter_if_success(super, 1)
188
+ end
189
+ end
190
+
191
+ def update_counter_if_success(saved_successfully, difference)
192
+ if saved_successfully
193
+ update_counter_in_memory(difference)
134
194
  end
195
+ saved_successfully
135
196
  end
136
197
  end
137
198
  end