activerecord 4.2.0 → 4.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (110) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -1
  3. data/lib/active_record.rb +3 -0
  4. data/lib/active_record/aggregations.rb +6 -3
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations.rb +5 -4
  7. data/lib/active_record/associations/association.rb +15 -3
  8. data/lib/active_record/associations/association_scope.rb +1 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +13 -5
  10. data/lib/active_record/associations/builder/association.rb +1 -1
  11. data/lib/active_record/associations/builder/collection_association.rb +5 -1
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -4
  13. data/lib/active_record/associations/collection_association.rb +35 -15
  14. data/lib/active_record/associations/collection_proxy.rb +15 -9
  15. data/lib/active_record/associations/foreign_association.rb +11 -0
  16. data/lib/active_record/associations/has_many_association.rb +30 -15
  17. data/lib/active_record/associations/has_many_through_association.rb +11 -2
  18. data/lib/active_record/associations/has_one_association.rb +1 -0
  19. data/lib/active_record/associations/join_dependency.rb +8 -2
  20. data/lib/active_record/associations/join_dependency/join_association.rb +7 -1
  21. data/lib/active_record/associations/preloader.rb +4 -4
  22. data/lib/active_record/associations/preloader/association.rb +5 -1
  23. data/lib/active_record/associations/singular_association.rb +2 -8
  24. data/lib/active_record/associations/through_association.rb +11 -6
  25. data/lib/active_record/attribute.rb +15 -1
  26. data/lib/active_record/attribute_assignment.rb +2 -2
  27. data/lib/active_record/attribute_methods.rb +4 -8
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +5 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +14 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +5 -1
  31. data/lib/active_record/attribute_methods/write.rb +1 -1
  32. data/lib/active_record/attribute_set.rb +4 -0
  33. data/lib/active_record/attribute_set/builder.rb +32 -12
  34. data/lib/active_record/attributes.rb +8 -0
  35. data/lib/active_record/autosave_association.rb +24 -9
  36. data/lib/active_record/base.rb +4 -5
  37. data/lib/active_record/callbacks.rb +1 -1
  38. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +12 -6
  39. data/lib/active_record/connection_adapters/abstract/database_statements.rb +23 -3
  40. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  42. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -16
  44. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +87 -24
  45. data/lib/active_record/connection_adapters/abstract/transaction.rb +2 -6
  46. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -7
  47. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -10
  48. data/lib/active_record/connection_adapters/column.rb +2 -2
  49. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -21
  50. data/lib/active_record/connection_adapters/mysql_adapter.rb +10 -3
  51. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
  52. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -1
  53. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -0
  54. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -0
  55. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  56. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -0
  58. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  59. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +3 -3
  60. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +21 -13
  61. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -12
  62. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -28
  63. data/lib/active_record/connection_handling.rb +1 -1
  64. data/lib/active_record/core.rb +28 -15
  65. data/lib/active_record/counter_cache.rb +1 -1
  66. data/lib/active_record/enum.rb +2 -3
  67. data/lib/active_record/errors.rb +6 -5
  68. data/lib/active_record/explain_subscriber.rb +1 -1
  69. data/lib/active_record/fixtures.rb +9 -7
  70. data/lib/active_record/gem_version.rb +1 -1
  71. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  72. data/lib/active_record/locking/optimistic.rb +16 -14
  73. data/lib/active_record/migration.rb +38 -10
  74. data/lib/active_record/model_schema.rb +4 -2
  75. data/lib/active_record/nested_attributes.rb +13 -3
  76. data/lib/active_record/no_touching.rb +1 -1
  77. data/lib/active_record/persistence.rb +7 -4
  78. data/lib/active_record/railtie.rb +5 -3
  79. data/lib/active_record/railties/databases.rake +17 -24
  80. data/lib/active_record/reflection.rb +40 -28
  81. data/lib/active_record/relation.rb +3 -2
  82. data/lib/active_record/relation/calculations.rb +10 -3
  83. data/lib/active_record/relation/delegation.rb +1 -1
  84. data/lib/active_record/relation/finder_methods.rb +4 -16
  85. data/lib/active_record/relation/merger.rb +24 -1
  86. data/lib/active_record/relation/predicate_builder.rb +32 -3
  87. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -2
  88. data/lib/active_record/relation/query_methods.rb +29 -27
  89. data/lib/active_record/relation/spawn_methods.rb +7 -3
  90. data/lib/active_record/schema_dumper.rb +1 -1
  91. data/lib/active_record/schema_migration.rb +1 -4
  92. data/lib/active_record/scoping/default.rb +1 -0
  93. data/lib/active_record/tasks/database_tasks.rb +5 -2
  94. data/lib/active_record/tasks/mysql_database_tasks.rb +30 -16
  95. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -8
  96. data/lib/active_record/transactions.rb +21 -11
  97. data/lib/active_record/type/boolean.rb +1 -0
  98. data/lib/active_record/type/date.rb +4 -0
  99. data/lib/active_record/type/date_time.rb +14 -3
  100. data/lib/active_record/type/decimal.rb +27 -3
  101. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  102. data/lib/active_record/type/integer.rb +9 -5
  103. data/lib/active_record/type/numeric.rb +1 -1
  104. data/lib/active_record/type/serialized.rb +7 -1
  105. data/lib/active_record/type/string.rb +4 -0
  106. data/lib/active_record/type/value.rb +9 -0
  107. data/lib/active_record/validations/uniqueness.rb +16 -6
  108. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -3
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -6
  110. metadata +9 -7
