activerecord 5.0.7.2 → 5.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 +5 -5
- data/CHANGELOG.md +389 -2252
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/examples/performance.rb +28 -28
- data/examples/simple.rb +3 -3
- data/lib/active_record.rb +20 -20
- data/lib/active_record/aggregations.rb +244 -244
- data/lib/active_record/association_relation.rb +5 -5
- data/lib/active_record/associations.rb +1579 -1569
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +23 -15
- data/lib/active_record/associations/association_scope.rb +83 -81
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/builder/belongs_to.rb +16 -14
- data/lib/active_record/associations/builder/collection_association.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
- data/lib/active_record/associations/collection_association.rb +74 -241
- data/lib/active_record/associations/collection_proxy.rb +144 -70
- data/lib/active_record/associations/has_many_association.rb +15 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -5
- data/lib/active_record/associations/has_one_association.rb +22 -28
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +117 -115
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
- data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/preloader.rb +94 -94
- data/lib/active_record/associations/preloader/association.rb +87 -64
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
- data/lib/active_record/associations/preloader/collection_association.rb +6 -6
- data/lib/active_record/associations/preloader/has_many.rb +0 -2
- data/lib/active_record/associations/preloader/singular_association.rb +6 -8
- data/lib/active_record/associations/preloader/through_association.rb +34 -41
- data/lib/active_record/associations/singular_association.rb +8 -25
- data/lib/active_record/associations/through_association.rb +3 -6
- data/lib/active_record/attribute.rb +98 -71
- data/lib/active_record/attribute/user_provided_default.rb +4 -2
- data/lib/active_record/attribute_assignment.rb +61 -61
- data/lib/active_record/attribute_decorators.rb +35 -13
- data/lib/active_record/attribute_methods.rb +56 -65
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +216 -34
- data/lib/active_record/attribute_methods/primary_key.rb +78 -73
- data/lib/active_record/attribute_methods/read.rb +39 -35
- data/lib/active_record/attribute_methods/serialization.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
- data/lib/active_record/attribute_methods/write.rb +36 -30
- data/lib/active_record/attribute_mutation_tracker.rb +53 -10
- data/lib/active_record/attribute_set.rb +9 -6
- data/lib/active_record/attribute_set/builder.rb +41 -49
- data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_record/attributes.rb +21 -21
- data/lib/active_record/autosave_association.rb +13 -13
- data/lib/active_record/base.rb +24 -22
- data/lib/active_record/callbacks.rb +52 -14
- data/lib/active_record/coders/yaml_column.rb +9 -11
- data/lib/active_record/collection_cache_key.rb +6 -17
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
- data/lib/active_record/connection_adapters/column.rb +27 -5
- data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
- data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
- data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
- data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
- data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
- data/lib/active_record/connection_handling.rb +14 -26
- data/lib/active_record/core.rb +110 -93
- data/lib/active_record/counter_cache.rb +62 -13
- data/lib/active_record/define_callbacks.rb +20 -0
- data/lib/active_record/dynamic_matchers.rb +80 -79
- data/lib/active_record/enum.rb +8 -6
- data/lib/active_record/errors.rb +58 -15
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +7 -4
- data/lib/active_record/fixture_set/file.rb +11 -8
- data/lib/active_record/fixtures.rb +66 -53
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +93 -79
- data/lib/active_record/integration.rb +7 -7
- data/lib/active_record/internal_metadata.rb +3 -16
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +64 -56
- data/lib/active_record/locking/pessimistic.rb +10 -1
- data/lib/active_record/log_subscriber.rb +29 -29
- data/lib/active_record/migration.rb +155 -172
- data/lib/active_record/migration/command_recorder.rb +94 -94
- data/lib/active_record/migration/compatibility.rb +76 -37
- data/lib/active_record/migration/join_table.rb +6 -6
- data/lib/active_record/model_schema.rb +85 -119
- data/lib/active_record/nested_attributes.rb +200 -199
- data/lib/active_record/null_relation.rb +10 -33
- data/lib/active_record/persistence.rb +45 -38
- data/lib/active_record/query_cache.rb +4 -8
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +16 -17
- data/lib/active_record/railties/controller_runtime.rb +6 -2
- data/lib/active_record/railties/databases.rake +125 -140
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -2
- data/lib/active_record/reflection.rb +79 -96
- data/lib/active_record/relation.rb +72 -115
- data/lib/active_record/relation/batches.rb +87 -58
- data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
- data/lib/active_record/relation/calculations.rb +154 -160
- data/lib/active_record/relation/delegation.rb +30 -29
- data/lib/active_record/relation/finder_methods.rb +195 -226
- data/lib/active_record/relation/merger.rb +58 -62
- data/lib/active_record/relation/predicate_builder.rb +92 -89
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
- data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +247 -295
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +4 -5
- data/lib/active_record/relation/where_clause.rb +79 -65
- data/lib/active_record/relation/where_clause_factory.rb +47 -8
- data/lib/active_record/result.rb +29 -31
- data/lib/active_record/runtime_registry.rb +3 -3
- data/lib/active_record/sanitization.rb +182 -197
- data/lib/active_record/schema.rb +3 -3
- data/lib/active_record/schema_dumper.rb +14 -37
- data/lib/active_record/schema_migration.rb +3 -3
- data/lib/active_record/scoping.rb +9 -10
- data/lib/active_record/scoping/default.rb +87 -91
- data/lib/active_record/scoping/named.rb +16 -28
- data/lib/active_record/secure_token.rb +2 -2
- data/lib/active_record/statement_cache.rb +13 -15
- data/lib/active_record/store.rb +31 -32
- data/lib/active_record/suppressor.rb +2 -1
- data/lib/active_record/table_metadata.rb +9 -5
- data/lib/active_record/tasks/database_tasks.rb +72 -65
- data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
- data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
- data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +1 -2
- data/lib/active_record/transactions.rb +98 -110
- data/lib/active_record/type.rb +17 -13
- data/lib/active_record/type/adapter_specific_registry.rb +46 -42
- data/lib/active_record/type/decimal_without_scale.rb +9 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
- data/lib/active_record/type/serialized.rb +8 -8
- data/lib/active_record/type/text.rb +9 -0
- data/lib/active_record/type/time.rb +0 -1
- data/lib/active_record/type/type_map.rb +11 -15
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type_caster.rb +2 -2
- data/lib/active_record/type_caster/connection.rb +8 -6
- data/lib/active_record/type_caster/map.rb +3 -1
- data/lib/active_record/validations.rb +4 -4
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +8 -39
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +4 -4
- data/lib/rails/generators/active_record/migration.rb +2 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
- metadata +22 -13
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_record/attribute"
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
class Attribute # :nodoc:
|
@@ -20,9 +20,11 @@ module ActiveRecord
|
|
20
20
|
self.class.new(name, user_provided_value, type, original_attribute)
|
21
21
|
end
|
22
22
|
|
23
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
24
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
23
25
|
protected
|
24
26
|
|
25
|
-
|
27
|
+
attr_reader :user_provided_value
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_model/forbidden_attributes_protection"
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module AttributeAssignment
|
@@ -12,80 +12,80 @@ module ActiveRecord
|
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def _assign_attributes(attributes)
|
16
|
+
multi_parameter_attributes = {}
|
17
|
+
nested_parameter_attributes = {}
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
attributes.each do |k, v|
|
20
|
+
if k.include?("(")
|
21
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
22
|
+
elsif v.is_a?(Hash)
|
23
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
24
|
+
end
|
24
25
|
end
|
25
|
-
|
26
|
-
super(attributes)
|
26
|
+
super(attributes)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
|
29
|
+
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
33
|
+
def assign_nested_parameter_attributes(pairs)
|
34
|
+
pairs.each { |k, v| _assign_attribute(k, v) }
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
37
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
38
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
39
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
40
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
41
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
42
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
43
|
+
def assign_multiparameter_attributes(pairs)
|
44
|
+
execute_callstack_for_multiparameter_attributes(
|
45
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
46
|
+
)
|
47
|
+
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
50
|
+
errors = []
|
51
|
+
callstack.each do |name, values_with_empty_parameters|
|
52
|
+
begin
|
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)
|
59
|
+
rescue => ex
|
60
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
57
61
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
62
|
+
end
|
63
|
+
unless errors.empty?
|
64
|
+
error_descriptions = errors.map(&:message).join(",")
|
65
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
61
66
|
end
|
62
67
|
end
|
63
|
-
unless errors.empty?
|
64
|
-
error_descriptions = errors.map(&:message).join(",")
|
65
|
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
66
|
-
end
|
67
|
-
end
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
70
|
+
attributes = {}
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
pairs.each do |(multiparameter_name, value)|
|
73
|
+
attribute_name = multiparameter_name.split("(").first
|
74
|
+
attributes[attribute_name] ||= {}
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
77
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
78
|
+
end
|
79
79
|
|
80
|
-
|
81
|
-
|
80
|
+
attributes
|
81
|
+
end
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
84
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
85
|
+
end
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
def find_parameter_position(multiparameter_name)
|
88
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
89
|
+
end
|
90
90
|
end
|
91
91
|
end
|
@@ -8,12 +8,34 @@ module ActiveRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods # :nodoc:
|
11
|
+
# This method is an internal API used to create class macros such as
|
12
|
+
# +serialize+, and features like time zone aware attributes.
|
13
|
+
#
|
14
|
+
# Used to wrap the type of an attribute in a new type.
|
15
|
+
# When the schema for a model is loaded, attributes with the same name as
|
16
|
+
# +column_name+ will have their type yielded to the given block. The
|
17
|
+
# return value of that block will be used instead.
|
18
|
+
#
|
19
|
+
# Subsequent calls where +column_name+ and +decorator_name+ are the same
|
20
|
+
# will override the previous decorator, not decorate twice. This can be
|
21
|
+
# used to create idempotent class macros like +serialize+
|
11
22
|
def decorate_attribute_type(column_name, decorator_name, &block)
|
12
23
|
matcher = ->(name, _) { name == column_name.to_s }
|
13
24
|
key = "_#{column_name}_#{decorator_name}"
|
14
25
|
decorate_matching_attribute_types(matcher, key, &block)
|
15
26
|
end
|
16
27
|
|
28
|
+
# This method is an internal API used to create higher level features like
|
29
|
+
# time zone aware attributes.
|
30
|
+
#
|
31
|
+
# When the schema for a model is loaded, +matcher+ will be called for each
|
32
|
+
# attribute with its name and type. If the matcher returns a truthy value,
|
33
|
+
# the type will then be yielded to the given block, and the return value
|
34
|
+
# of that block will replace the type.
|
35
|
+
#
|
36
|
+
# Subsequent calls to this method with the same value for +decorator_name+
|
37
|
+
# will replace the previous decorator, not decorate twice. This can be
|
38
|
+
# used to ensure that class macros are idempotent.
|
17
39
|
def decorate_matching_attribute_types(matcher, decorator_name, &block)
|
18
40
|
reload_schema_from_cache
|
19
41
|
decorator_name = decorator_name.to_s
|
@@ -24,13 +46,13 @@ module ActiveRecord
|
|
24
46
|
|
25
47
|
private
|
26
48
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
49
|
+
def load_schema!
|
50
|
+
super
|
51
|
+
attribute_types.each do |name, type|
|
52
|
+
decorated_type = attribute_type_decorations.apply(name, type)
|
53
|
+
define_attribute(name, decorated_type)
|
54
|
+
end
|
32
55
|
end
|
33
|
-
end
|
34
56
|
end
|
35
57
|
|
36
58
|
class TypeDecorator # :nodoc:
|
@@ -53,15 +75,15 @@ module ActiveRecord
|
|
53
75
|
|
54
76
|
private
|
55
77
|
|
56
|
-
|
57
|
-
|
58
|
-
|
78
|
+
def decorators_for(name, type)
|
79
|
+
matching(name, type).map(&:last)
|
80
|
+
end
|
59
81
|
|
60
|
-
|
61
|
-
|
62
|
-
|
82
|
+
def matching(name, type)
|
83
|
+
@decorations.values.select do |(matcher, _)|
|
84
|
+
matcher.call(name, type)
|
85
|
+
end
|
63
86
|
end
|
64
|
-
end
|
65
87
|
end
|
66
88
|
end
|
67
89
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "active_support/core_ext/enumerable"
|
2
|
+
require "active_support/core_ext/string/filters"
|
3
|
+
require "mutex_m"
|
4
|
+
require "concurrent/map"
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
7
|
# = Active Record Attribute Methods
|
@@ -148,7 +148,7 @@ module ActiveRecord
|
|
148
148
|
# Person.attribute_method?(:age=) # => true
|
149
149
|
# Person.attribute_method?(:nothing) # => false
|
150
150
|
def attribute_method?(attribute)
|
151
|
-
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/,
|
151
|
+
super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, "")))
|
152
152
|
end
|
153
153
|
|
154
154
|
# Returns an array of column names as strings if it's not an abstract class and
|
@@ -161,10 +161,10 @@ module ActiveRecord
|
|
161
161
|
# # => ["id", "created_at", "updated_at", "name", "age"]
|
162
162
|
def attribute_names
|
163
163
|
@attribute_names ||= if !abstract_class? && table_exists?
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
164
|
+
attribute_types.keys
|
165
|
+
else
|
166
|
+
[]
|
167
|
+
end
|
168
168
|
end
|
169
169
|
|
170
170
|
# Returns true if the given attribute exists, otherwise false.
|
@@ -209,13 +209,13 @@ module ActiveRecord
|
|
209
209
|
# end
|
210
210
|
#
|
211
211
|
# person = Person.new
|
212
|
-
# person.respond_to(:name) # => true
|
213
|
-
# person.respond_to(:name=) # => true
|
214
|
-
# person.respond_to(:name?) # => true
|
215
|
-
# person.respond_to('age') # => true
|
216
|
-
# person.respond_to('age=') # => true
|
217
|
-
# person.respond_to('age?') # => true
|
218
|
-
# person.respond_to(:nothing) # => false
|
212
|
+
# person.respond_to?(:name) # => true
|
213
|
+
# person.respond_to?(:name=) # => true
|
214
|
+
# person.respond_to?(:name?) # => true
|
215
|
+
# person.respond_to?('age') # => true
|
216
|
+
# person.respond_to?('age=') # => true
|
217
|
+
# person.respond_to?('age?') # => true
|
218
|
+
# person.respond_to?(:nothing) # => false
|
219
219
|
def respond_to?(name, include_private = false)
|
220
220
|
return false unless super
|
221
221
|
|
@@ -330,8 +330,6 @@ module ActiveRecord
|
|
330
330
|
#
|
331
331
|
# Note: +:id+ is always present.
|
332
332
|
#
|
333
|
-
# Alias for the #read_attribute method.
|
334
|
-
#
|
335
333
|
# class Person < ActiveRecord::Base
|
336
334
|
# belongs_to :organization
|
337
335
|
# end
|
@@ -396,65 +394,58 @@ module ActiveRecord
|
|
396
394
|
|
397
395
|
protected
|
398
396
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
value
|
404
|
-
end
|
405
|
-
|
406
|
-
def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
|
407
|
-
arel_attributes_with_values(attributes_for_create(attribute_names))
|
408
|
-
end
|
397
|
+
def attribute_method?(attr_name) # :nodoc:
|
398
|
+
# We check defined? because Syck calls respond_to? before actually calling initialize.
|
399
|
+
defined?(@attributes) && @attributes.key?(attr_name)
|
400
|
+
end
|
409
401
|
|
410
|
-
|
411
|
-
arel_attributes_with_values(attributes_for_update(attribute_names))
|
412
|
-
end
|
402
|
+
private
|
413
403
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
end
|
404
|
+
def arel_attributes_with_values_for_create(attribute_names)
|
405
|
+
arel_attributes_with_values(attributes_for_create(attribute_names))
|
406
|
+
end
|
418
407
|
|
419
|
-
|
408
|
+
def arel_attributes_with_values_for_update(attribute_names)
|
409
|
+
arel_attributes_with_values(attributes_for_update(attribute_names))
|
410
|
+
end
|
420
411
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
412
|
+
# Returns a Hash of the Arel::Attributes and attribute values that have been
|
413
|
+
# typecasted for use in an Arel insert/update method.
|
414
|
+
def arel_attributes_with_values(attribute_names)
|
415
|
+
attrs = {}
|
416
|
+
arel_table = self.class.arel_table
|
426
417
|
|
427
|
-
|
428
|
-
|
418
|
+
attribute_names.each do |name|
|
419
|
+
attrs[arel_table[name]] = typecasted_attribute_value(name)
|
420
|
+
end
|
421
|
+
attrs
|
429
422
|
end
|
430
|
-
attrs
|
431
|
-
end
|
432
423
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
424
|
+
# Filters the primary keys and readonly attributes from the attribute names.
|
425
|
+
def attributes_for_update(attribute_names)
|
426
|
+
attribute_names.reject do |name|
|
427
|
+
readonly_attribute?(name)
|
428
|
+
end
|
437
429
|
end
|
438
|
-
end
|
439
430
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
431
|
+
# Filters out the primary keys, from the attribute names, when the primary
|
432
|
+
# key is to be generated (e.g. the id attribute has no value).
|
433
|
+
def attributes_for_create(attribute_names)
|
434
|
+
attribute_names.reject do |name|
|
435
|
+
pk_attribute?(name) && id.nil?
|
436
|
+
end
|
445
437
|
end
|
446
|
-
end
|
447
438
|
|
448
|
-
|
449
|
-
|
450
|
-
|
439
|
+
def readonly_attribute?(name)
|
440
|
+
self.class.readonly_attributes.include?(name)
|
441
|
+
end
|
451
442
|
|
452
|
-
|
453
|
-
|
454
|
-
|
443
|
+
def pk_attribute?(name)
|
444
|
+
name == self.class.primary_key
|
445
|
+
end
|
455
446
|
|
456
|
-
|
457
|
-
|
458
|
-
|
447
|
+
def typecasted_attribute_value(name)
|
448
|
+
_read_attribute(name)
|
449
|
+
end
|
459
450
|
end
|
460
451
|
end
|
@@ -63,14 +63,14 @@ module ActiveRecord
|
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
# Handle *_before_type_cast for method_missing.
|
67
|
+
def attribute_before_type_cast(attribute_name)
|
68
|
+
read_attribute_before_type_cast(attribute_name)
|
69
|
+
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def attribute_came_from_user?(attribute_name)
|
72
|
+
@attributes[attribute_name].came_from_user?
|
73
|
+
end
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "active_support/core_ext/module/attribute_accessors"
|
3
|
+
require "active_record/attribute_mutation_tracker"
|
3
4
|
|
4
5
|
module ActiveRecord
|
5
6
|
module AttributeMethods
|
@@ -15,6 +16,18 @@ module ActiveRecord
|
|
15
16
|
|
16
17
|
class_attribute :partial_writes, instance_writer: false
|
17
18
|
self.partial_writes = true
|
19
|
+
|
20
|
+
after_create { changes_internally_applied }
|
21
|
+
after_update { changes_internally_applied }
|
22
|
+
|
23
|
+
# Attribute methods for "changed in last call to save?"
|
24
|
+
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
|
25
|
+
attribute_method_prefix("saved_change_to_")
|
26
|
+
attribute_method_suffix("_before_last_save")
|
27
|
+
|
28
|
+
# Attribute methods for "will change if I call save?"
|
29
|
+
attribute_method_affix(prefix: "will_save_change_to_", suffix: "?")
|
30
|
+
attribute_method_suffix("_change_to_be_saved", "_in_database")
|
18
31
|
end
|
19
32
|
|
20
33
|
# Attempts to +save+ the record and clears changed attributes if successful.
|
@@ -35,8 +48,8 @@ module ActiveRecord
|
|
35
48
|
# <tt>reload</tt> the record and clears changed attributes.
|
36
49
|
def reload(*)
|
37
50
|
super.tap do
|
38
|
-
@mutation_tracker = nil
|
39
51
|
@previous_mutation_tracker = nil
|
52
|
+
clear_mutation_trackers
|
40
53
|
@changed_attributes = HashWithIndifferentAccess.new
|
41
54
|
end
|
42
55
|
end
|
@@ -46,19 +59,26 @@ module ActiveRecord
|
|
46
59
|
@attributes = self.class._default_attributes.map do |attr|
|
47
60
|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
48
61
|
end
|
49
|
-
|
62
|
+
clear_mutation_trackers
|
63
|
+
end
|
64
|
+
|
65
|
+
def changes_internally_applied # :nodoc:
|
66
|
+
@mutations_before_last_save = mutation_tracker
|
67
|
+
forget_attribute_assignments
|
68
|
+
@mutations_from_database = AttributeMutationTracker.new(@attributes)
|
50
69
|
end
|
51
70
|
|
52
71
|
def changes_applied
|
53
72
|
@previous_mutation_tracker = mutation_tracker
|
54
73
|
@changed_attributes = HashWithIndifferentAccess.new
|
55
|
-
|
74
|
+
clear_mutation_trackers
|
56
75
|
end
|
57
76
|
|
58
77
|
def clear_changes_information
|
59
78
|
@previous_mutation_tracker = nil
|
60
79
|
@changed_attributes = HashWithIndifferentAccess.new
|
61
|
-
|
80
|
+
forget_attribute_assignments
|
81
|
+
clear_mutation_trackers
|
62
82
|
end
|
63
83
|
|
64
84
|
def raw_write_attribute(attr_name, *)
|
@@ -80,17 +100,27 @@ module ActiveRecord
|
|
80
100
|
if defined?(@cached_changed_attributes)
|
81
101
|
@cached_changed_attributes
|
82
102
|
else
|
103
|
+
emit_warning_if_needed("changed_attributes", "saved_changes.transform_values(&:first)")
|
83
104
|
super.reverse_merge(mutation_tracker.changed_values).freeze
|
84
105
|
end
|
85
106
|
end
|
86
107
|
|
87
108
|
def changes
|
88
109
|
cache_changed_attributes do
|
110
|
+
emit_warning_if_needed("changes", "saved_changes")
|
89
111
|
super
|
90
112
|
end
|
91
113
|
end
|
92
114
|
|
93
115
|
def previous_changes
|
116
|
+
unless previous_mutation_tracker.equal?(mutations_before_last_save)
|
117
|
+
ActiveSupport::Deprecation.warn(<<-EOW.strip_heredoc)
|
118
|
+
The behavior of `previous_changes` inside of after callbacks is
|
119
|
+
deprecated without replacement. In the next release of Rails,
|
120
|
+
this method inside of `after_save` will return the changes that
|
121
|
+
were just saved.
|
122
|
+
EOW
|
123
|
+
end
|
94
124
|
previous_mutation_tracker.changes
|
95
125
|
end
|
96
126
|
|
@@ -98,54 +128,206 @@ module ActiveRecord
|
|
98
128
|
mutation_tracker.changed_in_place?(attr_name)
|
99
129
|
end
|
100
130
|
|
101
|
-
|
131
|
+
# Did this attribute change when we last saved? This method can be invoked
|
132
|
+
# as `saved_change_to_name?` instead of `saved_change_to_attribute?("name")`.
|
133
|
+
# Behaves similarly to +attribute_changed?+. This method is useful in
|
134
|
+
# after callbacks to determine if the call to save changed a certain
|
135
|
+
# attribute.
|
136
|
+
#
|
137
|
+
# ==== Options
|
138
|
+
#
|
139
|
+
# +from+ When passed, this method will return false unless the original
|
140
|
+
# value is equal to the given option
|
141
|
+
#
|
142
|
+
# +to+ When passed, this method will return false unless the value was
|
143
|
+
# changed to the given value
|
144
|
+
def saved_change_to_attribute?(attr_name, **options)
|
145
|
+
mutations_before_last_save.changed?(attr_name, **options)
|
146
|
+
end
|
102
147
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
148
|
+
# Returns the change to an attribute during the last save. If the
|
149
|
+
# attribute was changed, the result will be an array containing the
|
150
|
+
# original value and the saved value.
|
151
|
+
#
|
152
|
+
# Behaves similarly to +attribute_change+. This method is useful in after
|
153
|
+
# callbacks, to see the change in an attribute that just occurred
|
154
|
+
#
|
155
|
+
# This method can be invoked as `saved_change_to_name` in instead of
|
156
|
+
# `saved_change_to_attribute("name")`
|
157
|
+
def saved_change_to_attribute(attr_name)
|
158
|
+
mutations_before_last_save.change_to_attribute(attr_name)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns the original value of an attribute before the last save.
|
162
|
+
# Behaves similarly to +attribute_was+. This method is useful in after
|
163
|
+
# callbacks to get the original value of an attribute before the save that
|
164
|
+
# just occurred
|
165
|
+
def attribute_before_last_save(attr_name)
|
166
|
+
mutations_before_last_save.original_value(attr_name)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Did the last call to `save` have any changes to change?
|
170
|
+
def saved_changes?
|
171
|
+
mutations_before_last_save.any_changes?
|
108
172
|
end
|
109
173
|
|
110
|
-
|
111
|
-
|
174
|
+
# Returns a hash containing all the changes that were just saved.
|
175
|
+
def saved_changes
|
176
|
+
mutations_before_last_save.changes
|
112
177
|
end
|
113
178
|
|
114
|
-
|
115
|
-
|
179
|
+
# Alias for `attribute_changed?`
|
180
|
+
def will_save_change_to_attribute?(attr_name, **options)
|
181
|
+
mutations_from_database.changed?(attr_name, **options)
|
116
182
|
end
|
117
183
|
|
118
|
-
|
119
|
-
|
184
|
+
# Alias for `attribute_change`
|
185
|
+
def attribute_change_to_be_saved(attr_name)
|
186
|
+
mutations_from_database.change_to_attribute(attr_name)
|
120
187
|
end
|
121
188
|
|
122
|
-
|
123
|
-
|
189
|
+
# Alias for `attribute_was`
|
190
|
+
def attribute_in_database(attr_name)
|
191
|
+
mutations_from_database.original_value(attr_name)
|
124
192
|
end
|
125
193
|
|
126
|
-
|
127
|
-
|
194
|
+
# Alias for `changed?`
|
195
|
+
def has_changes_to_save?
|
196
|
+
mutations_from_database.any_changes?
|
128
197
|
end
|
129
198
|
|
130
|
-
|
131
|
-
|
132
|
-
|
199
|
+
# Alias for `changes`
|
200
|
+
def changes_to_save
|
201
|
+
mutations_from_database.changes
|
133
202
|
end
|
134
203
|
|
135
|
-
|
136
|
-
|
204
|
+
# Alias for `changed`
|
205
|
+
def changed_attribute_names_to_save
|
206
|
+
changes_to_save.keys
|
137
207
|
end
|
138
208
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
ensure
|
143
|
-
clear_changed_attributes_cache
|
209
|
+
# Alias for `changed_attributes`
|
210
|
+
def attributes_in_database
|
211
|
+
changes_to_save.transform_values(&:first)
|
144
212
|
end
|
145
213
|
|
146
|
-
def
|
147
|
-
|
214
|
+
def attribute_was(*)
|
215
|
+
emit_warning_if_needed("attribute_was", "attribute_before_last_save")
|
216
|
+
super
|
148
217
|
end
|
218
|
+
|
219
|
+
def attribute_change(*)
|
220
|
+
emit_warning_if_needed("attribute_change", "saved_change_to_attribute")
|
221
|
+
super
|
222
|
+
end
|
223
|
+
|
224
|
+
def attribute_changed?(*)
|
225
|
+
emit_warning_if_needed("attribute_changed?", "saved_change_to_attribute?")
|
226
|
+
super
|
227
|
+
end
|
228
|
+
|
229
|
+
def changed?(*)
|
230
|
+
emit_warning_if_needed("changed?", "saved_changes?")
|
231
|
+
super
|
232
|
+
end
|
233
|
+
|
234
|
+
def changed(*)
|
235
|
+
emit_warning_if_needed("changed", "saved_changes.keys")
|
236
|
+
super
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def mutation_tracker
|
242
|
+
unless defined?(@mutation_tracker)
|
243
|
+
@mutation_tracker = nil
|
244
|
+
end
|
245
|
+
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
|
246
|
+
end
|
247
|
+
|
248
|
+
def emit_warning_if_needed(method_name, new_method_name)
|
249
|
+
unless mutation_tracker.equal?(mutations_from_database)
|
250
|
+
ActiveSupport::Deprecation.warn(<<-EOW.squish)
|
251
|
+
The behavior of `#{method_name}` inside of after callbacks will
|
252
|
+
be changing in the next version of Rails. The new return value will reflect the
|
253
|
+
behavior of calling the method after `save` returned (e.g. the opposite of what
|
254
|
+
it returns now). To maintain the current behavior, use `#{new_method_name}`
|
255
|
+
instead.
|
256
|
+
EOW
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def mutations_from_database
|
261
|
+
unless defined?(@mutations_from_database)
|
262
|
+
@mutations_from_database = nil
|
263
|
+
end
|
264
|
+
@mutations_from_database ||= mutation_tracker
|
265
|
+
end
|
266
|
+
|
267
|
+
def changes_include?(attr_name)
|
268
|
+
super || mutation_tracker.changed?(attr_name)
|
269
|
+
end
|
270
|
+
|
271
|
+
def clear_attribute_change(attr_name)
|
272
|
+
mutation_tracker.forget_change(attr_name)
|
273
|
+
mutations_from_database.forget_change(attr_name)
|
274
|
+
end
|
275
|
+
|
276
|
+
def attribute_will_change!(attr_name)
|
277
|
+
super
|
278
|
+
if self.class.has_attribute?(attr_name)
|
279
|
+
mutations_from_database.force_change(attr_name)
|
280
|
+
else
|
281
|
+
ActiveSupport::Deprecation.warn(<<-EOW.squish)
|
282
|
+
#{attr_name} is not an attribute known to Active Record.
|
283
|
+
This behavior is deprecated and will be removed in the next
|
284
|
+
version of Rails. If you'd like #{attr_name} to be managed
|
285
|
+
by Active Record, add `attribute :#{attr_name} to your class.
|
286
|
+
EOW
|
287
|
+
mutations_from_database.deprecated_force_change(attr_name)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def _update_record(*)
|
292
|
+
partial_writes? ? super(keys_for_partial_write) : super
|
293
|
+
end
|
294
|
+
|
295
|
+
def _create_record(*)
|
296
|
+
partial_writes? ? super(keys_for_partial_write) : super
|
297
|
+
end
|
298
|
+
|
299
|
+
def keys_for_partial_write
|
300
|
+
changed_attribute_names_to_save & self.class.column_names
|
301
|
+
end
|
302
|
+
|
303
|
+
def forget_attribute_assignments
|
304
|
+
@attributes = @attributes.map(&:forgetting_assignment)
|
305
|
+
end
|
306
|
+
|
307
|
+
def clear_mutation_trackers
|
308
|
+
@mutation_tracker = nil
|
309
|
+
@mutations_from_database = nil
|
310
|
+
@mutations_before_last_save = nil
|
311
|
+
end
|
312
|
+
|
313
|
+
def previous_mutation_tracker
|
314
|
+
@previous_mutation_tracker ||= NullMutationTracker.instance
|
315
|
+
end
|
316
|
+
|
317
|
+
def mutations_before_last_save
|
318
|
+
@mutations_before_last_save ||= previous_mutation_tracker
|
319
|
+
end
|
320
|
+
|
321
|
+
def cache_changed_attributes
|
322
|
+
@cached_changed_attributes = changed_attributes
|
323
|
+
yield
|
324
|
+
ensure
|
325
|
+
clear_changed_attributes_cache
|
326
|
+
end
|
327
|
+
|
328
|
+
def clear_changed_attributes_cache
|
329
|
+
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
330
|
+
end
|
149
331
|
end
|
150
332
|
end
|
151
333
|
end
|