activerecord 4.1.1 → 4.1.2.rc1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +312 -0
  3. data/lib/active_record/association_relation.rb +4 -0
  4. data/lib/active_record/associations.rb +24 -3
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -4
  7. data/lib/active_record/associations/builder/has_many.rb +1 -1
  8. data/lib/active_record/associations/collection_association.rb +5 -5
  9. data/lib/active_record/associations/collection_proxy.rb +4 -0
  10. data/lib/active_record/associations/has_many_association.rb +6 -5
  11. data/lib/active_record/associations/has_many_through_association.rb +6 -2
  12. data/lib/active_record/associations/join_dependency.rb +1 -1
  13. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  14. data/lib/active_record/associations/preloader.rb +14 -35
  15. data/lib/active_record/associations/preloader/association.rb +26 -5
  16. data/lib/active_record/associations/singular_association.rb +3 -3
  17. data/lib/active_record/associations/through_association.rb +4 -2
  18. data/lib/active_record/attribute_methods.rb +2 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +2 -2
  20. data/lib/active_record/attribute_methods/serialization.rb +24 -5
  21. data/lib/active_record/attribute_methods/write.rb +22 -14
  22. data/lib/active_record/autosave_association.rb +40 -35
  23. data/lib/active_record/base.rb +2 -2
  24. data/lib/active_record/callbacks.rb +2 -2
  25. data/lib/active_record/connection_adapters/abstract/database_statements.rb +10 -13
  26. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  27. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  29. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +8 -6
  30. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -4
  31. data/lib/active_record/connection_adapters/postgresql/oid.rb +10 -5
  32. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -0
  33. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +11 -6
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +7 -0
  35. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +6 -3
  36. data/lib/active_record/connection_handling.rb +2 -2
  37. data/lib/active_record/core.rb +3 -0
  38. data/lib/active_record/counter_cache.rb +2 -3
  39. data/lib/active_record/fixtures.rb +1 -1
  40. data/lib/active_record/gem_version.rb +2 -2
  41. data/lib/active_record/locking/optimistic.rb +1 -1
  42. data/lib/active_record/log_subscriber.rb +1 -1
  43. data/lib/active_record/migration.rb +1 -1
  44. data/lib/active_record/nested_attributes.rb +2 -2
  45. data/lib/active_record/null_relation.rb +19 -5
  46. data/lib/active_record/persistence.rb +8 -8
  47. data/lib/active_record/railties/databases.rake +3 -2
  48. data/lib/active_record/reflection.rb +45 -13
  49. data/lib/active_record/relation.rb +7 -6
  50. data/lib/active_record/relation/calculations.rb +10 -2
  51. data/lib/active_record/relation/delegation.rb +2 -2
  52. data/lib/active_record/relation/finder_methods.rb +1 -1
  53. data/lib/active_record/relation/merger.rb +10 -2
  54. data/lib/active_record/relation/predicate_builder.rb +2 -2
  55. data/lib/active_record/relation/query_methods.rb +2 -2
  56. data/lib/active_record/scoping/default.rb +3 -3
  57. data/lib/active_record/store.rb +14 -5
  58. data/lib/active_record/timestamp.rb +2 -2
  59. data/lib/active_record/transactions.rb +1 -1
  60. data/lib/active_record/validations/presence.rb +1 -1
  61. data/lib/active_record/validations/uniqueness.rb +2 -2
  62. metadata +27 -35
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder
5
5
  end
6
6
 
7
7
  def valid_options
8
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
8
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
9
9
  end
10
10
 
11
11
  def self.valid_dependent_options
@@ -134,11 +134,11 @@ module ActiveRecord
134
134
  end
135
135
 
136
136
  def create(attributes = {}, &block)
137
- create_record(attributes, &block)
137
+ _create_record(attributes, &block)
138
138
  end
139
139
 
140
140
  def create!(attributes = {}, &block)
141
- create_record(attributes, true, &block)
141
+ _create_record(attributes, true, &block)
142
142
  end
143
143
 
