activerecord 4.2.0 → 5.0.0
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 +1537 -789
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record/aggregations.rb +37 -23
- data/lib/active_record/association_relation.rb +16 -3
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +23 -9
- data/lib/active_record/associations/association_scope.rb +74 -102
- data/lib/active_record/associations/belongs_to_association.rb +26 -29
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +12 -20
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -10
- data/lib/active_record/associations/collection_association.rb +61 -33
- data/lib/active_record/associations/collection_proxy.rb +81 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +15 -45
- data/lib/active_record/associations/has_one_association.rb +13 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
- data/lib/active_record/associations/join_dependency.rb +37 -21
- data/lib/active_record/associations/preloader/association.rb +51 -53
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +27 -14
- data/lib/active_record/associations/preloader.rb +18 -8
- data/lib/active_record/associations/singular_association.rb +8 -8
- data/lib/active_record/associations/through_association.rb +22 -9
- data/lib/active_record/associations.rb +321 -212
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +79 -15
- data/lib/active_record/attribute_assignment.rb +20 -141
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
- data/lib/active_record/attribute_methods/dirty.rb +51 -81
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
- data/lib/active_record/attribute_methods/write.rb +14 -38
- data/lib/active_record/attribute_methods.rb +70 -45
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +37 -15
- data/lib/active_record/attribute_set.rb +34 -3
- data/lib/active_record/attributes.rb +199 -73
- data/lib/active_record/autosave_association.rb +73 -25
- data/lib/active_record/base.rb +35 -27
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
- data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +38 -15
- data/lib/active_record/core.rb +109 -114
- data/lib/active_record/counter_cache.rb +14 -25
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +115 -79
- data/lib/active_record/errors.rb +88 -48
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +84 -46
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +27 -25
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +43 -21
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration.rb +372 -114
- data/lib/active_record/model_schema.rb +128 -38
- data/lib/active_record/nested_attributes.rb +71 -32
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +124 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- data/lib/active_record/railtie.rb +28 -19
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +67 -51
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +318 -139
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +139 -34
- data/lib/active_record/relation/calculations.rb +80 -102
- data/lib/active_record/relation/delegation.rb +7 -20
- data/lib/active_record/relation/finder_methods.rb +167 -97
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +38 -41
- data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +124 -82
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +323 -257
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -10
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +176 -115
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +95 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +62 -38
- data/lib/active_record/schema_migration.rb +11 -17
- data/lib/active_record/scoping/default.rb +24 -9
- data/lib/active_record/scoping/named.rb +49 -28
- data/lib/active_record/scoping.rb +32 -15
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +16 -14
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +59 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
- data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +159 -67
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -41
- data/lib/active_record/type/date_time.rb +2 -38
- data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +21 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +29 -18
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record.rb +9 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +60 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -30
- data/lib/active_record/type/decimal.rb +0 -40
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -55
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -36
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -101
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_record/attribute'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class Attribute # :nodoc:
|
5
|
+
class UserProvidedDefault < FromUser # :nodoc:
|
6
|
+
def initialize(name, value, type, database_default)
|
7
|
+
@user_provided_value = value
|
8
|
+
super(name, value, type, database_default)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value_before_type_cast
|
12
|
+
if user_provided_value.is_a?(Proc)
|
13
|
+
@memoized_value_before_type_cast ||= user_provided_value.call
|
14
|
+
else
|
15
|
+
@user_provided_value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_type(type)
|
20
|
+
self.class.new(name, user_provided_value, type, original_attribute)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :user_provided_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -5,8 +5,8 @@ module ActiveRecord
|
|
5
5
|
FromDatabase.new(name, value, type)
|
6
6
|
end
|
7
7
|
|
8
|
-
def from_user(name, value, type)
|
9
|
-
FromUser.new(name, value, type)
|
8
|
+
def from_user(name, value, type, original_attribute = nil)
|
9
|
+
FromUser.new(name, value, type, original_attribute)
|
10
10
|
end
|
11
11
|
|
12
12
|
def with_cast_value(name, value, type)
|
@@ -26,36 +26,46 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
# This method should not be called directly.
|
28
28
|
# Use #from_database or #from_user
|
29
|
-
def initialize(name, value_before_type_cast, type)
|
29
|
+
def initialize(name, value_before_type_cast, type, original_attribute = nil)
|
30
30
|
@name = name
|
31
31
|
@value_before_type_cast = value_before_type_cast
|
32
32
|
@type = type
|
33
|
+
@original_attribute = original_attribute
|
33
34
|
end
|
34
35
|
|
35
36
|
def value
|
36
37
|
# `defined?` is cheaper than `||=` when we get back falsy values
|
37
|
-
@value =
|
38
|
+
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
38
39
|
@value
|
39
40
|
end
|
40
41
|
|
41
42
|
def original_value
|
42
|
-
|
43
|
+
if assigned?
|
44
|
+
original_attribute.original_value
|
45
|
+
else
|
46
|
+
type_cast(value_before_type_cast)
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
def value_for_database
|
46
|
-
type.
|
51
|
+
type.serialize(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed?
|
55
|
+
changed_from_assignment? || changed_in_place?
|
47
56
|
end
|
48
57
|
|
49
|
-
def
|
50
|
-
type.
|
58
|
+
def changed_in_place?
|
59
|
+
has_been_read? && type.changed_in_place?(original_value_for_database, value)
|
51
60
|
end
|
52
61
|
|
53
|
-
def
|
54
|
-
|
62
|
+
def forgetting_assignment
|
63
|
+
with_value_from_database(value_for_database)
|
55
64
|
end
|
56
65
|
|
57
66
|
def with_value_from_user(value)
|
58
|
-
|
67
|
+
type.assert_valid_value(value)
|
68
|
+
self.class.from_user(name, value, type, self)
|
59
69
|
end
|
60
70
|
|
61
71
|
def with_value_from_database(value)
|
@@ -66,6 +76,10 @@ module ActiveRecord
|
|
66
76
|
self.class.with_cast_value(name, value, type)
|
67
77
|
end
|
68
78
|
|
79
|
+
def with_type(type)
|
80
|
+
self.class.new(name, value_before_type_cast, type, original_attribute)
|
81
|
+
end
|
82
|
+
|
69
83
|
def type_cast(*)
|
70
84
|
raise NotImplementedError
|
71
85
|
end
|
@@ -74,30 +88,70 @@ module ActiveRecord
|
|
74
88
|
true
|
75
89
|
end
|
76
90
|
|
91
|
+
def came_from_user?
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_been_read?
|
96
|
+
defined?(@value)
|
97
|
+
end
|
98
|
+
|
77
99
|
def ==(other)
|
78
100
|
self.class == other.class &&
|
79
101
|
name == other.name &&
|
80
102
|
value_before_type_cast == other.value_before_type_cast &&
|
81
103
|
type == other.type
|
82
104
|
end
|
105
|
+
alias eql? ==
|
106
|
+
|
107
|
+
def hash
|
108
|
+
[self.class, name, value_before_type_cast, type].hash
|
109
|
+
end
|
83
110
|
|
84
111
|
protected
|
85
112
|
|
113
|
+
attr_reader :original_attribute
|
114
|
+
alias_method :assigned?, :original_attribute
|
115
|
+
|
86
116
|
def initialize_dup(other)
|
87
117
|
if defined?(@value) && @value.duplicable?
|
88
118
|
@value = @value.dup
|
89
119
|
end
|
90
120
|
end
|
91
121
|
|
122
|
+
def changed_from_assignment?
|
123
|
+
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
124
|
+
end
|
125
|
+
|
126
|
+
def original_value_for_database
|
127
|
+
if assigned?
|
128
|
+
original_attribute.original_value_for_database
|
129
|
+
else
|
130
|
+
_original_value_for_database
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def _original_value_for_database
|
135
|
+
value_for_database
|
136
|
+
end
|
137
|
+
|
92
138
|
class FromDatabase < Attribute # :nodoc:
|
93
139
|
def type_cast(value)
|
94
|
-
type.
|
140
|
+
type.deserialize(value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def _original_value_for_database
|
144
|
+
value_before_type_cast
|
95
145
|
end
|
96
146
|
end
|
97
147
|
|
98
148
|
class FromUser < Attribute # :nodoc:
|
99
149
|
def type_cast(value)
|
100
|
-
type.
|
150
|
+
type.cast(value)
|
151
|
+
end
|
152
|
+
|
153
|
+
def came_from_user?
|
154
|
+
true
|
101
155
|
end
|
102
156
|
end
|
103
157
|
|
@@ -116,10 +170,14 @@ module ActiveRecord
|
|
116
170
|
super(name, nil, Type::Value.new)
|
117
171
|
end
|
118
172
|
|
119
|
-
def
|
173
|
+
def type_cast(*)
|
120
174
|
nil
|
121
175
|
end
|
122
176
|
|
177
|
+
def with_type(type)
|
178
|
+
self.class.with_cast_value(name, nil, type)
|
179
|
+
end
|
180
|
+
|
123
181
|
def with_value_from_database(value)
|
124
182
|
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
125
183
|
end
|
@@ -127,6 +185,8 @@ module ActiveRecord
|
|
127
185
|
end
|
128
186
|
|
129
187
|
class Uninitialized < Attribute # :nodoc:
|
188
|
+
UNINITIALIZED_ORIGINAL_VALUE = Object.new
|
189
|
+
|
130
190
|
def initialize(name, type)
|
131
191
|
super(name, nil, type)
|
132
192
|
end
|
@@ -137,6 +197,10 @@ module ActiveRecord
|
|
137
197
|
end
|
138
198
|
end
|
139
199
|
|
200
|
+
def original_value
|
201
|
+
UNINITIALIZED_ORIGINAL_VALUE
|
202
|
+
end
|
203
|
+
|
140
204
|
def value_for_database
|
141
205
|
end
|
142
206
|
|
@@ -144,6 +208,6 @@ module ActiveRecord
|
|
144
208
|
false
|
145
209
|
end
|
146
210
|
end
|
147
|
-
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
148
212
|
end
|
149
213
|
end
|
@@ -3,63 +3,32 @@ require 'active_model/forbidden_attributes_protection'
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeAssignment
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
include ActiveModel::
|
6
|
+
include ActiveModel::AttributeAssignment
|
7
7
|
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
|
13
|
-
# exception is raised.
|
14
|
-
#
|
15
|
-
# cat = Cat.new(name: "Gorby", status: "yawning")
|
16
|
-
# cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
|
17
|
-
# cat.assign_attributes(status: "sleeping")
|
18
|
-
# cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
|
19
|
-
#
|
20
|
-
# New attributes will be persisted in the database when the object is saved.
|
21
|
-
#
|
22
|
-
# Aliased to <tt>attributes=</tt>.
|
23
|
-
def assign_attributes(new_attributes)
|
24
|
-
if !new_attributes.respond_to?(:stringify_keys)
|
25
|
-
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
26
|
-
end
|
27
|
-
return if new_attributes.blank?
|
8
|
+
# Alias for ActiveModel::AttributeAssignment#assign_attributes. See ActiveModel::AttributeAssignment.
|
9
|
+
def attributes=(attributes)
|
10
|
+
assign_attributes(attributes)
|
11
|
+
end
|
28
12
|
|
29
|
-
|
30
|
-
multi_parameter_attributes = []
|
31
|
-
nested_parameter_attributes = []
|
13
|
+
private
|
32
14
|
|
33
|
-
|
15
|
+
def _assign_attributes(attributes) # :nodoc:
|
16
|
+
multi_parameter_attributes = {}
|
17
|
+
nested_parameter_attributes = {}
|
34
18
|
|
35
19
|
attributes.each do |k, v|
|
36
20
|
if k.include?("(")
|
37
|
-
multi_parameter_attributes
|
21
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
38
22
|
elsif v.is_a?(Hash)
|
39
|
-
nested_parameter_attributes
|
40
|
-
else
|
41
|
-
_assign_attribute(k, v)
|
23
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
42
24
|
end
|
43
25
|
end
|
26
|
+
super(attributes)
|
44
27
|
|
45
28
|
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
46
29
|
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
47
30
|
end
|
48
31
|
|
49
|
-
alias attributes= assign_attributes
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def _assign_attribute(k, v)
|
54
|
-
public_send("#{k}=", v)
|
55
|
-
rescue NoMethodError
|
56
|
-
if respond_to?("#{k}=")
|
57
|
-
raise
|
58
|
-
else
|
59
|
-
raise UnknownAttributeError.new(self, k)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
32
|
# Assign any deferred nested attributes after the base attributes have been set.
|
64
33
|
def assign_nested_parameter_attributes(pairs)
|
65
34
|
pairs.each { |k, v| _assign_attribute(k, v) }
|
@@ -69,7 +38,7 @@ module ActiveRecord
|
|
69
38
|
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
70
39
|
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
71
40
|
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
72
|
-
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for
|
41
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
73
42
|
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
74
43
|
def assign_multiparameter_attributes(pairs)
|
75
44
|
execute_callstack_for_multiparameter_attributes(
|
@@ -81,13 +50,18 @@ module ActiveRecord
|
|
81
50
|
errors = []
|
82
51
|
callstack.each do |name, values_with_empty_parameters|
|
83
52
|
begin
|
84
|
-
|
53
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
54
|
+
values = nil
|
55
|
+
else
|
56
|
+
values = values_with_empty_parameters
|
57
|
+
end
|
58
|
+
send("#{name}=", values)
|
85
59
|
rescue => ex
|
86
60
|
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
87
61
|
end
|
88
62
|
end
|
89
63
|
unless errors.empty?
|
90
|
-
error_descriptions = errors.map
|
64
|
+
error_descriptions = errors.map(&:message).join(",")
|
91
65
|
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
92
66
|
end
|
93
67
|
end
|
@@ -113,100 +87,5 @@ module ActiveRecord
|
|
113
87
|
def find_parameter_position(multiparameter_name)
|
114
88
|
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
115
89
|
end
|
116
|
-
|
117
|
-
class MultiparameterAttribute #:nodoc:
|
118
|
-
attr_reader :object, :name, :values, :cast_type
|
119
|
-
|
120
|
-
def initialize(object, name, values)
|
121
|
-
@object = object
|
122
|
-
@name = name
|
123
|
-
@values = values
|
124
|
-
end
|
125
|
-
|
126
|
-
def read_value
|
127
|
-
return if values.values.compact.empty?
|
128
|
-
|
129
|
-
@cast_type = object.type_for_attribute(name)
|
130
|
-
klass = cast_type.klass
|
131
|
-
|
132
|
-
if klass == Time
|
133
|
-
read_time
|
134
|
-
elsif klass == Date
|
135
|
-
read_date
|
136
|
-
else
|
137
|
-
read_other
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
private
|
142
|
-
|
143
|
-
def instantiate_time_object(set_values)
|
144
|
-
if object.class.send(:create_time_zone_conversion_attribute?, name, cast_type)
|
145
|
-
Time.zone.local(*set_values)
|
146
|
-
else
|
147
|
-
Time.send(object.class.default_timezone, *set_values)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def read_time
|
152
|
-
# If column is a :time (and not :date or :datetime) there is no need to validate if
|
153
|
-
# there are year/month/day fields
|
154
|
-
if cast_type.type == :time
|
155
|
-
# if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil
|
156
|
-
{ 1 => 1970, 2 => 1, 3 => 1 }.each do |key,value|
|
157
|
-
values[key] ||= value
|
158
|
-
end
|
159
|
-
else
|
160
|
-
# else column is a timestamp, so if Date bits were not provided, error
|
161
|
-
validate_required_parameters!([1,2,3])
|
162
|
-
|
163
|
-
# If Date bits were provided but blank, then return nil
|
164
|
-
return if blank_date_parameter?
|
165
|
-
end
|
166
|
-
|
167
|
-
max_position = extract_max_param(6)
|
168
|
-
set_values = values.values_at(*(1..max_position))
|
169
|
-
# If Time bits are not there, then default to 0
|
170
|
-
(3..5).each { |i| set_values[i] = set_values[i].presence || 0 }
|
171
|
-
instantiate_time_object(set_values)
|
172
|
-
end
|
173
|
-
|
174
|
-
def read_date
|
175
|
-
return if blank_date_parameter?
|
176
|
-
set_values = values.values_at(1,2,3)
|
177
|
-
begin
|
178
|
-
Date.new(*set_values)
|
179
|
-
rescue ArgumentError # if Date.new raises an exception on an invalid date
|
180
|
-
instantiate_time_object(set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def read_other
|
185
|
-
max_position = extract_max_param
|
186
|
-
positions = (1..max_position)
|
187
|
-
validate_required_parameters!(positions)
|
188
|
-
|
189
|
-
values.slice(*positions)
|
190
|
-
end
|
191
|
-
|
192
|
-
# Checks whether some blank date parameter exists. Note that this is different
|
193
|
-
# than the validate_required_parameters! method, since it just checks for blank
|
194
|
-
# positions instead of missing ones, and does not raise in case one blank position
|
195
|
-
# exists. The caller is responsible to handle the case of this returning true.
|
196
|
-
def blank_date_parameter?
|
197
|
-
(1..3).any? { |position| values[position].blank? }
|
198
|
-
end
|
199
|
-
|
200
|
-
# If some position is not provided, it errors out a missing parameter exception.
|
201
|
-
def validate_required_parameters!(positions)
|
202
|
-
if missing_parameter = positions.detect { |position| !values.key?(position) }
|
203
|
-
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def extract_max_param(upper_cap = 100)
|
208
|
-
[values.keys.max, upper_cap].min
|
209
|
-
end
|
210
|
-
end
|
211
90
|
end
|
212
91
|
end
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
18
|
-
|
18
|
+
reload_schema_from_cache
|
19
19
|
decorator_name = decorator_name.to_s
|
20
20
|
|
21
21
|
# Create new hashes so we don't modify parent classes
|
@@ -24,10 +24,11 @@ module ActiveRecord
|
|
24
24
|
|
25
25
|
private
|
26
26
|
|
27
|
-
def
|
28
|
-
super
|
29
|
-
|
30
|
-
|
27
|
+
def load_schema!
|
28
|
+
super
|
29
|
+
attribute_types.each do |name, type|
|
30
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
31
|
+
define_attribute(name, decorated_type)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
end
|
@@ -2,7 +2,7 @@ module ActiveRecord
|
|
2
2
|
module AttributeMethods
|
3
3
|
# = Active Record Attribute Methods Before Type Cast
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to
|
6
6
|
# read the value of the attributes before typecasting and deserialization.
|
7
7
|
#
|
8
8
|
# class Task < ActiveRecord::Base
|
@@ -28,6 +28,7 @@ module ActiveRecord
|
|
28
28
|
|
29
29
|
included do
|
30
30
|
attribute_method_suffix "_before_type_cast"
|
31
|
+
attribute_method_suffix "_came_from_user?"
|
31
32
|
end
|
32
33
|
|
33
34
|
# Returns the value of the attribute identified by +attr_name+ before
|
@@ -66,6 +67,10 @@ module ActiveRecord
|
|
66
67
|
def attribute_before_type_cast(attribute_name)
|
67
68
|
read_attribute_before_type_cast(attribute_name)
|
68
69
|
end
|
70
|
+
|
71
|
+
def attribute_came_from_user?(attribute_name)
|
72
|
+
@attributes[attribute_name].came_from_user?
|
73
|
+
end
|
69
74
|
end
|
70
75
|
end
|
71
76
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/module/attribute_accessors'
|
2
|
+
require 'active_record/attribute_mutation_tracker'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
module AttributeMethods
|
@@ -34,23 +35,43 @@ module ActiveRecord
|
|
34
35
|
# <tt>reload</tt> the record and clears changed attributes.
|
35
36
|
def reload(*)
|
36
37
|
super.tap do
|
37
|
-
|
38
|
+
@mutation_tracker = nil
|
39
|
+
@previous_mutation_tracker = nil
|
40
|
+
@changed_attributes = HashWithIndifferentAccess.new
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
41
44
|
def initialize_dup(other) # :nodoc:
|
42
45
|
super
|
43
|
-
|
46
|
+
@attributes = self.class._default_attributes.map do |attr|
|
47
|
+
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
48
|
+
end
|
49
|
+
@mutation_tracker = nil
|
44
50
|
end
|
45
51
|
|
46
52
|
def changes_applied
|
47
|
-
|
48
|
-
|
53
|
+
@previous_mutation_tracker = mutation_tracker
|
54
|
+
@changed_attributes = HashWithIndifferentAccess.new
|
55
|
+
store_original_attributes
|
49
56
|
end
|
50
57
|
|
51
58
|
def clear_changes_information
|
59
|
+
@previous_mutation_tracker = nil
|
60
|
+
@changed_attributes = HashWithIndifferentAccess.new
|
61
|
+
store_original_attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
def raw_write_attribute(attr_name, *)
|
65
|
+
result = super
|
66
|
+
clear_attribute_change(attr_name)
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def clear_attribute_changes(attr_names)
|
52
71
|
super
|
53
|
-
|
72
|
+
attr_names.each do |attr_name|
|
73
|
+
clear_attribute_change(attr_name)
|
74
|
+
end
|
54
75
|
end
|
55
76
|
|
56
77
|
def changed_attributes
|
@@ -59,7 +80,7 @@ module ActiveRecord
|
|
59
80
|
if defined?(@cached_changed_attributes)
|
60
81
|
@cached_changed_attributes
|
61
82
|
else
|
62
|
-
super.reverse_merge(
|
83
|
+
super.reverse_merge(mutation_tracker.changed_values).freeze
|
63
84
|
end
|
64
85
|
end
|
65
86
|
|
@@ -69,54 +90,29 @@ module ActiveRecord
|
|
69
90
|
end
|
70
91
|
end
|
71
92
|
|
93
|
+
def previous_changes
|
94
|
+
previous_mutation_tracker.changes
|
95
|
+
end
|
96
|
+
|
72
97
|
def attribute_changed_in_place?(attr_name)
|
73
|
-
|
74
|
-
@attributes[attr_name].changed_in_place_from?(old_value)
|
98
|
+
mutation_tracker.changed_in_place?(attr_name)
|
75
99
|
end
|
76
100
|
|
77
101
|
private
|
78
102
|
|
79
|
-
def
|
80
|
-
@
|
81
|
-
|
82
|
-
set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value)
|
103
|
+
def mutation_tracker
|
104
|
+
unless defined?(@mutation_tracker)
|
105
|
+
@mutation_tracker = nil
|
83
106
|
end
|
107
|
+
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
|
84
108
|
end
|
85
109
|
|
86
|
-
|
87
|
-
|
88
|
-
attr = attr.to_s
|
89
|
-
|
90
|
-
old_value = old_attribute_value(attr)
|
91
|
-
|
92
|
-
result = super
|
93
|
-
store_original_raw_attribute(attr)
|
94
|
-
save_changed_attribute(attr, old_value)
|
95
|
-
result
|
96
|
-
end
|
97
|
-
|
98
|
-
def raw_write_attribute(attr, value)
|
99
|
-
attr = attr.to_s
|
100
|
-
|
101
|
-
result = super
|
102
|
-
original_raw_attributes[attr] = value
|
103
|
-
result
|
104
|
-
end
|
105
|
-
|
106
|
-
def save_changed_attribute(attr, old_value)
|
107
|
-
if attribute_changed?(attr)
|
108
|
-
clear_attribute_changes(attr) unless _field_changed?(attr, old_value)
|
109
|
-
else
|
110
|
-
set_attribute_was(attr, old_value) if _field_changed?(attr, old_value)
|
111
|
-
end
|
110
|
+
def changes_include?(attr_name)
|
111
|
+
super || mutation_tracker.changed?(attr_name)
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
115
|
-
|
116
|
-
changed_attributes[attr]
|
117
|
-
else
|
118
|
-
clone_attribute_value(:_read_attribute, attr)
|
119
|
-
end
|
114
|
+
def clear_attribute_change(attr_name)
|
115
|
+
mutation_tracker.forget_change(attr_name)
|
120
116
|
end
|
121
117
|
|
122
118
|
def _update_record(*)
|
@@ -127,54 +123,28 @@ module ActiveRecord
|
|
127
123
|
partial_writes? ? super(keys_for_partial_write) : super
|
128
124
|
end
|
129
125
|
|
130
|
-
# Serialized attributes should always be written in case they've been
|
131
|
-
# changed in place.
|
132
126
|
def keys_for_partial_write
|
133
|
-
changed
|
127
|
+
changed & self.class.column_names
|
134
128
|
end
|
135
129
|
|
136
|
-
def
|
137
|
-
@attributes
|
130
|
+
def store_original_attributes
|
131
|
+
@attributes = @attributes.map(&:forgetting_assignment)
|
132
|
+
@mutation_tracker = nil
|
138
133
|
end
|
139
134
|
|
140
|
-
def
|
141
|
-
|
142
|
-
orig = @attributes[attr_name].original_value
|
143
|
-
h[attr_name] = orig
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def changed_in_place
|
148
|
-
self.class.attribute_names.select do |attr_name|
|
149
|
-
attribute_changed_in_place?(attr_name)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def original_raw_attribute(attr_name)
|
154
|
-
original_raw_attributes.fetch(attr_name) do
|
155
|
-
read_attribute_before_type_cast(attr_name)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def original_raw_attributes
|
160
|
-
@original_raw_attributes ||= {}
|
161
|
-
end
|
162
|
-
|
163
|
-
def store_original_raw_attribute(attr_name)
|
164
|
-
original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database
|
165
|
-
end
|
166
|
-
|
167
|
-
def store_original_raw_attributes
|
168
|
-
attribute_names.each do |attr|
|
169
|
-
store_original_raw_attribute(attr)
|
170
|
-
end
|
135
|
+
def previous_mutation_tracker
|
136
|
+
@previous_mutation_tracker ||= NullMutationTracker.instance
|
171
137
|
end
|
172
138
|
|
173
139
|
def cache_changed_attributes
|
174
140
|
@cached_changed_attributes = changed_attributes
|
175
141
|
yield
|
176
142
|
ensure
|
177
|
-
|
143
|
+
clear_changed_attributes_cache
|
144
|
+
end
|
145
|
+
|
146
|
+
def clear_changed_attributes_cache
|
147
|
+
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
178
148
|
end
|
179
149
|
end
|
180
150
|
end
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
module PrimaryKey
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
# Returns this record's primary key value wrapped in an
|
8
|
+
# Returns this record's primary key value wrapped in an array if one is
|
9
9
|
# available.
|
10
10
|
def to_key
|
11
11
|
sync_with_transaction_state
|
@@ -108,7 +108,7 @@ module ActiveRecord
|
|
108
108
|
# self.primary_key = 'sysid'
|
109
109
|
# end
|
110
110
|
#
|
111
|
-
# You can also define the
|
111
|
+
# You can also define the #primary_key method yourself:
|
112
112
|
#
|
113
113
|
# class Project < ActiveRecord::Base
|
114
114
|
# def self.primary_key
|