data/lib/active_record.rb CHANGED
@@ -52,6 +52,7 @@ module ActiveRecord
52
52
  autoload :QueryCache
53
53
  autoload :Querying
54
54
  autoload :ReadonlyAttributes
55
+ autoload :RecordInvalid, 'active_record/validations'
55
56
  autoload :Reflection
56
57
  autoload :RuntimeRegistry
57
58
  autoload :Sanitization
@@ -78,6 +79,8 @@ module ActiveRecord
78
79
  autoload :AttributeMethods
79
80
  autoload :AutosaveAssociation
80
81
 
82
+ autoload :LegacyYamlAdapter
83
+
81
84
  autoload :Relation
82
85
  autoload :AssociationRelation
83
86
  autoload :NullRelation
@@ -244,14 +244,17 @@ module ActiveRecord
244
244
  def writer_method(name, class_name, mapping, allow_nil, converter)
245
245
  define_method("#{name}=") do |part|
246
246
  klass = class_name.constantize
247
- if part.is_a?(Hash)
248
- part = klass.new(*part.values)
249
- end
250
247
 
251
248
  unless part.is_a?(klass) || converter.nil? || part.nil?
252
249
  part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
253
250
  end
254
251
 
252
+ hash_from_multiparameter_assignment = part.is_a?(Hash) &&
253
+ part.each_key.all? { |k| k.is_a?(Integer) }
254
+ if hash_from_multiparameter_assignment
255
+ part = klass.new(*part.values)
256
+ end
257
+
255
258
  if part.nil? && allow_nil
256
259
  mapping.each { |key, _| self[key] = nil }
257
260
  @aggregation_cache[name] = nil
@@ -13,6 +13,19 @@ module ActiveRecord
13
13
  other == to_a
14
14
  end
15
15
 
16
+ def build(*args, &block)
17
+ scoping { @association.build(*args, &block) }
18
+ end
19
+ alias new build
20
+
21
+ def create(*args, &block)
22
+ scoping { @association.create(*args, &block) }
23
+ end
24
+
25
+ def create!(*args, &block)
26
+ scoping { @association.create!(*args, &block) }
27
+ end
28
+
16
29
  private
17
30
 
18
31
  def exec_queries
@@ -116,6 +116,7 @@ module ActiveRecord
116
116
  autoload :Association, 'active_record/associations/association'
117
117
  autoload :SingularAssociation, 'active_record/associations/singular_association'
118
118
  autoload :CollectionAssociation, 'active_record/associations/collection_association'
119
+ autoload :ForeignAssociation, 'active_record/associations/foreign_association'
119
120
  autoload :CollectionProxy, 'active_record/associations/collection_proxy'
120
121
 
121
122
  autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
@@ -1011,7 +1012,7 @@ module ActiveRecord
1011
1012
  # record(s) being removed so that callbacks are run. However <tt>delete</tt> and <tt>delete_all</tt> will either
1012
1013
  # do the deletion according to the strategy specified by the <tt>:dependent</tt> option, or
1013
1014
  # if no <tt>:dependent</tt> option is given, then it will follow the default strategy.
1014
- # The default strategy is <tt>:nullify</tt> (set the foreign keys to <tt>nil</tt>), except for
1015
+ # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for
1015
1016
  # +has_many+ <tt>:through</tt>, where the default strategy is <tt>delete_all</tt> (delete
1016
1017
  # the join records, without running their callbacks).
1017
1018
  #