144
144
  # Add +records+ to this association. Returns +self+ so method calls may
@@ -249,7 +249,7 @@ module ActiveRecord
249
249
  dependent = _options[:dependent] || options[:dependent]
250
250
 
251
251
  if records.first == :all
252
- if loaded? || dependent == :destroy
252
+ if (loaded? || dependent == :destroy) && dependent != :delete_all
253
253
  delete_or_destroy(load_target, dependent)
254
254
  else
255
255
  delete_records(:all, dependent)
@@ -448,13 +448,13 @@ module ActiveRecord
448
448
  persisted + memory
449
449
  end
450
450
 
451
- def create_record(attributes, raise = false, &block)
451
+ def _create_record(attributes, raise = false, &block)
452
452
  unless owner.persisted?
453
453
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
454
454
  end
455
455
 
456
456
  if attributes.is_a?(Array)
457
- attributes.collect { |attr| create_record(attr, raise, &block) }
457
+ attributes.collect { |attr| _create_record(attr, raise, &block) }
458
458
  else
459
459
  transaction do
460
460
  add_to_target(build_record(attributes)) do |record|
@@ -860,6 +860,10 @@ module ActiveRecord
860
860
  !!@association.include?(record)
861
861
  end
862
862
 
863
+ def arel
864
+ scope.arel
865
+ end
866
+
863
867
  def proxy_association
864
868
  @association
865
869
  end
@@ -71,15 +71,15 @@ module ActiveRecord
71
71
  [association_scope.limit_value, count].compact.min
72
72
  end
73
73
 
74
- def has_cached_counter?(reflection = reflection)
74
+ def has_cached_counter?(reflection = reflection())
75
75
  owner.attribute_present?(cached_counter_attribute_name(reflection))
76
76
  end
77
77
 
78
- def cached_counter_attribute_name(reflection = reflection)
78
+ def cached_counter_attribute_name(reflection = reflection())
79
79
  options[:counter_cache] || "#{reflection.name}_count"
80
80
  end
81
81
 
82
- def update_counter(difference, reflection = reflection)
82
+ def update_counter(difference, reflection = reflection())
83
83
  if has_cached_counter?(reflection)
84
84
  counter = cached_counter_attribute_name(reflection)
85
85
  owner.class.update_counters(owner.id, counter => difference)
@@ -98,9 +98,10 @@ module ActiveRecord
98
98
  # it will be decremented twice.
99
99
  #
100
100
  # Hence this method.
101
- def inverse_updates_counter_cache?(reflection = reflection)
101
+ def inverse_updates_counter_cache?(reflection = reflection())
102
102
  counter_name = cached_counter_attribute_name(reflection)
103
- reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
103
+ reflection.klass._reflections.values.any? { |inverse_reflection|
104
+ :belongs_to == inverse_reflection.macro &&
104
105
  inverse_reflection.counter_cache_column == counter_name
105
106
  }
106
107
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  elsif loaded?
23
23
  target.size
24
24
  else
25
- count
25
+ super
26
26
  end
27
27
  end
28
28
 
@@ -83,12 +83,16 @@ module ActiveRecord
83
83
  @through_records[record.object_id] ||= begin
84
84
  ensure_mutable
85
85
 
86
- through_record = through_association.build
86
+ through_record = through_association.build through_scope_attributes
87
87
  through_record.send("#{source_reflection.name}=", record)
88
88
  through_record
89
89
  end
90
90
  end
91
91
 
92
+ def through_scope_attributes
93
+ scope.where_values_hash(through_association.reflection.name.to_s)
94
+ end
95
+
92
96
  def save_through_record(record)
93
97
  build_through_record(record).save!
94
98
  ensure
@@ -207,7 +207,7 @@ module ActiveRecord
207
207
  end
208
208
 
209
209
  def find_reflection(klass, name)
210
- klass.reflect_on_association(name) or
210
+ klass._reflect_on_association(name) or
211
211
  raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
212
212
  end
213
213
 
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  end
55
55
  scope_chain_index += 1
