activerecord 3.2.22.5 → 4.0.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 +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -4,37 +4,19 @@ module ActiveRecord
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
# Returns a hash of all the attributes that have been specified for
|
8
|
-
# keys and their class restriction as values.
|
9
|
-
class_attribute :serialized_attributes
|
7
|
+
# Returns a hash of all the attributes that have been specified for
|
8
|
+
# serialization as keys and their class restriction as values.
|
9
|
+
class_attribute :serialized_attributes, instance_accessor: false
|
10
10
|
self.serialized_attributes = {}
|
11
11
|
end
|
12
12
|
|
13
|
-
class Attribute < Struct.new(:coder, :value, :state)
|
14
|
-
def unserialized_value
|
15
|
-
state == :serialized ? unserialize : value
|
16
|
-
end
|
17
|
-
|
18
|
-
def serialized_value
|
19
|
-
state == :unserialized ? serialize : value
|
20
|
-
end
|
21
|
-
|
22
|
-
def unserialize
|
23
|
-
self.state = :unserialized
|
24
|
-
self.value = coder.load(value)
|
25
|
-
end
|
26
|
-
|
27
|
-
def serialize
|
28
|
-
self.state = :serialized
|
29
|
-
self.value = coder.dump(value)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
13
|
module ClassMethods
|
34
|
-
# If you have an attribute that needs to be saved to the database as an
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
14
|
+
# If you have an attribute that needs to be saved to the database as an
|
15
|
+
# object, and retrieved as the same object, then specify the name of that
|
16
|
+
# attribute using this method and it will be handled automatically. The
|
17
|
+
# serialization is done through YAML. If +class_name+ is specified, the
|
18
|
+
# serialized object must be of that class on retrieval or
|
19
|
+
# <tt>SerializationTypeMismatch</tt> will be raised.
|
38
20
|
#
|
39
21
|
# ==== Parameters
|
40
22
|
#
|
@@ -42,11 +24,14 @@ module ActiveRecord
|
|
42
24
|
# * +class_name+ - Optional, class name that the object type should be equal to.
|
43
25
|
#
|
44
26
|
# ==== Example
|
45
|
-
#
|
27
|
+
#
|
28
|
+
# # Serialize a preferences attribute.
|
46
29
|
# class User < ActiveRecord::Base
|
47
30
|
# serialize :preferences
|
48
31
|
# end
|
49
32
|
def serialize(attr_name, class_name = Object)
|
33
|
+
include Behavior
|
34
|
+
|
50
35
|
coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
|
51
36
|
class_name
|
52
37
|
else
|
@@ -57,61 +42,107 @@ module ActiveRecord
|
|
57
42
|
# has its own hash of own serialized attributes
|
58
43
|
self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
|
59
44
|
end
|
45
|
+
end
|
60
46
|
|
61
|
-
|
62
|
-
|
63
|
-
|
47
|
+
def serialized_attributes
|
48
|
+
message = "Instance level serialized_attributes method is deprecated, please use class level method."
|
49
|
+
ActiveSupport::Deprecation.warn message
|
50
|
+
defined?(@serialized_attributes) ? @serialized_attributes : self.class.serialized_attributes
|
51
|
+
end
|
64
52
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
53
|
+
class Type # :nodoc:
|
54
|
+
def initialize(column)
|
55
|
+
@column = column
|
56
|
+
end
|
70
57
|
|
71
|
-
|
58
|
+
def type_cast(value)
|
59
|
+
value.unserialized_value
|
72
60
|
end
|
73
61
|
|
74
|
-
|
62
|
+
def type
|
63
|
+
@column.type
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
|
68
|
+
def unserialized_value
|
69
|
+
state == :serialized ? unserialize : value
|
70
|
+
end
|
75
71
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
72
|
+
def serialized_value
|
73
|
+
state == :unserialized ? serialize : value
|
74
|
+
end
|
75
|
+
|
76
|
+
def unserialize
|
77
|
+
self.state = :unserialized
|
78
|
+
self.value = coder.load(value)
|
79
|
+
end
|
80
|
+
|
81
|
+
def serialize
|
82
|
+
self.state = :serialized
|
83
|
+
self.value = coder.dump(value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# This is only added to the model when serialize is called, which
|
88
|
+
# ensures we do not make things slower when serialization is not used.
|
89
|
+
module Behavior #:nodoc:
|
90
|
+
extend ActiveSupport::Concern
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
def initialize_attributes(attributes, options = {})
|
94
|
+
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
|
95
|
+
super(attributes, options)
|
96
|
+
|
97
|
+
serialized_attributes.each do |key, coder|
|
98
|
+
if attributes.key?(key)
|
99
|
+
attributes[key] = Attribute.new(coder, attributes[key], serialized)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
attributes
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def type_cast_attribute_for_write(column, value)
|
108
|
+
if column && coder = self.class.serialized_attributes[column.name]
|
109
|
+
Attribute.new(coder, value, :unserialized)
|
79
110
|
else
|
80
111
|
super
|
81
112
|
end
|
82
113
|
end
|
83
|
-
end
|
84
114
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
115
|
+
def _field_changed?(attr, old, value)
|
116
|
+
if self.class.serialized_attributes.include?(attr)
|
117
|
+
old != value
|
118
|
+
else
|
119
|
+
super
|
120
|
+
end
|
90
121
|
end
|
91
|
-
end
|
92
122
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
123
|
+
def read_attribute_before_type_cast(attr_name)
|
124
|
+
if self.class.serialized_attributes.include?(attr_name)
|
125
|
+
super.unserialized_value
|
126
|
+
else
|
127
|
+
super
|
128
|
+
end
|
98
129
|
end
|
99
|
-
end
|
100
130
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
131
|
+
def attributes_before_type_cast
|
132
|
+
super.dup.tap do |attributes|
|
133
|
+
self.class.serialized_attributes.each_key do |key|
|
134
|
+
if attributes.key?(key)
|
135
|
+
attributes[key] = attributes[key].unserialized_value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
106
139
|
end
|
107
|
-
end
|
108
140
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
141
|
+
def typecasted_attribute_value(name)
|
142
|
+
if self.class.serialized_attributes.include?(name)
|
143
|
+
@attributes[name].serialized_value
|
144
|
+
else
|
145
|
+
super
|
115
146
|
end
|
116
147
|
end
|
117
148
|
end
|
@@ -1,64 +1,58 @@
|
|
1
|
-
require 'active_support/core_ext/class/attribute'
|
2
|
-
require 'active_support/core_ext/object/inclusion'
|
3
|
-
|
4
1
|
module ActiveRecord
|
5
2
|
module AttributeMethods
|
6
3
|
module TimeZoneConversion
|
4
|
+
class Type # :nodoc:
|
5
|
+
def initialize(column)
|
6
|
+
@column = column
|
7
|
+
end
|
8
|
+
|
9
|
+
def type_cast(value)
|
10
|
+
value = @column.type_cast(value)
|
11
|
+
value.acts_like?(:time) ? value.in_time_zone : value
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
@column.type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
7
19
|
extend ActiveSupport::Concern
|
8
20
|
|
9
21
|
included do
|
10
|
-
|
22
|
+
mattr_accessor :time_zone_aware_attributes, instance_writer: false
|
11
23
|
self.time_zone_aware_attributes = false
|
12
24
|
|
13
|
-
class_attribute :skip_time_zone_conversion_for_attributes, :
|
25
|
+
class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false
|
14
26
|
self.skip_time_zone_conversion_for_attributes = []
|
15
27
|
end
|
16
28
|
|
17
29
|
module ClassMethods
|
18
30
|
protected
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
36
|
-
def define_method_attribute=(attr_name)
|
37
|
-
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
38
|
-
method_body, line = <<-EOV, __LINE__ + 1
|
39
|
-
def #{attr_name}=(original_time)
|
40
|
-
original_time = nil if original_time.blank?
|
41
|
-
time = original_time
|
42
|
-
unless time.acts_like?(:time)
|
43
|
-
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
44
|
-
end
|
45
|
-
time = time.in_time_zone rescue nil if time
|
46
|
-
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
47
|
-
write_attribute(:#{attr_name}, original_time)
|
48
|
-
#{attr_name}_will_change! if previous_time != time
|
49
|
-
@attributes_cache["#{attr_name}"] = time
|
50
|
-
end
|
51
|
-
EOV
|
52
|
-
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
53
|
-
else
|
54
|
-
super
|
55
|
-
end
|
31
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
32
|
+
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
33
|
+
def define_method_attribute=(attr_name)
|
34
|
+
if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
|
35
|
+
method_body, line = <<-EOV, __LINE__ + 1
|
36
|
+
def #{attr_name}=(time)
|
37
|
+
time_with_zone = time.respond_to?(:in_time_zone) ? time.in_time_zone : nil
|
38
|
+
previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name})
|
39
|
+
write_attribute(:#{attr_name}, time)
|
40
|
+
#{attr_name}_will_change! if previous_time != time_with_zone
|
41
|
+
@attributes_cache["#{attr_name}"] = time_with_zone
|
42
|
+
end
|
43
|
+
EOV
|
44
|
+
generated_attribute_methods.module_eval(method_body, __FILE__, line)
|
45
|
+
else
|
46
|
+
super
|
56
47
|
end
|
48
|
+
end
|
57
49
|
|
58
50
|
private
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
def create_time_zone_conversion_attribute?(name, column)
|
52
|
+
time_zone_aware_attributes &&
|
53
|
+
!self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) &&
|
54
|
+
[:datetime, :timestamp].include?(column.type)
|
55
|
+
end
|
62
56
|
end
|
63
57
|
end
|
64
58
|
end
|
@@ -9,62 +9,55 @@ module ActiveRecord
|
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
protected
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
|
13
|
+
# See define_method_attribute in read.rb for an explanation of
|
14
|
+
# this code.
|
15
|
+
def define_method_attribute=(name)
|
16
|
+
safe_name = name.unpack('h*').first
|
17
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
18
|
+
def __temp__#{safe_name}=(value)
|
19
|
+
write_attribute(AttrNames::ATTR_#{safe_name}, value)
|
19
20
|
end
|
20
|
-
|
21
|
+
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
22
|
+
undef_method :__temp__#{safe_name}=
|
23
|
+
STR
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
|
-
# Updates the attribute identified by <tt>attr_name</tt> with the
|
24
|
-
# for fixnum and float columns are
|
27
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the
|
28
|
+
# specified +value+. Empty strings for fixnum and float columns are
|
29
|
+
# turned into +nil+.
|
25
30
|
def write_attribute(attr_name, value)
|
26
31
|
attr_name = attr_name.to_s
|
27
32
|
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
28
33
|
@attributes_cache.delete(attr_name)
|
29
34
|
column = column_for_attribute(attr_name)
|
30
35
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
)
|
36
|
+
# If we're dealing with a binary column, write the data to the cache
|
37
|
+
# so we don't attempt to typecast multiple times.
|
38
|
+
if column && column.binary?
|
39
|
+
@attributes_cache[attr_name] = value
|
36
40
|
end
|
37
41
|
|
38
|
-
@attributes
|
42
|
+
if column || @attributes.has_key?(attr_name)
|
43
|
+
@attributes[attr_name] = type_cast_attribute_for_write(column, value)
|
44
|
+
else
|
45
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
46
|
+
end
|
39
47
|
end
|
40
48
|
alias_method :raw_write_attribute, :write_attribute
|
41
49
|
|
42
50
|
private
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
51
|
+
# Handle *= for method_missing.
|
52
|
+
def attribute=(attribute_name, value)
|
53
|
+
write_attribute(attribute_name, value)
|
54
|
+
end
|
47
55
|
|
48
|
-
|
49
|
-
|
50
|
-
convert_number_column_value(value)
|
51
|
-
else
|
52
|
-
value
|
53
|
-
end
|
54
|
-
end
|
56
|
+
def type_cast_attribute_for_write(column, value)
|
57
|
+
return value unless column
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
when FalseClass
|
59
|
-
0
|
60
|
-
when TrueClass
|
61
|
-
1
|
62
|
-
when String
|
63
|
-
value.presence
|
64
|
-
else
|
65
|
-
value
|
66
|
-
end
|
67
|
-
end
|
59
|
+
column.type_cast_for_write value
|
60
|
+
end
|
68
61
|
end
|
69
62
|
end
|
70
63
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
# = Active Record Autosave Association
|
5
3
|
#
|
@@ -18,7 +16,7 @@ module ActiveRecord
|
|
18
16
|
# Note that it also means that associations marked for destruction won't
|
19
17
|
# be destroyed directly. They will however still be marked for destruction.
|
20
18
|
#
|
21
|
-
# Note that <tt
|
19
|
+
# Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
|
22
20
|
# When the <tt>:autosave</tt> option is not present new associations are saved.
|
23
21
|
#
|
24
22
|
# == Validation
|
@@ -30,16 +28,14 @@ module ActiveRecord
|
|
30
28
|
# Association with autosave option defines several callbacks on your
|
31
29
|
# model (before_save, after_create, after_update). Please note that
|
32
30
|
# callbacks are executed in the order they were defined in
|
33
|
-
# model. You should avoid
|
31
|
+
# model. You should avoid modifying the association content, before
|
34
32
|
# autosave callbacks are executed. Placing your callbacks after
|
35
33
|
# associations is usually a good practice.
|
36
34
|
#
|
37
|
-
# == Examples
|
38
|
-
#
|
39
35
|
# === One-to-one Example
|
40
36
|
#
|
41
37
|
# class Post
|
42
|
-
# has_one :author, :
|
38
|
+
# has_one :author, autosave: true
|
43
39
|
# end
|
44
40
|
#
|
45
41
|
# Saving changes to the parent and its associated model can now be performed
|
@@ -80,29 +76,30 @@ module ActiveRecord
|
|
80
76
|
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
|
81
77
|
#
|
82
78
|
# class Post
|
83
|
-
# has_many :comments # :autosave option is
|
79
|
+
# has_many :comments # :autosave option is not declared
|
84
80
|
# end
|
85
81
|
#
|
86
|
-
# post = Post.new(:
|
87
|
-
# post.comments.build(:
|
82
|
+
# post = Post.new(title: 'ruby rocks')
|
83
|
+
# post.comments.build(body: 'hello world')
|
88
84
|
# post.save # => saves both post and comment
|
89
85
|
#
|
90
|
-
# post = Post.create(:
|
91
|
-
# post.comments.build(:
|
86
|
+
# post = Post.create(title: 'ruby rocks')
|
87
|
+
# post.comments.build(body: 'hello world')
|
92
88
|
# post.save # => saves both post and comment
|
93
89
|
#
|
94
|
-
# post = Post.create(:
|
95
|
-
# post.comments.create(:
|
90
|
+
# post = Post.create(title: 'ruby rocks')
|
91
|
+
# post.comments.create(body: 'hello world')
|
96
92
|
# post.save # => saves both post and comment
|
97
93
|
#
|
98
|
-
# When <tt>:autosave</tt> is true all children
|
94
|
+
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
|
95
|
+
# are new records or not:
|
99
96
|
#
|
100
97
|
# class Post
|
101
|
-
# has_many :comments, :
|
98
|
+
# has_many :comments, autosave: true
|
102
99
|
# end
|
103
100
|
#
|
104
|
-
# post = Post.create(:
|
105
|
-
# post.comments.create(:
|
101
|
+
# post = Post.create(title: 'ruby rocks')
|
102
|
+
# post.comments.create(body: 'hello world')
|
106
103
|
# post.comments[0].body = 'hi everyone'
|
107
104
|
# post.save # => saves both post and comment, with 'hi everyone' as body
|
108
105
|
#
|
@@ -128,23 +125,17 @@ module ActiveRecord
|
|
128
125
|
module AutosaveAssociation
|
129
126
|
extend ActiveSupport::Concern
|
130
127
|
|
131
|
-
ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
|
132
|
-
|
133
128
|
module AssociationBuilderExtension #:nodoc:
|
134
|
-
def self.included(base)
|
135
|
-
base.valid_options << :autosave
|
136
|
-
end
|
137
|
-
|
138
129
|
def build
|
139
|
-
reflection = super
|
140
130
|
model.send(:add_autosave_association_callbacks, reflection)
|
141
|
-
|
131
|
+
super
|
142
132
|
end
|
143
133
|
end
|
144
134
|
|
145
135
|
included do
|
146
|
-
|
147
|
-
|
136
|
+
Associations::Builder::Association.class_eval do
|
137
|
+
self.valid_options << :autosave
|
138
|
+
include AssociationBuilderExtension
|
148
139
|
end
|
149
140
|
end
|
150
141
|
|
@@ -192,23 +183,21 @@ module ActiveRecord
|
|
192
183
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
193
184
|
after_create save_method
|
194
185
|
after_update save_method
|
186
|
+
elsif reflection.macro == :has_one
|
187
|
+
define_method(save_method) { save_has_one_association(reflection) }
|
188
|
+
# Configures two callbacks instead of a single after_save so that
|
189
|
+
# the model may rely on their execution order relative to its
|
190
|
+
# own callbacks.
|
191
|
+
#
|
192
|
+
# For example, given that after_creates run before after_saves, if
|
193
|
+
# we configured instead an after_save there would be no way to fire
|
194
|
+
# a custom after_create callback after the child association gets
|
195
|
+
# created.
|
196
|
+
after_create save_method
|
197
|
+
after_update save_method
|
195
198
|
else
|
196
|
-
|
197
|
-
|
198
|
-
# Configures two callbacks instead of a single after_save so that
|
199
|
-
# the model may rely on their execution order relative to its
|
200
|
-
# own callbacks.
|
201
|
-
#
|
202
|
-
# For example, given that after_creates run before after_saves, if
|
203
|
-
# we configured instead an after_save there would be no way to fire
|
204
|
-
# a custom after_create callback after the child association gets
|
205
|
-
# created.
|
206
|
-
after_create save_method
|
207
|
-
after_update save_method
|
208
|
-
else
|
209
|
-
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
|
210
|
-
before_save save_method
|
211
|
-
end
|
199
|
+
define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
|
200
|
+
before_save save_method
|
212
201
|
end
|
213
202
|
end
|
214
203
|
|
@@ -297,7 +286,7 @@ module ActiveRecord
|
|
297
286
|
def association_valid?(reflection, record)
|
298
287
|
return true if record.destroyed? || record.marked_for_destruction?
|
299
288
|
|
300
|
-
unless valid = record.valid?
|
289
|
+
unless valid = record.valid?(validation_context)
|
301
290
|
if reflection.options[:autosave]
|
302
291
|
record.errors.each do |attribute, message|
|
303
292
|
attribute = "#{reflection.name}.#{attribute}"
|
@@ -331,38 +320,34 @@ module ActiveRecord
|
|
331
320
|
autosave = reflection.options[:autosave]
|
332
321
|
|
333
322
|
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
323
|
+
records_to_destroy = []
|
324
|
+
records.each do |record|
|
325
|
+
next if record.destroyed?
|
326
|
+
|
327
|
+
saved = true
|
328
|
+
|
329
|
+
if autosave && record.marked_for_destruction?
|
330
|
+
records_to_destroy << record
|
331
|
+
elsif autosave != false && (@new_record_before_save || record.new_record?)
|
332
|
+
if autosave
|
333
|
+
saved = association.insert_record(record, false)
|
334
|
+
else
|
335
|
+
association.insert_record(record) unless reflection.nested?
|
336
|
+
end
|
337
|
+
elsif autosave
|
338
|
+
saved = record.save(:validate => false)
|
339
339
|
end
|
340
340
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
saved = true
|
345
|
-
|
346
|
-
if autosave != false && (@new_record_before_save || record.new_record?)
|
347
|
-
if autosave
|
348
|
-
saved = association.insert_record(record, false)
|
349
|
-
else
|
350
|
-
association.insert_record(record) unless reflection.nested?
|
351
|
-
end
|
352
|
-
elsif autosave
|
353
|
-
saved = record.save(:validate => false)
|
354
|
-
end
|
341
|
+
raise ActiveRecord::Rollback unless saved
|
342
|
+
end
|
355
343
|
|
356
|
-
|
357
|
-
|
358
|
-
rescue
|
359
|
-
records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
|
360
|
-
raise
|
344
|
+
records_to_destroy.each do |record|
|
345
|
+
association.destroy(record)
|
361
346
|
end
|
362
347
|
end
|
363
348
|
|
364
349
|
# reconstruct the scope now that we know the owner's id
|
365
|
-
association.reset_scope if association.respond_to?(:reset_scope)
|
350
|
+
association.send(:reset_scope) if association.respond_to?(:reset_scope)
|
366
351
|
end
|
367
352
|
end
|
368
353
|
|
@@ -407,6 +392,7 @@ module ActiveRecord
|
|
407
392
|
autosave = reflection.options[:autosave]
|
408
393
|
|
409
394
|
if autosave && record.marked_for_destruction?
|
395
|
+
self[reflection.foreign_key] = nil
|
410
396
|
record.destroy
|
411
397
|
elsif autosave != false
|
412
398
|
saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|