@@ -1377,7 +1378,7 @@ module ActiveRecord
1377
1378
  # has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
1378
1379
  # has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
1379
1380
  # has_one :attachment, as: :attachable
1380
- # has_one :boss, readonly: :true
1381
+ # has_one :boss, -> { readonly }
1381
1382
  # has_one :club, through: :membership
1382
1383
  # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
1383
1384
  # has_one :credit_card, required: true
@@ -1505,7 +1506,7 @@ module ActiveRecord
1505
1506
  # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
1506
1507
  # class_name: "Coupon", foreign_key: "coupon_id"
1507
1508
  # belongs_to :attachable, polymorphic: true
1508
- # belongs_to :project, readonly: true
1509
+ # belongs_to :project, -> { readonly }
1509
1510
  # belongs_to :post, counter_cache: true
1510
1511
  # belongs_to :company, touch: true
1511
1512
  # belongs_to :company, touch: :employees_last_updated_at
@@ -1712,7 +1713,7 @@ module ActiveRecord
1712
1713
  hm_options[:through] = middle_reflection.name
1713
1714
  hm_options[:source] = join_model.right_reflection.name
1714
1715
 
1715
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name].each do |k|
1716
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
1716
1717
  hm_options[k] = options[k] if options.key? k
1717
1718
  end
1718
1719
 
@@ -211,9 +211,12 @@ module ActiveRecord
211
211
  # the kind of the class of the associated objects. Meant to be used as
212
212
  # a sanity check when you are about to assign an associated record.
213
213
  def raise_on_type_mismatch!(record)
214
- unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
215
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
216
- raise ActiveRecord::AssociationTypeMismatch, message
214
+ unless record.is_a?(reflection.klass)
215
+ fresh_class = reflection.class_name.safe_constantize
216
+ unless fresh_class && record.is_a?(fresh_class)
217
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
218
+ raise ActiveRecord::AssociationTypeMismatch, message
219
+ end
217
220
  end
218
221
  end
219
222
 
@@ -248,6 +251,15 @@ module ActiveRecord
248
251
  initialize_attributes(record)
249
252
  end
250
253
  end
254
+
255
+ # Returns true if statement cache should be skipped on the association reader.
256
+ def skip_statement_cache?
257
+ reflection.scope_chain.any?(&:any?) ||
258
+ scope.eager_loading? ||
259
+ klass.current_scope ||
260
+ klass.default_scopes.any? ||
261
+ reflection.source_reflection.active_record.default_scopes.any?
262
+ end
251
263
  end
252
264
  end
253
265
  end
@@ -162,6 +162,7 @@ module ActiveRecord
162
162
  scope.includes! item.includes_values
163
163
  end
164
164
 
165
+ scope.unscope!(*item.unscope_values)
165
166
  scope.where_values += item.where_values
166
167
  scope.bind_values += item.bind_values
167
168
  scope.order_values |= item.order_values
@@ -68,16 +68,19 @@ module ActiveRecord
68
68
  def increment_counter(counter_cache_name)
69
69
  if foreign_key_present?
70
70
  klass.increment_counter(counter_cache_name, target_id)
71
+ if target && !stale_target? && counter_cache_available_in_memory?(counter_cache_name)
72
+ target.increment(counter_cache_name)
73
+ end
71
74
  end
72
75
  end
73
76
 
74
77
  # Checks whether record is different to the current target, without loading it
75
78
  def different_target?(record)
76
- record.id != owner[reflection.foreign_key]
79
+ record.id != owner._read_attribute(reflection.foreign_key)
77
80
  end
78
81
 
79
82
  def replace_keys(record)
80
- owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
83
+ owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
81
84
  end
82
85
 
83
86
  def remove_keys
@@ -85,7 +88,7 @@ module ActiveRecord
85
88
  end
86
89
 
87
90
  def foreign_key_present?
88
- owner[reflection.foreign_key]
91
+ owner._read_attribute(reflection.foreign_key)
89
92
  end
90
93
 
91
94
  # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
@@ -99,12 +102,17 @@ module ActiveRecord
99
102
  if options[:primary_key]
100
103
  owner.send(reflection.name).try(:id)
101
104
  else
102
- owner[reflection.foreign_key]
105
+ owner._read_attribute(reflection.foreign_key)
103
106
  end
104
107
  end
105
108
 
106
109
  def stale_state