56
56
 
57
- scope_chain_items.concat [klass.send(:build_default_scope)].compact
57
+ scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
58
58
 
59
59
  rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
60
60
  left.merge right
@@ -112,13 +112,14 @@ module ActiveRecord
112
112
  end
113
113
 
114
114
  def preloaders_for_hash(association, records, scope)
115
- parent, child = association.to_a.first # hash should only be of length 1
115
+ association.flat_map { |parent, child|
116
+ loaders = preloaders_for_one parent, records, scope
116
117
 
117
- loaders = preloaders_for_one parent, records, scope
118
-
119
- recs = loaders.flat_map(&:preloaded_records).uniq
120
- loaders.concat Array.wrap(child).flat_map { |assoc|
121
- preloaders_on assoc, recs, scope
118
+ recs = loaders.flat_map(&:preloaded_records).uniq
119
+ loaders.concat Array.wrap(child).flat_map { |assoc|
120
+ preloaders_on assoc, recs, scope
121
+ }
122
+ loaders
122
123
  }
123
124
  end
124
125
 
@@ -140,36 +141,14 @@ module ActiveRecord
140
141
  end
141
142
 
142
143
  def grouped_records(association, records)
143
- reflection_records = records_by_reflection(association, records)
144
-
145
- reflection_records.each_with_object({}) do |(reflection, r_records),h|
146
- h[reflection] = r_records.group_by { |record|
147
- association_klass(reflection, record)
148
- }
149
- end
150
- end
151
-
152
- def records_by_reflection(association, records)
153
- records.group_by do |record|
154
- reflection = record.class.reflect_on_association(association)
155
-
156
- reflection || raise_config_error(record, association)
157
- end
158
- end
159
-
160
- def raise_config_error(record, association)
161
- raise ActiveRecord::ConfigurationError,
162
- "Association named '#{association}' was not found on #{record.class.name}; " \
163
- "perhaps you misspelled it?"
164
- end
165
-
166
- def association_klass(reflection, record)
167
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
168
- klass = record.read_attribute(reflection.foreign_type.to_s)
169
- klass && klass.constantize
170
- else
171
- reflection.klass
144
+ h = {}
145
+ records.each do |record|
146
+ next unless record
147
+ assoc = record.association(association)
148
+ klasses = h[assoc.reflection] ||= {}
149
+ (klasses[assoc.klass] ||= []) << record
172
150
  end
151
+ h
173
152
  end
174
153
 
175
154
  class AlreadyLoaded
@@ -57,9 +57,15 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def owners_by_key
60
- @owners_by_key ||= owners.group_by do |owner|
61
- owner[owner_key_name]
62
- end
60
+ @owners_by_key ||= if key_conversion_required?
61
+ owners.group_by do |owner|
62
+ owner[owner_key_name].to_s
63
+ end
64
+ else
65
+ owners.group_by do |owner|
66
+ owner[owner_key_name]
67
+ end
68
+ end
63
69
  end
64
70
 
65
71
  def options
@@ -93,13 +99,28 @@ module ActiveRecord
93
99
  records_by_owner
94
100
  end
95
101
 
102
+ def key_conversion_required?
103
+ association_key_type != owner_key_type
104
+ end
105
+
106
+ def association_key_type
107
+ @klass.column_types[association_key_name.to_s].type
108
+ end
109
+
110
+ def owner_key_type
111
+ @model.column_types[owner_key_name.to_s].type
112
+ end
113
+
96
114
  def load_slices(slices)
97
115
  @preloaded_records = slices.flat_map { |slice|
98
116
  records_for(slice)
99
117
  }
100
118
 
101
119
  @preloaded_records.map { |record|
102
- [record, record[association_key_name]]
120
+ key = record[association_key_name]
121
+ key = key.to_s if key_conversion_required?
122
+
123
+ [record, key]
103
124
  }
104
125
  end
105
126
 
@@ -116,7 +137,7 @@ module ActiveRecord
116
137
  scope.where_values = Array(values[:where]) + Array(preload_values[:where])
117
138
  scope.references_values = Array(values[:references]) + Array(preload_values[:references])
118
139
 
119
- scope.select! preload_values[:select] || values[:select] || table[Arel.star]
140
+ scope._select! preload_values[:select] || values[:select] || table[Arel.star]
120
141
  scope.includes! preload_values[:includes] || values[:includes]
121
142
 
122
143
  if preload_values.key? :order
@@ -18,11 +18,11 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def create(attributes = {}, &block)
21
- create_record(attributes, &block)
21
+ _create_record(attributes, &block)
22
22
  end
23
23
 
24
24
  def create!(attributes = {}, &block)
25
- create_record(attributes, true, &block)
25
+ _create_record(attributes, true, &block)
26
26
  end
27
27
 
28
28
  def build(attributes = {})
@@ -52,7 +52,7 @@ module ActiveRecord
52
52
  replace(record)
53
53
  end
54
54
 
55
- def create_record(attributes, raise_error = false)
55
+ def _create_record(attributes, raise_error = false)
56
56
  record = build_record(attributes)
57
57
  yield(record) if block_given?
58
58
  saved = record.save
@@ -14,9 +14,11 @@ module ActiveRecord
14
14
  def target_scope
15
15
  scope = super
16
16
  chain.drop(1).each do |reflection|
17
+ relation = reflection.klass.all
18
+ relation.merge!(reflection.scope) if reflection.scope
19
+
17
20
  scope.merge!(
18
- reflection.klass.all.
19
- except(:select, :create_with, :includes, :preload, :joins, :eager_load)
21
+ relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
20
22
  )
21
23
  end
22
24
  scope
@@ -66,6 +66,7 @@ module ActiveRecord
66
66
  # Generates all the attribute related methods for columns in the database
67
67
  # accessors, mutators and query methods.
68
68
  def define_attribute_methods # :nodoc:
69
+ return false if @attribute_methods_generated
69
70
  # Use a mutex; we don't want two thread simultaneously trying to define
70
71
  # attribute methods.
71
72
  generated_attribute_methods.synchronize do
@@ -200,6 +201,7 @@ module ActiveRecord
200
201
  # this is probably horribly slow, but should only happen at most once for a given AR class
201
202
  attribute_method.bind(self).call(*args, &block)
202
203
  else
204
+ return super unless respond_to_missing?(method, true)
203
205
  send(method, *args, &block)
204
206
  end
205
207
  else
@@ -79,11 +79,11 @@ module ActiveRecord
79
79
  end
80
80
  end
81
81
 
82
- def update_record(*)
82
+ def _update_record(*)
83
83
  partial_writes? ? super(keys_for_partial_write) : super
84
84
  end
85
85
 
86
- def create_record(*)
86
+ def _create_record(*)
87
87
  partial_writes? ? super(keys_for_partial_write) : super
88
88
  end
89
89
 
@@ -30,7 +30,8 @@ module ActiveRecord
30
30
  # ==== Parameters
31
31
  #
32
32
  # * +attr_name+ - The field name that should be serialized.
33
- # * +class_name+ - Optional, class name that the object type should be equal to.
33
+ # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump`
34
+ # or a class name that the object type should be equal to.
34
35
  #
35
36
  # ==== Example
36
37
  #
@@ -38,13 +39,23 @@ module ActiveRecord
38
39
  # class User < ActiveRecord::Base
39
40
  # serialize :preferences
40
41
  # end
41
- def serialize(attr_name, class_name = Object)
42
+ #
43
+ # # Serialize preferences using JSON as coder.
44
+ # class User < ActiveRecord::Base
45
+ # serialize :preferences, JSON
46
+ # end
47
+ #
48
+ # # Serialize preferences as Hash using YAML coder.
49
+ # class User < ActiveRecord::Base
50
+ # serialize :preferences, Hash
51
+ # end
52
+ def serialize(attr_name, class_name_or_coder = Object)
42
53
  include Behavior
43
54
 
44
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
45
- class_name
55
+ coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
56
+ class_name_or_coder
46
57
  else
47
- Coders::YAMLColumn.new(class_name)
58
+ Coders::YAMLColumn.new(class_name_or_coder)
48
59
  end
49
60
 
50
61
  # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
@@ -131,6 +142,14 @@ module ActiveRecord
131
142
  end
132
143
  end
133
144
 
145
+ def raw_type_cast_attribute_for_write(column, value)
146
+ if column && coder = self.class.serialized_attributes[column.name]
147
+ Attribute.new(coder, value, :serialized)
148
+ else
149
+ super
150
+ end
151
+ end
152
+
134
153
  def _field_changed?(attr, old, value)
135
154
  if self.class.serialized_attributes.include?(attr)
136
155
  old != value
@@ -55,6 +55,27 @@ module ActiveRecord
55
55
  # specified +value+. Empty strings for fixnum and float columns are
56
56
  # turned into +nil+.
57
57
  def write_attribute(attr_name, value)
58
+ write_attribute_with_type_cast(attr_name, value, :type_cast_attribute_for_write)
59
+ end
60
+
61
+ def raw_write_attribute(attr_name, value)
62
+ write_attribute_with_type_cast(attr_name, value, :raw_type_cast_attribute_for_write)
63
+ end
64
+
65
+ private
66
+ # Handle *= for method_missing.
67
+ def attribute=(attribute_name, value)
68
+ write_attribute(attribute_name, value)
69
+ end
70
+
71
+ def type_cast_attribute_for_write(column, value)
72
+ return value unless column
73
+
74
+ column.type_cast_for_write value
75
+ end
76
+ alias_method :raw_type_cast_attribute_for_write, :type_cast_attribute_for_write
77
+
78
+ def write_attribute_with_type_cast(attr_name, value, type_cast_method)
58
79
  attr_name = attr_name.to_s
59
80
  attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
60
81
  @attributes_cache.delete(attr_name)
@@ -67,24 +88,11 @@ module ActiveRecord
67
88
  end
68
89
 
69
90
  if column || @attributes.has_key?(attr_name)
70
- @attributes[attr_name] = type_cast_attribute_for_write(column, value)
91
+ @attributes[attr_name] = send(type_cast_method, column, value)
71
92
  else
72
93
  raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
73
94
  end
74
95
  end
75
- alias_method :raw_write_attribute, :write_attribute
76
-
77
- private
78
- # Handle *= for method_missing.
79
- def attribute=(attribute_name, value)
80
- write_attribute(attribute_name, value)
81
- end
82
-
83
- def type_cast_attribute_for_write(column, value)
84
- return value unless column
85
-
86
- column.type_cast_for_write value
87
- end
88
96
  end
89
97
  end
90
98
  end
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  #
36
36
  # === One-to-one Example
37
37
  #
38
- # class Post
38
+ # class Post < ActiveRecord::Base
39
39
  # has_one :author, autosave: true
40
40
  # end
41
41
  #
@@ -76,7 +76,7 @@ module ActiveRecord
76
76
  #
77
77
  # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
78
78
  #
79
- # class Post
79
+ # class Post < ActiveRecord::Base
80
80
  # has_many :comments # :autosave option is not declared
81
81
  # end
82
82
  #
@@ -95,20 +95,23 @@ module ActiveRecord
95
95
  # When <tt>:autosave</tt> is true all children are saved, no matter whether they
96
96
  # are new records or not:
97
97
  #
98
- # class Post
98
+ # class Post < ActiveRecord::Base
99
99
  # has_many :comments, autosave: true
100
100
  # end
101
101
  #
102
102
  # post = Post.create(title: 'ruby rocks')
103
103
  # post.comments.create(body: 'hello world')
104
104
  # post.comments[0].body = 'hi everyone'
105
- # post.save # => saves both post and comment, with 'hi everyone' as body
105
+ # post.comments.build(body: "good morning.")
106
+ # post.title += "!"
107
+ # post.save # => saves both post and comments.
106
108
  #
107
109
  # Destroying one of the associated models as part of the parent's save action
108
110
  # is as simple as marking it for destruction:
109
111
  #
110
- # post.comments.last.mark_for_destruction
111
- # post.comments.last.marked_for_destruction? # => true
112
+ # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
113
+ # post.comments[1].mark_for_destruction
114
+ # post.comments[1].marked_for_destruction? # => true
112
115
  # post.comments.length # => 2
113
116
  #
114
117
  # Note that the model is _not_ yet removed from the database:
@@ -144,6 +147,7 @@ module ActiveRecord
144
147
  private
145
148
 
146
149
  def define_non_cyclic_method(name, &block)
150
+ return if method_defined?(name)
147
151
  define_method(name) do |*args|
148
152
  result = true; @_already_called ||= {}
149
153
  # Loop prevention for validation of associations
@@ -176,30 +180,28 @@ module ActiveRecord
176
180
  validation_method = :"validate_associated_records_for_#{reflection.name}"
177
181
  collection = reflection.collection?
178
182
 
179
- unless method_defined?(save_method)
180
- if collection
181
- before_save :before_save_collection_association
182
-
183
- define_non_cyclic_method(save_method) { save_collection_association(reflection) }
184
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
185
- after_create save_method
186
- after_update save_method
187
- elsif reflection.macro == :has_one
188
- define_method(save_method) { save_has_one_association(reflection) }
189
- # Configures two callbacks instead of a single after_save so that
190
- # the model may rely on their execution order relative to its
191
- # own callbacks.
192
- #
193
- # For example, given that after_creates run before after_saves, if
194
- # we configured instead an after_save there would be no way to fire
195
- # a custom after_create callback after the child association gets
196
- # created.
197
- after_create save_method
198
- after_update save_method
199
- else
200
- define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
201
- before_save save_method
202
- end
183
+ if collection
184
+ before_save :before_save_collection_association
185
+
186
+ define_non_cyclic_method(save_method) { save_collection_association(reflection) }
187
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
188
+ after_create save_method
189
+ after_update save_method
190
+ elsif reflection.macro == :has_one
191
+ define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method)
192
+ # Configures two callbacks instead of a single after_save so that
193
+ # the model may rely on their execution order relative to its
194
+ # own callbacks.
195
+ #
196
+ # For example, given that after_creates run before after_saves, if
197
+ # we configured instead an after_save there would be no way to fire
198
+ # a custom after_create callback after the child association gets
199
+ # created.
200
+ after_create save_method
201
+ after_update save_method
202
+ else
203
+ define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
204
+ before_save save_method
203
205
  end
