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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +312 -0
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations.rb +24 -3
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +10 -4
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +5 -5
- data/lib/active_record/associations/collection_proxy.rb +4 -0
- data/lib/active_record/associations/has_many_association.rb +6 -5
- data/lib/active_record/associations/has_many_through_association.rb +6 -2
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +14 -35
- data/lib/active_record/associations/preloader/association.rb +26 -5
- data/lib/active_record/associations/singular_association.rb +3 -3
- data/lib/active_record/associations/through_association.rb +4 -2
- data/lib/active_record/attribute_methods.rb +2 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/serialization.rb +24 -5
- data/lib/active_record/attribute_methods/write.rb +22 -14
- data/lib/active_record/autosave_association.rb +40 -35
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +10 -13
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +10 -5
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +11 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +6 -3
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +3 -0
- data/lib/active_record/counter_cache.rb +2 -3
- data/lib/active_record/fixtures.rb +1 -1
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/nested_attributes.rb +2 -2
- data/lib/active_record/null_relation.rb +19 -5
- data/lib/active_record/persistence.rb +8 -8
- data/lib/active_record/railties/databases.rake +3 -2
- data/lib/active_record/reflection.rb +45 -13
- data/lib/active_record/relation.rb +7 -6
- data/lib/active_record/relation/calculations.rb +10 -2
- data/lib/active_record/relation/delegation.rb +2 -2
- data/lib/active_record/relation/finder_methods.rb +1 -1
- data/lib/active_record/relation/merger.rb +10 -2
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_methods.rb +2 -2
- data/lib/active_record/scoping/default.rb +3 -3
- data/lib/active_record/store.rb +14 -5
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +1 -1
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +2 -2
- 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
|
-
|
137
|
+
_create_record(attributes, &block)
|
138
138
|
end
|
139
139
|
|
140
140
|
def create!(attributes = {}, &block)
|
141
|
-
|
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
|
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|
|
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|
|
@@ -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.
|
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
|
-
|
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.
|
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
|
-
|
115
|
+
association.flat_map { |parent, child|
|
116
|
+
loaders = preloaders_for_one parent, records, scope
|
116
117
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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 ||=
|
61
|
-
|
62
|
-
|
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
|
-
|
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.
|
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
|
-
|
21
|
+
_create_record(attributes, &block)
|
22
22
|
end
|
23
23
|
|
24
24
|
def create!(attributes = {}, &block)
|
25
|
-
|
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
|
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
|
-
|
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
|
82
|
+
def _update_record(*)
|
83
83
|
partial_writes? ? super(keys_for_partial_write) : super
|
84
84
|
end
|
85
85
|
|
86
|
-
def
|
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
|
-
# * +
|
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
|
-
|
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|
|
45
|
-
|
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(
|
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] =
|
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.
|
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
|
111
|
-
# post.comments.
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
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.
|
274
|
-
|
275
|
-
|
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
|
-
|
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
|