107
- owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
110
+ result = owner._read_attribute(reflection.foreign_key)
111
+ result && result.to_s
112
+ end
113
+
114
+ def counter_cache_available_in_memory?(counter_cache_name)
115
+ target.respond_to?(counter_cache_name)
108
116
  end
109
117
  end
110
118
  end
@@ -21,7 +21,7 @@ module ActiveRecord::Associations::Builder
21
21
  end
22
22
  self.extensions = []
23
23
 
24
- self.valid_options = [:class_name, :class, :foreign_key, :validate]
24
+ self.valid_options = [:class_name, :anonymous_class, :foreign_key, :validate]
25
25
 
26
26
  attr_reader :name, :scope, :options
27
27
 
@@ -82,7 +82,11 @@ module ActiveRecord::Associations::Builder
82
82
 
83
83
  def wrap_scope(scope, mod)
84
84
  if scope
85
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
85
+ if scope.arity > 0
86
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
87
+ else
88
+ proc { instance_exec(&scope).extending(mod) }
89
+ end
86
90
  else
87
91
  proc { extending(mod) }
88
92
  end
@@ -46,7 +46,7 @@ module ActiveRecord::Associations::Builder
46
46
 
47
47
  join_model = Class.new(ActiveRecord::Base) {
48
48
  class << self;
49
- attr_accessor :class_resolver
49
+ attr_accessor :left_model
50
50
  attr_accessor :name
51
51
  attr_accessor :table_name_resolver
52
52
  attr_accessor :left_reflection
@@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder
58
58
  end
59
59
 
60
60
  def self.compute_type(class_name)
61
- class_resolver.compute_type class_name
61
+ left_model.compute_type class_name
62
62
  end
63
63
 
64
64
  def self.add_left_association(name, options)
@@ -72,13 +72,17 @@ module ActiveRecord::Associations::Builder
72
72
  self.right_reflection = _reflect_on_association(rhs_name)
73
73
  end
74
74
 
75
+ def self.retrieve_connection
76
+ left_model.retrieve_connection
77
+ end
78
+
75
79
  }
76
80
 
77
81
  join_model.name = "HABTM_#{association_name.to_s.camelize}"
78
82
  join_model.table_name_resolver = habtm
79
- join_model.class_resolver = lhs_model
83
+ join_model.left_model = lhs_model
80
84
 
81
- join_model.add_left_association :left_side, class: lhs_model
85
+ join_model.add_left_association :left_side, anonymous_class: lhs_model
82
86
  join_model.add_right_association association_name, belongs_to_options(options)
83
87
  join_model
84
88
  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_type = reflection.primary_key_type