204
206
 
205
207
  if reflection.validate? && !method_defined?(validation_method)
@@ -270,9 +272,11 @@ module ActiveRecord
270
272
  # go through nested autosave associations that are loaded in memory (without loading
271
273
  # any new ones), and return true if is changed for autosave
272
274
  def nested_records_changed_for_autosave?
273
- self.class.reflect_on_all_autosave_associations.any? do |reflection|
274
- association = association_instance_get(reflection.name)
275
- association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
275
+ self.class._reflections.values.any? do |reflection|
276
+ if reflection.options[:autosave]
277
+ association = association_instance_get(reflection.name)
278
+ association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? }
279
+ end
276
280
  end
277
281
  end
278
282
 
@@ -377,15 +381,16 @@ module ActiveRecord
377
381
  def save_has_one_association(reflection)
378
382
  association = association_instance_get(reflection.name)
379
383
  record = association && association.load_target
384
+
380
385
  if record && !record.destroyed?
381
386
  autosave = reflection.options[:autosave]
382
387
 
383
388
  if autosave && record.marked_for_destruction?
384
389
  record.destroy
385
- else
390
+ elsif autosave != false
386
391
  key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
387
- if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key))
388
392
 
393
+ if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
389
394
  unless reflection.through_reflection
390
395
  record[reflection.foreign_key] = key
391
396
  end