activerecord 6.0.3 → 6.1.3
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 +968 -682
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record.rb +7 -14
- data/lib/active_record/aggregations.rb +5 -5
- data/lib/active_record/association_relation.rb +30 -12
- data/lib/active_record/associations.rb +118 -11
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +44 -28
- data/lib/active_record/associations/association_scope.rb +19 -15
- data/lib/active_record/associations/belongs_to_association.rb +22 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +19 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -5
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -2
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency.rb +72 -50
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/preloader.rb +11 -5
- data/lib/active_record/associations/preloader/association.rb +51 -25
- data/lib/active_record/associations/preloader/through_association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/attribute_assignment.rb +10 -8
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +1 -11
- data/lib/active_record/attribute_methods/primary_key.rb +6 -2
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -11
- data/lib/active_record/attribute_methods/serialization.rb +11 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
- data/lib/active_record/attribute_methods/write.rb +12 -20
- data/lib/active_record/attributes.rb +33 -8
- data/lib/active_record/autosave_association.rb +57 -40
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +1 -1
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
- data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +129 -88
- data/lib/active_record/connection_adapters/column.rb +15 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +74 -63
- data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
- data/lib/active_record/connection_handling.rb +218 -71
- data/lib/active_record/core.rb +245 -61
- data/lib/active_record/database_configurations.rb +124 -85
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -40
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +82 -38
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -4
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -2
- data/lib/active_record/fixtures.rb +58 -9
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +35 -6
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +16 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +33 -17
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +27 -8
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/migration.rb +114 -84
- data/lib/active_record/migration/command_recorder.rb +47 -27
- data/lib/active_record/migration/compatibility.rb +68 -17
- data/lib/active_record/model_schema.rb +117 -13
- data/lib/active_record/nested_attributes.rb +2 -3
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +50 -45
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +11 -6
- data/lib/active_record/railtie.rb +64 -44
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +276 -99
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +71 -57
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/calculations.rb +101 -44
- data/lib/active_record/relation/delegation.rb +2 -1
- data/lib/active_record/relation/finder_methods.rb +45 -15
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +27 -25
- data/lib/active_record/relation/predicate_builder.rb +61 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +333 -195
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -7
- data/lib/active_record/relation/where_clause.rb +107 -60
- data/lib/active_record/result.rb +41 -33
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/named.rb +6 -17
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +20 -4
- data/lib/active_record/store.rb +2 -2
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -51
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +37 -16
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +19 -66
- data/lib/active_record/type.rb +8 -1
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/arel.rb +5 -13
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/predications.rb +12 -18
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel/visitors/dot.rb +14 -2
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -4
- data/lib/arel/visitors/to_sql.rb +89 -78
- data/lib/rails/generators/active_record/migration.rb +6 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +28 -29
- data/lib/active_record/advisory_lock_base.rb +0 -18
- data/lib/active_record/attribute_decorators.rb +0 -88
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -203
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -156
- data/lib/arel/visitors/oracle.rb +0 -158
- data/lib/arel/visitors/oracle12.rb +0 -65
- data/lib/arel/visitors/where_sql.rb +0 -22
@@ -29,7 +29,7 @@ module ActiveRecord
|
|
29
29
|
extend ActiveSupport::Concern
|
30
30
|
|
31
31
|
included do
|
32
|
-
attribute_method_suffix "_before_type_cast"
|
32
|
+
attribute_method_suffix "_before_type_cast", "_for_database"
|
33
33
|
attribute_method_suffix "_came_from_user?"
|
34
34
|
end
|
35
35
|
|
@@ -46,8 +46,10 @@ module ActiveRecord
|
|
46
46
|
# task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
|
47
47
|
# task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
|
48
48
|
def read_attribute_before_type_cast(attr_name)
|
49
|
-
|
50
|
-
|
49
|
+
name = attr_name.to_s
|
50
|
+
name = self.class.attribute_aliases[name] || name
|
51
|
+
|
52
|
+
attribute_before_type_cast(name)
|
51
53
|
end
|
52
54
|
|
53
55
|
# Returns a hash of attributes before typecasting and deserialization.
|
@@ -61,19 +63,21 @@ module ActiveRecord
|
|
61
63
|
# task.attributes_before_type_cast
|
62
64
|
# # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
|
63
65
|
def attributes_before_type_cast
|
64
|
-
sync_with_transaction_state if @transaction_state&.finalized?
|
65
66
|
@attributes.values_before_type_cast
|
66
67
|
end
|
67
68
|
|
68
69
|
private
|
69
70
|
# Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
|
70
|
-
def attribute_before_type_cast(
|
71
|
-
|
71
|
+
def attribute_before_type_cast(attr_name)
|
72
|
+
@attributes[attr_name].value_before_type_cast
|
73
|
+
end
|
74
|
+
|
75
|
+
def attribute_for_database(attr_name)
|
76
|
+
@attributes[attr_name].value_for_database
|
72
77
|
end
|
73
78
|
|
74
|
-
def attribute_came_from_user?(
|
75
|
-
|
76
|
-
@attributes[attribute_name].came_from_user?
|
79
|
+
def attribute_came_from_user?(attr_name)
|
80
|
+
@attributes[attr_name].came_from_user?
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
@@ -89,7 +89,7 @@ module ActiveRecord
|
|
89
89
|
# This method is useful in validations and before callbacks to determine
|
90
90
|
# if the next call to +save+ will change a particular attribute. It can be
|
91
91
|
# invoked as +will_save_change_to_name?+ instead of
|
92
|
-
# <tt>will_save_change_to_attribute("name")</tt>.
|
92
|
+
# <tt>will_save_change_to_attribute?("name")</tt>.
|
93
93
|
#
|
94
94
|
# ==== Options
|
95
95
|
#
|
@@ -156,16 +156,6 @@ module ActiveRecord
|
|
156
156
|
end
|
157
157
|
|
158
158
|
private
|
159
|
-
def mutations_from_database
|
160
|
-
sync_with_transaction_state if @transaction_state&.finalized?
|
161
|
-
super
|
162
|
-
end
|
163
|
-
|
164
|
-
def mutations_before_last_save
|
165
|
-
sync_with_transaction_state if @transaction_state&.finalized?
|
166
|
-
super
|
167
|
-
end
|
168
|
-
|
169
159
|
def write_attribute_without_type_cast(attr_name, value)
|
170
160
|
result = super
|
171
161
|
clear_attribute_change(attr_name)
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
|
32
32
|
# Returns the primary key column's value before type cast.
|
33
33
|
def id_before_type_cast
|
34
|
-
|
34
|
+
attribute_before_type_cast(@primary_key)
|
35
35
|
end
|
36
36
|
|
37
37
|
# Returns the primary key column's previous value.
|
@@ -44,13 +44,17 @@ module ActiveRecord
|
|
44
44
|
attribute_in_database(@primary_key)
|
45
45
|
end
|
46
46
|
|
47
|
+
def id_for_database # :nodoc:
|
48
|
+
@attributes[@primary_key].value_for_database
|
49
|
+
end
|
50
|
+
|
47
51
|
private
|
48
52
|
def attribute_method?(attr_name)
|
49
53
|
attr_name == "id" || super
|
50
54
|
end
|
51
55
|
|
52
56
|
module ClassMethods
|
53
|
-
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
|
57
|
+
ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
|
54
58
|
|
55
59
|
def instance_method_already_implemented?(method_name)
|
56
60
|
super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
when false, nil then false
|
18
18
|
else
|
19
19
|
if !type_for_attribute(attr_name) { false }
|
20
|
-
if Numeric === value || value
|
20
|
+
if Numeric === value || !value.match?(/[^0-9]/)
|
21
21
|
!value.to_i.zero?
|
22
22
|
else
|
23
23
|
return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
|
@@ -31,11 +31,8 @@ module ActiveRecord
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
def attribute?(attribute_name)
|
37
|
-
query_attribute(attribute_name)
|
38
|
-
end
|
34
|
+
alias :attribute? :query_attribute
|
35
|
+
private :attribute?
|
39
36
|
end
|
40
37
|
end
|
41
38
|
end
|
@@ -7,16 +7,14 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
module ClassMethods # :nodoc:
|
9
9
|
private
|
10
|
-
def define_method_attribute(name)
|
10
|
+
def define_method_attribute(name, owner:)
|
11
11
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
12
|
-
|
12
|
+
owner, name
|
13
13
|
) do |temp_method_name, attr_name_expr|
|
14
|
-
|
15
|
-
def #{temp_method_name}
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
RUBY
|
14
|
+
owner <<
|
15
|
+
"def #{temp_method_name}" <<
|
16
|
+
" _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
|
17
|
+
"end"
|
20
18
|
end
|
21
19
|
end
|
22
20
|
end
|
@@ -29,14 +27,13 @@ module ActiveRecord
|
|
29
27
|
name = self.class.attribute_aliases[name] || name
|
30
28
|
|
31
29
|
name = @primary_key if name == "id" && @primary_key
|
32
|
-
|
30
|
+
@attributes.fetch_value(name, &block)
|
33
31
|
end
|
34
32
|
|
35
33
|
# This method exists to avoid the expensive primary_key check internally, without
|
36
34
|
# breaking compatibility with the read_attribute API
|
37
35
|
def _read_attribute(attr_name, &block) # :nodoc
|
38
|
-
|
39
|
-
@attributes.fetch_value(attr_name.to_s, &block)
|
36
|
+
@attributes.fetch_value(attr_name, &block)
|
40
37
|
end
|
41
38
|
|
42
39
|
alias :attribute :_read_attribute
|
@@ -41,6 +41,12 @@ module ActiveRecord
|
|
41
41
|
# * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+
|
42
42
|
# or a class name that the object type should be equal to.
|
43
43
|
#
|
44
|
+
# ==== Options
|
45
|
+
#
|
46
|
+
# +default+ The default value to use when no value is provided. If this option
|
47
|
+
# is not passed, the previous default value (if any) will be used.
|
48
|
+
# Otherwise, the default will be +nil+.
|
49
|
+
#
|
44
50
|
# ==== Example
|
45
51
|
#
|
46
52
|
# # Serialize a preferences attribute.
|
@@ -57,7 +63,7 @@ module ActiveRecord
|
|
57
63
|
# class User < ActiveRecord::Base
|
58
64
|
# serialize :preferences, Hash
|
59
65
|
# end
|
60
|
-
def serialize(attr_name, class_name_or_coder = Object)
|
66
|
+
def serialize(attr_name, class_name_or_coder = Object, **options)
|
61
67
|
# When ::JSON is used, force it to go through the Active Support JSON encoder
|
62
68
|
# to ensure special objects (e.g. Active Record models) are dumped correctly
|
63
69
|
# using the #as_json hook.
|
@@ -69,12 +75,12 @@ module ActiveRecord
|
|
69
75
|
Coders::YAMLColumn.new(attr_name, class_name_or_coder)
|
70
76
|
end
|
71
77
|
|
72
|
-
decorate_attribute_type(attr_name,
|
73
|
-
if type_incompatible_with_serialize?(
|
74
|
-
raise ColumnNotSerializableError.new(attr_name,
|
78
|
+
decorate_attribute_type(attr_name.to_s, **options) do |cast_type|
|
79
|
+
if type_incompatible_with_serialize?(cast_type, class_name_or_coder)
|
80
|
+
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
75
81
|
end
|
76
82
|
|
77
|
-
Type::Serialized.new(
|
83
|
+
Type::Serialized.new(cast_type, coder)
|
78
84
|
end
|
79
85
|
end
|
80
86
|
|
@@ -1,9 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module AttributeMethods
|
5
7
|
module TimeZoneConversion
|
6
8
|
class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc:
|
9
|
+
def self.new(subtype)
|
10
|
+
self === subtype ? subtype : super
|
11
|
+
end
|
12
|
+
|
7
13
|
def deserialize(value)
|
8
14
|
convert_time_to_time_zone(super)
|
9
15
|
end
|
@@ -62,21 +68,14 @@ module ActiveRecord
|
|
62
68
|
end
|
63
69
|
|
64
70
|
module ClassMethods # :nodoc:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
# We need to apply this decorator here, rather than on module inclusion. The closure
|
69
|
-
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
|
70
|
-
# sub class being decorated. As such, changes to `time_zone_aware_attributes`, or
|
71
|
-
# `skip_time_zone_conversion_for_attributes` would not be picked up.
|
72
|
-
subclass.class_eval do
|
73
|
-
matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
|
74
|
-
decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
|
75
|
-
TimeZoneConverter.new(type)
|
76
|
-
end
|
77
|
-
end
|
71
|
+
def define_attribute(name, cast_type, **)
|
72
|
+
if create_time_zone_conversion_attribute?(name, cast_type)
|
73
|
+
cast_type = TimeZoneConverter.new(cast_type)
|
78
74
|
end
|
75
|
+
super
|
76
|
+
end
|
79
77
|
|
78
|
+
private
|
80
79
|
def create_time_zone_conversion_attribute?(name, cast_type)
|
81
80
|
enabled_for_column = time_zone_aware_attributes &&
|
82
81
|
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
@@ -11,16 +11,14 @@ module ActiveRecord
|
|
11
11
|
|
12
12
|
module ClassMethods # :nodoc:
|
13
13
|
private
|
14
|
-
def define_method_attribute=(name)
|
14
|
+
def define_method_attribute=(name, owner:)
|
15
15
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
16
|
-
|
16
|
+
owner, name, writer: true,
|
17
17
|
) do |temp_method_name, attr_name_expr|
|
18
|
-
|
19
|
-
def #{temp_method_name}(value)
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
RUBY
|
18
|
+
owner <<
|
19
|
+
"def #{temp_method_name}(value)" <<
|
20
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
21
|
+
"end"
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
@@ -33,27 +31,21 @@ module ActiveRecord
|
|
33
31
|
name = self.class.attribute_aliases[name] || name
|
34
32
|
|
35
33
|
name = @primary_key if name == "id" && @primary_key
|
36
|
-
|
34
|
+
@attributes.write_from_user(name, value)
|
37
35
|
end
|
38
36
|
|
39
37
|
# This method exists to avoid the expensive primary_key check internally, without
|
40
38
|
# breaking compatibility with the write_attribute API
|
41
39
|
def _write_attribute(attr_name, value) # :nodoc:
|
42
|
-
|
43
|
-
@attributes.write_from_user(attr_name.to_s, value)
|
44
|
-
value
|
40
|
+
@attributes.write_from_user(attr_name, value)
|
45
41
|
end
|
46
42
|
|
43
|
+
alias :attribute= :_write_attribute
|
44
|
+
private :attribute=
|
45
|
+
|
47
46
|
private
|
48
47
|
def write_attribute_without_type_cast(attr_name, value)
|
49
|
-
|
50
|
-
@attributes.write_cast_value(attr_name.to_s, value)
|
51
|
-
value
|
52
|
-
end
|
53
|
-
|
54
|
-
# Dispatch target for <tt>*=</tt> attribute methods.
|
55
|
-
def attribute=(attribute_name, value)
|
56
|
-
_write_attribute(attribute_name, value)
|
48
|
+
@attributes.write_cast_value(attr_name, value)
|
57
49
|
end
|
58
50
|
end
|
59
51
|
end
|
@@ -12,6 +12,9 @@ module ActiveRecord
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
|
+
##
|
16
|
+
# :call-seq: attribute(name, cast_type = nil, **options)
|
17
|
+
#
|
15
18
|
# Defines an attribute with a type on this model. It will override the
|
16
19
|
# type of existing attributes if needed. This allows control over how
|
17
20
|
# values are converted to and from SQL when assigned to a model. It also
|
@@ -170,7 +173,7 @@ module ActiveRecord
|
|
170
173
|
# class Money < Struct.new(:amount, :currency)
|
171
174
|
# end
|
172
175
|
#
|
173
|
-
# class MoneyType < Type::Value
|
176
|
+
# class MoneyType < ActiveRecord::Type::Value
|
174
177
|
# def initialize(currency_converter:)
|
175
178
|
# @currency_converter = currency_converter
|
176
179
|
# end
|
@@ -205,13 +208,13 @@ module ActiveRecord
|
|
205
208
|
# tracking is performed. The methods +changed?+ and +changed_in_place?+
|
206
209
|
# will be called from ActiveModel::Dirty. See the documentation for those
|
207
210
|
# methods in ActiveModel::Type::Value for more details.
|
208
|
-
def attribute(name, cast_type =
|
211
|
+
def attribute(name, cast_type = nil, **options, &block)
|
209
212
|
name = name.to_s
|
210
213
|
reload_schema_from_cache
|
211
214
|
|
212
215
|
self.attributes_to_define_after_schema_loads =
|
213
216
|
attributes_to_define_after_schema_loads.merge(
|
214
|
-
name => [cast_type, options]
|
217
|
+
name => [cast_type || block, options]
|
215
218
|
)
|
216
219
|
end
|
217
220
|
|
@@ -246,11 +249,7 @@ module ActiveRecord
|
|
246
249
|
def load_schema! # :nodoc:
|
247
250
|
super
|
248
251
|
attributes_to_define_after_schema_loads.each do |name, (type, options)|
|
249
|
-
|
250
|
-
type = ActiveRecord::Type.lookup(type, **options.except(:default))
|
251
|
-
end
|
252
|
-
|
253
|
-
define_attribute(name, type, **options.slice(:default))
|
252
|
+
define_attribute(name, _lookup_cast_type(name, type, options), **options.slice(:default))
|
254
253
|
end
|
255
254
|
end
|
256
255
|
|
@@ -273,6 +272,32 @@ module ActiveRecord
|
|
273
272
|
end
|
274
273
|
_default_attributes[name] = default_attribute
|
275
274
|
end
|
275
|
+
|
276
|
+
def decorate_attribute_type(attr_name, **default)
|
277
|
+
type, options = attributes_to_define_after_schema_loads[attr_name]
|
278
|
+
|
279
|
+
default.with_defaults!(default: options[:default]) if options&.key?(:default)
|
280
|
+
|
281
|
+
attribute(attr_name, **default) do |cast_type|
|
282
|
+
if type && !type.is_a?(Proc)
|
283
|
+
cast_type = _lookup_cast_type(attr_name, type, options)
|
284
|
+
end
|
285
|
+
|
286
|
+
yield cast_type
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def _lookup_cast_type(name, type, options)
|
291
|
+
case type
|
292
|
+
when Symbol
|
293
|
+
adapter_name = ActiveRecord::Type.adapter_name_from(self)
|
294
|
+
ActiveRecord::Type.lookup(type, **options.except(:default), adapter: adapter_name)
|
295
|
+
when Proc
|
296
|
+
type[type_for_attribute(name)]
|
297
|
+
else
|
298
|
+
type || type_for_attribute(name)
|
299
|
+
end
|
300
|
+
end
|
276
301
|
end
|
277
302
|
end
|
278
303
|
end
|
@@ -29,9 +29,9 @@ module ActiveRecord
|
|
29
29
|
# == Callbacks
|
30
30
|
#
|
31
31
|
# Association with autosave option defines several callbacks on your
|
32
|
-
# model (before_save, after_create, after_update). Please note that
|
32
|
+
# model (around_save, before_save, after_create, after_update). Please note that
|
33
33
|
# callbacks are executed in the order they were defined in
|
34
|
-
# model. You should avoid modifying the association content
|
34
|
+
# model. You should avoid modifying the association content before
|
35
35
|
# autosave callbacks are executed. Placing your callbacks after
|
36
36
|
# associations is usually a good practice.
|
37
37
|
#
|
@@ -91,8 +91,9 @@ module ActiveRecord
|
|
91
91
|
# post.save # => saves both post and comment
|
92
92
|
#
|
93
93
|
# post = Post.create(title: 'ruby rocks')
|
94
|
-
# post.comments.create(body: 'hello world')
|
95
|
-
#
|
94
|
+
# comment = post.comments.create(body: 'hello world')
|
95
|
+
# comment.body = 'hi everyone'
|
96
|
+
# post.save # => saves post, but not comment
|
96
97
|
#
|
97
98
|
# When <tt>:autosave</tt> is true all children are saved, no matter whether they
|
98
99
|
# are new records or not:
|
@@ -102,11 +103,10 @@ module ActiveRecord
|
|
102
103
|
# end
|
103
104
|
#
|
104
105
|
# post = Post.create(title: 'ruby rocks')
|
105
|
-
# post.comments.create(body: 'hello world')
|
106
|
-
#
|
106
|
+
# comment = post.comments.create(body: 'hello world')
|
107
|
+
# comment.body = 'hi everyone'
|
107
108
|
# post.comments.build(body: "good morning.")
|
108
|
-
# post.
|
109
|
-
# post.save # => saves both post and comments.
|
109
|
+
# post.save # => saves post and both comments.
|
110
110
|
#
|
111
111
|
# Destroying one of the associated models as part of the parent's save action
|
112
112
|
# is as simple as marking it for destruction:
|
@@ -127,6 +127,14 @@ module ActiveRecord
|
|
127
127
|
# Now it _is_ removed from the database:
|
128
128
|
#
|
129
129
|
# Comment.find_by(id: id).nil? # => true
|
130
|
+
#
|
131
|
+
# === Caveats
|
132
|
+
#
|
133
|
+
# Note that autosave will only trigger for already-persisted association records
|
134
|
+
# if the records themselves have been changed. This is to protect against
|
135
|
+
# <tt>SystemStackError</tt> caused by circular association validations. The one
|
136
|
+
# exception is if a custom validation context is used, in which case the validations
|
137
|
+
# will always fire on the associated records.
|
130
138
|
module AutosaveAssociation
|
131
139
|
extend ActiveSupport::Concern
|
132
140
|
|
@@ -147,8 +155,23 @@ module ActiveRecord
|
|
147
155
|
|
148
156
|
module ClassMethods # :nodoc:
|
149
157
|
private
|
158
|
+
if Module.method(:method_defined?).arity == 1 # MRI 2.5 and older
|
159
|
+
using Module.new {
|
160
|
+
refine Module do
|
161
|
+
def method_defined?(method, inherit = true)
|
162
|
+
if inherit
|
163
|
+
super(method)
|
164
|
+
else
|
165
|
+
instance_methods(false).include?(method.to_sym)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
150
172
|
def define_non_cyclic_method(name, &block)
|
151
|
-
return if
|
173
|
+
return if method_defined?(name, false)
|
174
|
+
|
152
175
|
define_method(name) do |*args|
|
153
176
|
result = true; @_already_called ||= {}
|
154
177
|
# Loop prevention for validation of associations
|
@@ -180,8 +203,7 @@ module ActiveRecord
|
|
180
203
|
save_method = :"autosave_associated_records_for_#{reflection.name}"
|
181
204
|
|
182
205
|
if reflection.collection?
|
183
|
-
|
184
|
-
after_save :after_save_collection_association
|
206
|
+
around_save :around_save_collection_association
|
185
207
|
|
186
208
|
define_non_cyclic_method(save_method) { save_collection_association(reflection) }
|
187
209
|
# Doesn't use after_save as that would save associations added in after_create/after_update twice
|
@@ -279,8 +301,9 @@ module ActiveRecord
|
|
279
301
|
end
|
280
302
|
end
|
281
303
|
|
282
|
-
#
|
283
|
-
# any new ones), and return true if
|
304
|
+
# Go through nested autosave associations that are loaded in memory (without loading
|
305
|
+
# any new ones), and return true if any are changed for autosave.
|
306
|
+
# Returns false if already called to prevent an infinite loop.
|
284
307
|
def nested_records_changed_for_autosave?
|
285
308
|
@_nested_records_changed_for_autosave_already_called ||= false
|
286
309
|
return false if @_nested_records_changed_for_autosave_already_called
|
@@ -328,21 +351,16 @@ module ActiveRecord
|
|
328
351
|
if reflection.options[:autosave]
|
329
352
|
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
|
330
353
|
|
331
|
-
record.errors.each
|
354
|
+
record.errors.group_by_attribute.each { |attribute, errors|
|
332
355
|
attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
333
|
-
errors[attribute] << message
|
334
|
-
errors[attribute].uniq!
|
335
|
-
end
|
336
356
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
345
|
-
end
|
357
|
+
errors.each { |error|
|
358
|
+
self.errors.import(
|
359
|
+
error,
|
360
|
+
attribute: attribute
|
361
|
+
)
|
362
|
+
}
|
363
|
+
}
|
346
364
|
else
|
347
365
|
errors.add(reflection.name)
|
348
366
|
end
|
@@ -358,14 +376,15 @@ module ActiveRecord
|
|
358
376
|
end
|
359
377
|
end
|
360
378
|
|
361
|
-
# Is used as
|
379
|
+
# Is used as an around_save callback to check while saving a collection
|
362
380
|
# association whether or not the parent was a new record before saving.
|
363
|
-
def
|
364
|
-
@new_record_before_save ||=
|
365
|
-
|
381
|
+
def around_save_collection_association
|
382
|
+
previously_new_record_before_save = (@new_record_before_save ||= false)
|
383
|
+
@new_record_before_save = !previously_new_record_before_save && new_record?
|
366
384
|
|
367
|
-
|
368
|
-
|
385
|
+
yield
|
386
|
+
ensure
|
387
|
+
@new_record_before_save = previously_new_record_before_save
|
369
388
|
end
|
370
389
|
|
371
390
|
# Saves any new associated records, or all loaded autosave associations if
|
@@ -438,13 +457,13 @@ module ActiveRecord
|
|
438
457
|
if autosave && record.marked_for_destruction?
|
439
458
|
record.destroy
|
440
459
|
elsif autosave != false
|
441
|
-
key = reflection.options[:primary_key] ?
|
460
|
+
key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id
|
442
461
|
|
443
|
-
if (autosave && record.changed_for_autosave?) ||
|
462
|
+
if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key)
|
444
463
|
unless reflection.through_reflection
|
445
464
|
record[reflection.foreign_key] = key
|
446
465
|
if inverse_reflection = reflection.inverse_of
|
447
|
-
record.association(inverse_reflection.name).
|
466
|
+
record.association(inverse_reflection.name).inversed_from(self)
|
448
467
|
end
|
449
468
|
end
|
450
469
|
|
@@ -466,7 +485,7 @@ module ActiveRecord
|
|
466
485
|
def association_foreign_key_changed?(reflection, record, key)
|
467
486
|
return false if reflection.through_reflection?
|
468
487
|
|
469
|
-
record.
|
488
|
+
record._has_attribute?(reflection.foreign_key) && record._read_attribute(reflection.foreign_key) != key
|
470
489
|
end
|
471
490
|
|
472
491
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
@@ -487,7 +506,7 @@ module ActiveRecord
|
|
487
506
|
saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
|
488
507
|
|
489
508
|
if association.updated?
|
490
|
-
association_id = record.
|
509
|
+
association_id = record.public_send(reflection.options[:primary_key] || :id)
|
491
510
|
self[reflection.foreign_key] = association_id
|
492
511
|
association.loaded!
|
493
512
|
end
|
@@ -502,9 +521,7 @@ module ActiveRecord
|
|
502
521
|
end
|
503
522
|
|
504
523
|
def _ensure_no_duplicate_errors
|
505
|
-
errors.
|
506
|
-
errors[attribute].uniq!
|
507
|
-
end
|
524
|
+
errors.uniq!
|
508
525
|
end
|
509
526
|
end
|
510
527
|
end
|