65
- ids = Array(ids).reject { |id| id.blank? }
66
- ids.map! { |i| pk_type.type_cast_from_user(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
@@ -129,6 +140,16 @@ module ActiveRecord
129
140
  first_nth_or_last(:last, *args)
130
141
  end
131
142
 
143
+ def take(n = nil)
144
+ if loaded?
145
+ n ? target.take(n) : target.first
146
+ else
147
+ scope.take(n).tap do |record|
148
+ set_inverse_instance record if record.is_a? ActiveRecord::Base
149
+ end
150
+ end
151
+ end
152
+
132
153
  def build(attributes = {}, &block)
133
154
  if attributes.is_a?(Array)
134
155
  attributes.collect { |attr| build(attr, &block) }
@@ -254,7 +275,7 @@ module ActiveRecord
254
275
  _options = records.extract_options!
255
276
  dependent = _options[:dependent] || options[:dependent]
256
277
 
257
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
278
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
258
279
  delete_or_destroy(records, dependent)
259
280
  end
260
281
 
@@ -265,7 +286,7 @@ module ActiveRecord
265
286
  # +:dependent+ option.
266
287
  def destroy(*records)
267
288
  return if records.empty?
268
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
289
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
269
290
  delete_or_destroy(records, :destroy)
270
291
  end
271
292
 
@@ -421,13 +442,7 @@ module ActiveRecord
421
442
 
422
443
  private
423
444
  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
445
+ return scope.to_a if skip_statement_cache?
431
446
 
432
447
  conn = klass.connection
433
448
  sc = reflection.association_scope_cache(conn, owner) do
@@ -597,8 +612,13 @@ module ActiveRecord
597
612
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
598
613
  assoc = owner.association(reflection.through_reflection.name)
599
614
  assoc.reader.any? { |source|
600
- target = source.send(reflection.source_reflection.name)
601
- target.respond_to?(:include?) ? target.include?(record) : target == record
615
+ target_association = source.send(reflection.source_reflection.name)
616
+
617
+ if target_association.respond_to?(:include?)
618
+ target_association.include?(record)
619
+ else
620
+ target_association == record
621
+ end
602
622
  } || target.include?(record)
603
623
  else
604
624
  target.include?(record)
@@ -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
@@ -226,6 +227,10 @@ module ActiveRecord
226
227
  @association.last(*args)
227
228
  end
228
229
 
230
+ def take(n = nil)
231
+ @association.take(n)
232
+ end
233
+
229
234
  # Returns a new object of the collection type that has been instantiated
230
235
  # with +attributes+ and linked to this object, but have not yet been saved.
231
236
  # You can pass an array of attributes hashes, this will return an array
@@ -465,15 +470,16 @@ module ActiveRecord
465
470
  @association.destroy_all
466
471
  end
467
472
 
468
- # Deletes the +records+ supplied and removes them from the collection. For
469
- # +has_many+ associations, the deletion is done according to the strategy
470
- # 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
471
476
  # deleted records.
472
477
  #
473
- # If no <tt>:dependent</tt> option is given, then it will follow the default
474
- # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
475
- # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
476
- # 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+.
477
483
  #
478
484
  # class Person < ActiveRecord::Base
479
485
  # has_many :pets # dependent: :nullify option by default
@@ -556,7 +562,7 @@ module ActiveRecord
556
562
  # Pet.find(1)
557
563
  # # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
558
564
  #
559
- # You can pass +Fixnum+ or +String+ values, it finds the records
565
+ # You can pass +Integer+ or +String+ values, it finds the records
560
566
  # responding to the +id+ and executes delete on them.
561
567
  #
562
568
  # class Person < ActiveRecord::Base
@@ -620,7 +626,7 @@ module ActiveRecord
620
626
  #
621
627
  # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
622
628
  #
623
- # You can pass +Fixnum+ or +String+ values, it finds the records
629
+ # You can pass +Integer+ or +String+ values, it finds the records
624
630
  # responding to the +id+ and then deletes them from the database.
625
631
  #
626
632
  # person.pets.size # => 3
@@ -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]
@@ -79,12 +80,23 @@ module ActiveRecord
79
80
  [association_scope.limit_value, count].compact.min
80
81
  end
81
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.
82
88
  def has_cached_counter?(reflection = reflection())
83
- 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
84
92
  end
85
93
 
86
94
  def cached_counter_attribute_name(reflection = reflection())
87
- 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
88
100
  end
89
101
 
90
102
  def update_counter(difference, reflection = reflection())
@@ -100,7 +112,7 @@ module ActiveRecord
100
112
  end
101
113
 
102
114
  def update_counter_in_memory(difference, reflection = reflection())
103
- if has_cached_counter?(reflection)
115
+ if counter_must_be_updated_by_has_many?(reflection)
104
116
  counter = cached_counter_attribute_name(reflection)
105
117
  owner[counter] += difference
106
118
  owner.send(:clear_attribute_changes, counter) # eww
@@ -117,17 +129,28 @@ module ActiveRecord
117
129
  # it will be decremented twice.
118
130
  #
119
131
  # Hence this method.
120
- def inverse_updates_counter_cache?(reflection = reflection())
132
+ def inverse_which_updates_counter_cache(reflection = reflection())
121
133
  counter_name = cached_counter_attribute_name(reflection)
122
- inverse_updates_counter_named?(counter_name, reflection)
134
+ inverse_which_updates_counter_named(counter_name, reflection)
123
135
  end
136
+ alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
124
137
 
125
- def inverse_updates_counter_named?(counter_name, reflection = reflection())
126
- reflection.klass._reflections.values.any? { |inverse_reflection|
138
+ def inverse_which_updates_counter_named(counter_name, reflection)
139
+ reflection.klass._reflections.values.find { |inverse_reflection|
127
140
  inverse_reflection.belongs_to? &&
128
141
  inverse_reflection.counter_cache_column == counter_name
129
142
  }
130
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
131
154
 
132
155
  def delete_count(method, scope)
133
156
  if method == :delete_all
@@ -153,14 +176,6 @@ module ActiveRecord
153
176
  end
154
177
  end
155
178
 
156
- def foreign_key_present?
157
- if reflection.klass.primary_key
158
- owner.attribute_present?(reflection.association_primary_key)
159
- else
160
- false
161
- end
162
- end
163
-
164
179
  def concat_records(records, *)
165
180
  update_counter_if_success(super, records.length)
166
181
  end