activerecord 4.0.13 → 4.1.0.beta1
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 +4 -4
- data/CHANGELOG.md +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -102
@@ -41,8 +41,9 @@ module ActiveRecord
|
|
41
41
|
# task.read_attribute_before_type_cast('id') # => '1'
|
42
42
|
# task.read_attribute('completed_on') # => Sun, 21 Oct 2012
|
43
43
|
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
44
|
+
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
44
45
|
def read_attribute_before_type_cast(attr_name)
|
45
|
-
@attributes[attr_name]
|
46
|
+
@attributes[attr_name.to_s]
|
46
47
|
end
|
47
48
|
|
48
49
|
# Returns a hash of attributes before typecasting and deserialization.
|
@@ -14,24 +14,12 @@ module ActiveRecord
|
|
14
14
|
|
15
15
|
class_attribute :partial_writes, instance_writer: false
|
16
16
|
self.partial_writes = true
|
17
|
-
|
18
|
-
def self.partial_updates=(v); self.partial_writes = v; end
|
19
|
-
def self.partial_updates?; partial_writes?; end
|
20
|
-
def self.partial_updates; partial_writes; end
|
21
|
-
|
22
|
-
ActiveSupport::Deprecation.deprecate_methods(
|
23
|
-
singleton_class,
|
24
|
-
:partial_updates= => :partial_writes=,
|
25
|
-
:partial_updates? => :partial_writes?,
|
26
|
-
:partial_updates => :partial_writes
|
27
|
-
)
|
28
17
|
end
|
29
18
|
|
30
19
|
# Attempts to +save+ the record and clears changed attributes if successful.
|
31
20
|
def save(*)
|
32
21
|
if status = super
|
33
|
-
|
34
|
-
@changed_attributes.clear
|
22
|
+
changes_applied
|
35
23
|
end
|
36
24
|
status
|
37
25
|
end
|
@@ -39,16 +27,14 @@ module ActiveRecord
|
|
39
27
|
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
40
28
|
def save!(*)
|
41
29
|
super.tap do
|
42
|
-
|
43
|
-
@changed_attributes.clear
|
30
|
+
changes_applied
|
44
31
|
end
|
45
32
|
end
|
46
33
|
|
47
34
|
# <tt>reload</tt> the record and clears changed attributes.
|
48
35
|
def reload(*)
|
49
36
|
super.tap do
|
50
|
-
|
51
|
-
@changed_attributes.clear
|
37
|
+
reset_changes
|
52
38
|
end
|
53
39
|
end
|
54
40
|
|
@@ -59,22 +45,22 @@ module ActiveRecord
|
|
59
45
|
|
60
46
|
# The attribute already has an unsaved change.
|
61
47
|
if attribute_changed?(attr)
|
62
|
-
old =
|
63
|
-
|
48
|
+
old = changed_attributes[attr]
|
49
|
+
changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
|
64
50
|
else
|
65
51
|
old = clone_attribute_value(:read_attribute, attr)
|
66
|
-
|
52
|
+
changed_attributes[attr] = old if _field_changed?(attr, old, value)
|
67
53
|
end
|
68
54
|
|
69
55
|
# Carry on.
|
70
56
|
super(attr, value)
|
71
57
|
end
|
72
58
|
|
73
|
-
def
|
59
|
+
def update_record(*)
|
74
60
|
partial_writes? ? super(keys_for_partial_write) : super
|
75
61
|
end
|
76
62
|
|
77
|
-
def
|
63
|
+
def create_record(*)
|
78
64
|
partial_writes? ? super(keys_for_partial_write) : super
|
79
65
|
end
|
80
66
|
|
@@ -37,12 +37,6 @@ module ActiveRecord
|
|
37
37
|
read_attribute_before_type_cast(self.class.primary_key)
|
38
38
|
end
|
39
39
|
|
40
|
-
# Returns the primary key previous value.
|
41
|
-
def id_was
|
42
|
-
sync_with_transaction_state
|
43
|
-
attribute_was(self.class.primary_key)
|
44
|
-
end
|
45
|
-
|
46
40
|
protected
|
47
41
|
|
48
42
|
def attribute_method?(attr_name)
|
@@ -58,7 +52,7 @@ module ActiveRecord
|
|
58
52
|
end
|
59
53
|
end
|
60
54
|
|
61
|
-
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast
|
55
|
+
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
|
62
56
|
|
63
57
|
def dangerous_attribute_method?(method_name)
|
64
58
|
super && !ID_ATTRIBUTE_METHODS.include?(method_name)
|
@@ -1,6 +1,38 @@
|
|
1
|
+
require 'active_support/core_ext/module/method_transplanting'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeMethods
|
3
5
|
module Read
|
6
|
+
ReaderMethodCache = Class.new(AttributeMethodCache) {
|
7
|
+
private
|
8
|
+
# We want to generate the methods via module_eval rather than
|
9
|
+
# define_method, because define_method is slower on dispatch.
|
10
|
+
# Evaluating many similar methods may use more memory as the instruction
|
11
|
+
# sequences are duplicated and cached (in MRI). define_method may
|
12
|
+
# be slower on dispatch, but if you're careful about the closure
|
13
|
+
# created, then define_method will consume much less memory.
|
14
|
+
#
|
15
|
+
# But sometimes the database might return columns with
|
16
|
+
# characters that are not allowed in normal method names (like
|
17
|
+
# 'my_column(omg)'. So to work around this we first define with
|
18
|
+
# the __temp__ identifier, and then use alias method to rename
|
19
|
+
# it to what we want.
|
20
|
+
#
|
21
|
+
# We are also defining a constant to hold the frozen string of
|
22
|
+
# the attribute name. Using a constant means that we do not have
|
23
|
+
# to allocate an object on each call to the attribute method.
|
24
|
+
# Making it frozen means that it doesn't get duped when used to
|
25
|
+
# key the @attributes_cache in read_attribute.
|
26
|
+
def method_body(method_name, const_name)
|
27
|
+
<<-EOMETHOD
|
28
|
+
def #{method_name}
|
29
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
|
30
|
+
read_attribute(name) { |n| missing_attribute(n, caller) }
|
31
|
+
end
|
32
|
+
EOMETHOD
|
33
|
+
end
|
34
|
+
}.new
|
35
|
+
|
4
36
|
extend ActiveSupport::Concern
|
5
37
|
|
6
38
|
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
|
@@ -32,35 +64,30 @@ module ActiveRecord
|
|
32
64
|
|
33
65
|
protected
|
34
66
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
58
|
-
def __temp__#{safe_name}
|
59
|
-
read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
|
67
|
+
if Module.methods_transplantable?
|
68
|
+
def define_method_attribute(name)
|
69
|
+
method = ReaderMethodCache[name]
|
70
|
+
generated_attribute_methods.module_eval { define_method name, method }
|
71
|
+
end
|
72
|
+
else
|
73
|
+
def define_method_attribute(name)
|
74
|
+
safe_name = name.unpack('h*').first
|
75
|
+
temp_method = "__temp__#{safe_name}"
|
76
|
+
|
77
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
78
|
+
|
79
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
80
|
+
def #{temp_method}
|
81
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
82
|
+
read_attribute(name) { |n| missing_attribute(n, caller) }
|
83
|
+
end
|
84
|
+
STR
|
85
|
+
|
86
|
+
generated_attribute_methods.module_eval do
|
87
|
+
alias_method name, temp_method
|
88
|
+
undef_method temp_method
|
60
89
|
end
|
61
|
-
|
62
|
-
undef_method :__temp__#{safe_name}
|
63
|
-
STR
|
90
|
+
end
|
64
91
|
end
|
65
92
|
|
66
93
|
private
|
@@ -24,11 +24,13 @@ module ActiveRecord
|
|
24
24
|
# serialized object must be of that class on retrieval or
|
25
25
|
# <tt>SerializationTypeMismatch</tt> will be raised.
|
26
26
|
#
|
27
|
+
# A notable side effect of serialized attributes is that the model will
|
28
|
+
# be updated on every save, even if it is not dirty.
|
29
|
+
#
|
27
30
|
# ==== Parameters
|
28
31
|
#
|
29
32
|
# * +attr_name+ - The field name that should be serialized.
|
30
|
-
# * +
|
31
|
-
# or a class name that the object type should be equal to.
|
33
|
+
# * +class_name+ - Optional, class name that the object type should be equal to.
|
32
34
|
#
|
33
35
|
# ==== Example
|
34
36
|
#
|
@@ -36,23 +38,13 @@ module ActiveRecord
|
|
36
38
|
# class User < ActiveRecord::Base
|
37
39
|
# serialize :preferences
|
38
40
|
# end
|
39
|
-
|
40
|
-
# # Serialize preferences using JSON as coder.
|
41
|
-
# class User < ActiveRecord::Base
|
42
|
-
# serialize :preferences, JSON
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# # Serialize preferences as Hash using YAML coder.
|
46
|
-
# class User < ActiveRecord::Base
|
47
|
-
# serialize :preferences, Hash
|
48
|
-
# end
|
49
|
-
def serialize(attr_name, class_name_or_coder = Object)
|
41
|
+
def serialize(attr_name, class_name = Object)
|
50
42
|
include Behavior
|
51
43
|
|
52
|
-
coder = if [:load, :dump].all? { |x|
|
53
|
-
|
44
|
+
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
45
|
+
class_name
|
54
46
|
else
|
55
|
-
Coders::YAMLColumn.new(
|
47
|
+
Coders::YAMLColumn.new(class_name)
|
56
48
|
end
|
57
49
|
|
58
50
|
# merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
|
@@ -61,13 +53,6 @@ module ActiveRecord
|
|
61
53
|
end
|
62
54
|
end
|
63
55
|
|
64
|
-
# *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
|
65
|
-
def serialized_attributes
|
66
|
-
message = "Instance level serialized_attributes method is deprecated, please use class level method."
|
67
|
-
ActiveSupport::Deprecation.warn message
|
68
|
-
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
|
69
|
-
end
|
70
|
-
|
71
56
|
class Type # :nodoc:
|
72
57
|
def initialize(column)
|
73
58
|
@column = column
|
@@ -84,6 +69,10 @@ module ActiveRecord
|
|
84
69
|
def type
|
85
70
|
@column.type
|
86
71
|
end
|
72
|
+
|
73
|
+
def accessor
|
74
|
+
ActiveRecord::Store::IndifferentHashAccessor
|
75
|
+
end
|
87
76
|
end
|
88
77
|
|
89
78
|
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
|
@@ -167,16 +156,6 @@ module ActiveRecord
|
|
167
156
|
super
|
168
157
|
end
|
169
158
|
end
|
170
|
-
|
171
|
-
def attributes_for_coder
|
172
|
-
attribute_names.each_with_object({}) do |name, attrs|
|
173
|
-
attrs[name] = if self.class.serialized_attributes.include?(name)
|
174
|
-
@attributes[name].serialized_value
|
175
|
-
else
|
176
|
-
read_attribute(name)
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
159
|
end
|
181
160
|
end
|
182
161
|
end
|
@@ -34,7 +34,7 @@ module ActiveRecord
|
|
34
34
|
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
35
35
|
method_body, line = <<-EOV, __LINE__ + 1
|
36
36
|
def #{attr_name}=(time)
|
37
|
-
time_with_zone =
|
37
|
+
time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
|
38
38
|
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
39
39
|
write_attribute(:#{attr_name}, time)
|
40
40
|
#{attr_name}_will_change! if previous_time != time_with_zone
|
@@ -54,18 +54,6 @@ module ActiveRecord
|
|
54
54
|
(:datetime == column.type || :timestamp == column.type)
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def convert_value_to_time_zone(value)
|
61
|
-
if value.is_a?(Array)
|
62
|
-
value.map { |v| convert_value_to_time_zone(v) }
|
63
|
-
elsif value.respond_to?(:in_time_zone)
|
64
|
-
value.in_time_zone
|
65
|
-
else
|
66
|
-
nil
|
67
|
-
end
|
68
|
-
end
|
69
57
|
end
|
70
58
|
end
|
71
59
|
end
|
@@ -1,6 +1,21 @@
|
|
1
|
+
require 'active_support/core_ext/module/method_transplanting'
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module AttributeMethods
|
3
5
|
module Write
|
6
|
+
WriterMethodCache = Class.new(AttributeMethodCache) {
|
7
|
+
private
|
8
|
+
|
9
|
+
def method_body(method_name, const_name)
|
10
|
+
<<-EOMETHOD
|
11
|
+
def #{method_name}(value)
|
12
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
|
13
|
+
write_attribute(name, value)
|
14
|
+
end
|
15
|
+
EOMETHOD
|
16
|
+
end
|
17
|
+
}.new
|
18
|
+
|
4
19
|
extend ActiveSupport::Concern
|
5
20
|
|
6
21
|
included do
|
@@ -10,19 +25,29 @@ module ActiveRecord
|
|
10
25
|
module ClassMethods
|
11
26
|
protected
|
12
27
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
28
|
+
if Module.methods_transplantable?
|
29
|
+
# See define_method_attribute in read.rb for an explanation of
|
30
|
+
# this code.
|
31
|
+
def define_method_attribute=(name)
|
32
|
+
method = WriterMethodCache[name]
|
33
|
+
generated_attribute_methods.module_eval {
|
34
|
+
define_method "#{name}=", method
|
35
|
+
}
|
36
|
+
end
|
37
|
+
else
|
38
|
+
def define_method_attribute=(name)
|
39
|
+
safe_name = name.unpack('h*').first
|
40
|
+
ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
18
41
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
42
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
43
|
+
def __temp__#{safe_name}=(value)
|
44
|
+
name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
45
|
+
write_attribute(name, value)
|
46
|
+
end
|
47
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
48
|
+
undef_method :__temp__#{safe_name}=
|
49
|
+
STR
|
50
|
+
end
|
26
51
|
end
|
27
52
|
end
|
28
53
|
|
@@ -127,87 +127,87 @@ module ActiveRecord
|
|
127
127
|
extend ActiveSupport::Concern
|
128
128
|
|
129
129
|
module AssociationBuilderExtension #:nodoc:
|
130
|
-
def build
|
130
|
+
def self.build(model, reflection)
|
131
131
|
model.send(:add_autosave_association_callbacks, reflection)
|
132
|
-
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.valid_options
|
135
|
+
[ :autosave ]
|
133
136
|
end
|
134
137
|
end
|
135
138
|
|
136
139
|
included do
|
137
|
-
Associations::Builder::Association.
|
138
|
-
self.valid_options << :autosave
|
139
|
-
include AssociationBuilderExtension
|
140
|
-
end
|
140
|
+
Associations::Builder::Association.extensions << AssociationBuilderExtension
|
141
141
|
end
|
142
142
|
|
143
143
|
module ClassMethods
|
144
144
|
private
|
145
145
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
146
|
+
def define_non_cyclic_method(name, &block)
|
147
|
+
define_method(name) do |*args|
|
148
|
+
result = true; @_already_called ||= {}
|
149
|
+
# Loop prevention for validation of associations
|
150
|
+
unless @_already_called[name]
|
151
|
+
begin
|
152
|
+
@_already_called[name]=true
|
153
|
+
result = instance_eval(&block)
|
154
|
+
ensure
|
155
|
+
@_already_called[name]=false
|
156
|
+
end
|
156
157
|
end
|
157
|
-
end
|
158
158
|
|
159
|
-
|
159
|
+
result
|
160
|
+
end
|
160
161
|
end
|
161
|
-
end
|
162
162
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
163
|
+
# Adds validation and save callbacks for the association as specified by
|
164
|
+
# the +reflection+.
|
165
|
+
#
|
166
|
+
# For performance reasons, we don't check whether to validate at runtime.
|
167
|
+
# However the validation and callback methods are lazy and those methods
|
168
|
+
# get created when they are invoked for the very first time. However,
|
169
|
+
# this can change, for instance, when using nested attributes, which is
|
170
|
+
# called _after_ the association has been defined. Since we don't want
|
171
|
+
# the callbacks to get defined multiple times, there are guards that
|
172
|
+
# check if the save or validation methods have already been defined
|
173
|
+
# before actually defining them.
|
174
|
+
def add_autosave_association_callbacks(reflection)
|
175
|
+
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
176
|
+
validation_method = :"validate_associated_records_for_#{reflection.name}"
|
177
|
+
collection = reflection.collection?
|
178
|
+
|
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
|
202
203
|
end
|
203
|
-
end
|
204
204
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
205
|
+
if reflection.validate? && !method_defined?(validation_method)
|
206
|
+
method = (collection ? :validate_collection_association : :validate_single_association)
|
207
|
+
define_non_cyclic_method(validation_method) { send(method, reflection) }
|
208
|
+
validate validation_method
|
209
|
+
end
|
209
210
|
end
|
210
|
-
end
|
211
211
|
end
|
212
212
|
|
213
213
|
# Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
|
@@ -254,179 +254,179 @@ module ActiveRecord
|
|
254
254
|
|
255
255
|
private
|
256
256
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
257
|
+
# Returns the record for an association collection that should be validated
|
258
|
+
# or saved. If +autosave+ is +false+ only new records will be returned,
|
259
|
+
# unless the parent is/was a new record itself.
|
260
|
+
def associated_records_to_validate_or_save(association, new_record, autosave)
|
261
|
+
if new_record
|
262
|
+
association && association.target
|
263
|
+
elsif autosave
|
264
|
+
association.target.find_all { |record| record.changed_for_autosave? }
|
265
|
+
else
|
266
|
+
association.target.find_all { |record| record.new_record? }
|
267
|
+
end
|
267
268
|
end
|
268
|
-
end
|
269
269
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
270
|
+
# go through nested autosave associations that are loaded in memory (without loading
|
271
|
+
# any new ones), and return true if is changed for autosave
|
272
|
+
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? }
|
276
|
+
end
|
276
277
|
end
|
277
|
-
end
|
278
278
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
279
|
+
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
|
280
|
+
# turned on for the association.
|
281
|
+
def validate_single_association(reflection)
|
282
|
+
association = association_instance_get(reflection.name)
|
283
|
+
record = association && association.reader
|
284
|
+
association_valid?(reflection, record) if record
|
285
|
+
end
|
286
286
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
287
|
+
# Validate the associated records if <tt>:validate</tt> or
|
288
|
+
# <tt>:autosave</tt> is turned on for the association specified by
|
289
|
+
# +reflection+.
|
290
|
+
def validate_collection_association(reflection)
|
291
|
+
if association = association_instance_get(reflection.name)
|
292
|
+
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
293
|
+
records.each { |record| association_valid?(reflection, record) }
|
294
|
+
end
|
294
295
|
end
|
295
296
|
end
|
296
|
-
end
|
297
297
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
298
|
+
# Returns whether or not the association is valid and applies any errors to
|
299
|
+
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
300
|
+
# enabled records if they're marked_for_destruction? or destroyed.
|
301
|
+
def association_valid?(reflection, record)
|
302
|
+
return true if record.destroyed? || record.marked_for_destruction?
|
303
|
+
|
304
|
+
unless valid = record.valid?
|
305
|
+
if reflection.options[:autosave]
|
306
|
+
record.errors.each do |attribute, message|
|
307
|
+
attribute = "#{reflection.name}.#{attribute}"
|
308
|
+
errors[attribute] << message
|
309
|
+
errors[attribute].uniq!
|
310
|
+
end
|
311
|
+
else
|
312
|
+
errors.add(reflection.name)
|
310
313
|
end
|
311
|
-
else
|
312
|
-
errors.add(reflection.name)
|
313
314
|
end
|
315
|
+
valid
|
314
316
|
end
|
315
|
-
valid
|
316
|
-
end
|
317
317
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
318
|
+
# Is used as a before_save callback to check while saving a collection
|
319
|
+
# association whether or not the parent was a new record before saving.
|
320
|
+
def before_save_collection_association
|
321
|
+
@new_record_before_save = new_record?
|
322
|
+
true
|
323
|
+
end
|
324
324
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
325
|
+
# Saves any new associated records, or all loaded autosave associations if
|
326
|
+
# <tt>:autosave</tt> is enabled on the association.
|
327
|
+
#
|
328
|
+
# In addition, it destroys all children that were marked for destruction
|
329
|
+
# with mark_for_destruction.
|
330
|
+
#
|
331
|
+
# This all happens inside a transaction, _if_ the Transactions module is included into
|
332
|
+
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
333
|
+
def save_collection_association(reflection)
|
334
|
+
if association = association_instance_get(reflection.name)
|
335
|
+
autosave = reflection.options[:autosave]
|
336
|
+
|
337
|
+
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
338
|
+
|
339
|
+
if autosave
|
340
|
+
records_to_destroy = records.select(&:marked_for_destruction?)
|
341
|
+
records_to_destroy.each { |record| association.destroy(record) }
|
342
|
+
records -= records_to_destroy
|
343
|
+
end
|
344
344
|
|
345
|
-
|
346
|
-
|
345
|
+
records.each do |record|
|
346
|
+
next if record.destroyed?
|
347
347
|
|
348
|
-
|
348
|
+
saved = true
|
349
349
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
350
|
+
if autosave != false && (@new_record_before_save || record.new_record?)
|
351
|
+
if autosave
|
352
|
+
saved = association.insert_record(record, false)
|
353
|
+
else
|
354
|
+
association.insert_record(record) unless reflection.nested?
|
355
|
+
end
|
356
|
+
elsif autosave
|
357
|
+
saved = record.save(:validate => false)
|
355
358
|
end
|
356
|
-
elsif autosave
|
357
|
-
saved = record.save(:validate => false)
|
358
|
-
end
|
359
359
|
|
360
|
-
|
360
|
+
raise ActiveRecord::Rollback unless saved
|
361
|
+
end
|
361
362
|
end
|
362
|
-
end
|
363
363
|
|
364
|
-
|
365
|
-
|
364
|
+
# reconstruct the scope now that we know the owner's id
|
365
|
+
association.reset_scope if association.respond_to?(:reset_scope)
|
366
|
+
end
|
366
367
|
end
|
367
|
-
end
|
368
368
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
if autosave && record.marked_for_destruction?
|
384
|
-
record.destroy
|
385
|
-
elsif autosave != false
|
386
|
-
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
387
|
-
|
388
|
-
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
|
389
|
-
unless reflection.through_reflection
|
390
|
-
record[reflection.foreign_key] = key
|
391
|
-
end
|
369
|
+
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
|
370
|
+
# on the association.
|
371
|
+
#
|
372
|
+
# In addition, it will destroy the association if it was marked for
|
373
|
+
# destruction with mark_for_destruction.
|
374
|
+
#
|
375
|
+
# This all happens inside a transaction, _if_ the Transactions module is included into
|
376
|
+
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
377
|
+
def save_has_one_association(reflection)
|
378
|
+
association = association_instance_get(reflection.name)
|
379
|
+
record = association && association.load_target
|
380
|
+
if record && !record.destroyed?
|
381
|
+
autosave = reflection.options[:autosave]
|
392
382
|
|
393
|
-
|
394
|
-
|
395
|
-
|
383
|
+
if autosave && record.marked_for_destruction?
|
384
|
+
record.destroy
|
385
|
+
else
|
386
|
+
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
387
|
+
if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key))
|
388
|
+
|
389
|
+
unless reflection.through_reflection
|
390
|
+
record[reflection.foreign_key] = key
|
391
|
+
end
|
392
|
+
|
393
|
+
saved = record.save(:validate => !autosave)
|
394
|
+
raise ActiveRecord::Rollback if !saved && autosave
|
395
|
+
saved
|
396
|
+
end
|
396
397
|
end
|
397
398
|
end
|
398
399
|
end
|
399
|
-
end
|
400
400
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
401
|
+
# If the record is new or it has changed, returns true.
|
402
|
+
def record_changed?(reflection, record, key)
|
403
|
+
record.new_record? || record[reflection.foreign_key] != key || record.attribute_changed?(reflection.foreign_key)
|
404
|
+
end
|
405
405
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
406
|
+
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
407
|
+
#
|
408
|
+
# In addition, it will destroy the association if it was marked for destruction.
|
409
|
+
def save_belongs_to_association(reflection)
|
410
|
+
association = association_instance_get(reflection.name)
|
411
|
+
record = association && association.load_target
|
412
|
+
if record && !record.destroyed?
|
413
|
+
autosave = reflection.options[:autosave]
|
414
|
+
|
415
|
+
if autosave && record.marked_for_destruction?
|
416
|
+
self[reflection.foreign_key] = nil
|
417
|
+
record.destroy
|
418
|
+
elsif autosave != false
|
419
|
+
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
420
|
+
|
421
|
+
if association.updated?
|
422
|
+
association_id = record.send(reflection.options[:primary_key] || :id)
|
423
|
+
self[reflection.foreign_key] = association_id
|
424
|
+
association.loaded!
|
425
|
+
end
|
426
426
|
|
427
|
-
|
427
|
+
saved if autosave
|
428
|
+
end
|
428
429
|
end
|
429
430
|
end
|
430
|
-
end
|
431
431
|
end
|
432
432
|
end
|