activerecord 7.1.3.2 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +570 -2094
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +6 -8
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +34 -273
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +60 -45
- data/lib/active_record/autosave_association.rb +17 -31
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +244 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +188 -75
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +22 -9
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +69 -19
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +16 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +60 -39
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +8 -4
- data/lib/active_record/enum.rb +26 -6
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +39 -70
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +54 -67
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +102 -37
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +95 -62
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +212 -47
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +500 -66
- data/lib/active_record/result.rb +32 -45
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +87 -48
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -12
@@ -8,11 +8,11 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
module ClassMethods # :nodoc:
|
10
10
|
private
|
11
|
-
def define_method_attribute(
|
11
|
+
def define_method_attribute(canonical_name, owner:, as: canonical_name)
|
12
12
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
13
|
-
owner,
|
13
|
+
owner, canonical_name
|
14
14
|
) do |temp_method_name, attr_name_expr|
|
15
|
-
owner.define_cached_method(
|
15
|
+
owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
|
16
16
|
batch <<
|
17
17
|
"def #{temp_method_name}" <<
|
18
18
|
" _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
|
@@ -30,19 +30,7 @@ module ActiveRecord
|
|
30
30
|
name = attr_name.to_s
|
31
31
|
name = self.class.attribute_aliases[name] || name
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
if self.class.composite_primary_key?
|
36
|
-
@attributes.fetch_value("id", &block)
|
37
|
-
else
|
38
|
-
if @primary_key != "id"
|
39
|
-
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
40
|
-
Using read_attribute(:id) to read the primary key value is deprecated.
|
41
|
-
Use #id instead.
|
42
|
-
MSG
|
43
|
-
end
|
44
|
-
@attributes.fetch_value(@primary_key, &block)
|
45
|
-
end
|
33
|
+
@attributes.fetch_value(name, &block)
|
46
34
|
end
|
47
35
|
|
48
36
|
# This method exists to avoid the expensive primary_key check internally, without
|
@@ -180,29 +180,7 @@ module ActiveRecord
|
|
180
180
|
# serialize :preferences, coder: Rot13JSON
|
181
181
|
# end
|
182
182
|
#
|
183
|
-
def serialize(attr_name,
|
184
|
-
unless class_name_or_coder.nil?
|
185
|
-
if class_name_or_coder == ::JSON || [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
|
186
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
187
|
-
Passing the coder as positional argument is deprecated and will be removed in Rails 7.2.
|
188
|
-
|
189
|
-
Please pass the coder as a keyword argument:
|
190
|
-
|
191
|
-
serialize #{attr_name.inspect}, coder: #{class_name_or_coder}
|
192
|
-
MSG
|
193
|
-
coder = class_name_or_coder
|
194
|
-
else
|
195
|
-
ActiveRecord.deprecator.warn(<<~MSG)
|
196
|
-
Passing the class as positional argument is deprecated and will be removed in Rails 7.2.
|
197
|
-
|
198
|
-
Please pass the class as a keyword argument:
|
199
|
-
|
200
|
-
serialize #{attr_name.inspect}, type: #{class_name_or_coder.name}
|
201
|
-
MSG
|
202
|
-
type = class_name_or_coder
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
183
|
+
def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
|
206
184
|
coder ||= default_column_serializer
|
207
185
|
unless coder
|
208
186
|
raise ArgumentError, <<~MSG.squish
|
@@ -214,7 +192,9 @@ module ActiveRecord
|
|
214
192
|
|
215
193
|
column_serializer = build_column_serializer(attr_name, coder, type, yaml)
|
216
194
|
|
217
|
-
attribute(attr_name, **options)
|
195
|
+
attribute(attr_name, **options)
|
196
|
+
|
197
|
+
decorate_attributes([attr_name]) do |attr_name, cast_type|
|
218
198
|
if type_incompatible_with_serialize?(cast_type, coder, type)
|
219
199
|
raise ColumnNotSerializableError.new(attr_name, cast_type)
|
220
200
|
end
|
@@ -69,14 +69,15 @@ module ActiveRecord
|
|
69
69
|
end
|
70
70
|
|
71
71
|
module ClassMethods # :nodoc:
|
72
|
-
|
73
|
-
|
74
|
-
|
72
|
+
private
|
73
|
+
def hook_attribute_type(name, cast_type)
|
74
|
+
if create_time_zone_conversion_attribute?(name, cast_type)
|
75
|
+
cast_type = TimeZoneConverter.new(cast_type)
|
76
|
+
end
|
77
|
+
|
78
|
+
super
|
75
79
|
end
|
76
|
-
super
|
77
|
-
end
|
78
80
|
|
79
|
-
private
|
80
81
|
def create_time_zone_conversion_attribute?(name, cast_type)
|
81
82
|
enabled_for_column = time_zone_aware_attributes &&
|
82
83
|
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
@@ -12,11 +12,11 @@ module ActiveRecord
|
|
12
12
|
|
13
13
|
module ClassMethods # :nodoc:
|
14
14
|
private
|
15
|
-
def define_method_attribute=(
|
15
|
+
def define_method_attribute=(canonical_name, owner:, as: canonical_name)
|
16
16
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
17
|
-
owner,
|
17
|
+
owner, canonical_name, writer: true,
|
18
18
|
) do |temp_method_name, attr_name_expr|
|
19
|
-
owner.define_cached_method("#{
|
19
|
+
owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
|
20
20
|
batch <<
|
21
21
|
"def #{temp_method_name}(value)" <<
|
22
22
|
" _write_attribute(#{attr_name_expr}, value)" <<
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "mutex_m"
|
4
3
|
require "active_support/core_ext/enumerable"
|
5
4
|
|
6
5
|
module ActiveRecord
|
@@ -21,10 +20,10 @@ module ActiveRecord
|
|
21
20
|
include Serialization
|
22
21
|
end
|
23
22
|
|
24
|
-
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name
|
23
|
+
RESTRICTED_CLASS_METHODS = %w(private public protected allocate new name superclass)
|
25
24
|
|
26
25
|
class GeneratedAttributeMethods < Module # :nodoc:
|
27
|
-
|
26
|
+
LOCK = Monitor.new
|
28
27
|
end
|
29
28
|
|
30
29
|
class << self
|
@@ -50,6 +49,20 @@ module ActiveRecord
|
|
50
49
|
super
|
51
50
|
end
|
52
51
|
|
52
|
+
# Allows you to make aliases for attributes.
|
53
|
+
#
|
54
|
+
# class Person < ActiveRecord::Base
|
55
|
+
# alias_attribute :nickname, :name
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# person = Person.create(name: 'Bob')
|
59
|
+
# person.name # => "Bob"
|
60
|
+
# person.nickname # => "Bob"
|
61
|
+
#
|
62
|
+
# The alias can also be used for querying:
|
63
|
+
#
|
64
|
+
# Person.where(nickname: "Bob")
|
65
|
+
# # SELECT "people".* FROM "people" WHERE "people"."name" = "Bob"
|
53
66
|
def alias_attribute(new_name, old_name)
|
54
67
|
super
|
55
68
|
|
@@ -64,80 +77,71 @@ module ActiveRecord
|
|
64
77
|
# alias attributes in Active Record are lazily generated
|
65
78
|
end
|
66
79
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
generated_attribute_methods.synchronize do
|
72
|
-
return if @alias_attributes_mass_generated
|
73
|
-
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
74
|
-
aliases_by_attribute_name.each do |old_name, new_names|
|
75
|
-
new_names.each do |new_name|
|
76
|
-
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
@alias_attributes_mass_generated = true
|
80
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
|
81
|
+
attribute_method_patterns.each do |pattern|
|
82
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
82
83
|
end
|
84
|
+
attribute_method_patterns_cache.clear
|
83
85
|
end
|
84
86
|
|
85
87
|
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
86
|
-
method_name = pattern.method_name(new_name).to_s
|
87
|
-
target_name = pattern.method_name(old_name).to_s
|
88
|
-
parameters = pattern.parameters
|
89
88
|
old_name = old_name.to_s
|
90
89
|
|
91
|
-
method_defined = method_defined?(target_name) || private_method_defined?(target_name)
|
92
|
-
manually_defined = method_defined &&
|
93
|
-
!self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
|
94
|
-
reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
|
95
|
-
|
96
90
|
if !abstract_class? && !has_attribute?(old_name)
|
97
|
-
|
98
|
-
|
99
|
-
if should_warn
|
100
|
-
ActiveRecord.deprecator.warn(
|
101
|
-
"#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
|
102
|
-
"Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
|
103
|
-
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
|
104
|
-
)
|
105
|
-
end
|
106
|
-
super
|
107
|
-
elsif manually_defined && !reserved_method_name
|
108
|
-
aliased_method_redefined_as_well = method_defined_within?(method_name, self)
|
109
|
-
return if aliased_method_redefined_as_well
|
110
|
-
|
111
|
-
ActiveRecord.deprecator.warn(
|
112
|
-
"#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
|
113
|
-
"Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
|
114
|
-
"You may want to additionally define `#{method_name}` to preserve the current behavior."
|
115
|
-
)
|
116
|
-
super
|
91
|
+
raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
|
92
|
+
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
|
117
93
|
else
|
118
|
-
|
119
|
-
namespace: :proxy_alias_attribute
|
120
|
-
)
|
94
|
+
define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
|
121
95
|
end
|
122
96
|
end
|
123
97
|
|
98
|
+
def attribute_methods_generated? # :nodoc:
|
99
|
+
@attribute_methods_generated
|
100
|
+
end
|
101
|
+
|
124
102
|
# Generates all the attribute related methods for columns in the database
|
125
103
|
# accessors, mutators and query methods.
|
126
104
|
def define_attribute_methods # :nodoc:
|
127
105
|
return false if @attribute_methods_generated
|
128
106
|
# Use a mutex; we don't want two threads simultaneously trying to define
|
129
107
|
# attribute methods.
|
130
|
-
|
108
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
131
109
|
return false if @attribute_methods_generated
|
110
|
+
|
132
111
|
superclass.define_attribute_methods unless base_class?
|
133
|
-
|
112
|
+
|
113
|
+
unless abstract_class?
|
114
|
+
load_schema
|
115
|
+
super(attribute_names)
|
116
|
+
alias_attribute :id_value, :id if _has_attribute?("id")
|
117
|
+
end
|
118
|
+
|
134
119
|
@attribute_methods_generated = true
|
120
|
+
|
121
|
+
generate_alias_attributes
|
122
|
+
end
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def generate_alias_attributes # :nodoc:
|
127
|
+
superclass.generate_alias_attributes unless superclass == Base
|
128
|
+
|
129
|
+
return if @alias_attributes_mass_generated
|
130
|
+
|
131
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
132
|
+
aliases_by_attribute_name.each do |old_name, new_names|
|
133
|
+
new_names.each do |new_name|
|
134
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
135
|
+
end
|
136
|
+
end
|
135
137
|
end
|
138
|
+
|
139
|
+
@alias_attributes_mass_generated = true
|
136
140
|
end
|
137
141
|
|
138
142
|
def undefine_attribute_methods # :nodoc:
|
139
|
-
|
140
|
-
super if
|
143
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
144
|
+
super if @attribute_methods_generated
|
141
145
|
@attribute_methods_generated = false
|
142
146
|
@alias_attributes_mass_generated = false
|
143
147
|
end
|
@@ -288,9 +292,7 @@ module ActiveRecord
|
|
288
292
|
|
289
293
|
# If the result is true then check for the select case.
|
290
294
|
# For queries selecting a subset of columns, return false for unselected columns.
|
291
|
-
|
292
|
-
# have been allocated but not yet initialized.
|
293
|
-
if defined?(@attributes)
|
295
|
+
if @attributes
|
294
296
|
if name = self.class.symbol_column_to_string(name.to_sym)
|
295
297
|
return _has_attribute?(name)
|
296
298
|
end
|
@@ -459,9 +461,38 @@ module ActiveRecord
|
|
459
461
|
end
|
460
462
|
|
461
463
|
private
|
464
|
+
def respond_to_missing?(name, include_private = false)
|
465
|
+
if self.class.define_attribute_methods
|
466
|
+
# Some methods weren't defined yet.
|
467
|
+
return true if self.class.method_defined?(name)
|
468
|
+
return true if include_private && self.class.private_method_defined?(name)
|
469
|
+
end
|
470
|
+
|
471
|
+
super
|
472
|
+
end
|
473
|
+
|
474
|
+
def method_missing(name, ...)
|
475
|
+
unless self.class.attribute_methods_generated?
|
476
|
+
if self.class.method_defined?(name)
|
477
|
+
# The method is explicitly defined in the model, but calls a generated
|
478
|
+
# method with super. So we must resume the call chain at the right step.
|
479
|
+
last_method = method(name)
|
480
|
+
last_method = last_method.super_method while last_method.super_method
|
481
|
+
self.class.define_attribute_methods
|
482
|
+
if last_method.super_method
|
483
|
+
return last_method.super_method.call(...)
|
484
|
+
end
|
485
|
+
elsif self.class.define_attribute_methods
|
486
|
+
# Some attribute methods weren't generated yet, we retry the call
|
487
|
+
return public_send(name, ...)
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
super
|
492
|
+
end
|
493
|
+
|
462
494
|
def attribute_method?(attr_name)
|
463
|
-
|
464
|
-
defined?(@attributes) && @attributes.key?(attr_name)
|
495
|
+
@attributes&.key?(attr_name)
|
465
496
|
end
|
466
497
|
|
467
498
|
def attributes_with_values(attribute_names)
|
@@ -6,12 +6,13 @@ module ActiveRecord
|
|
6
6
|
# See ActiveRecord::Attributes::ClassMethods for documentation
|
7
7
|
module Attributes
|
8
8
|
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::AttributeRegistration
|
9
10
|
|
10
|
-
included do
|
11
|
-
class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal:
|
12
|
-
end
|
13
11
|
# = Active Record \Attributes
|
14
12
|
module ClassMethods
|
13
|
+
# :method: attribute
|
14
|
+
# :call-seq: attribute(name, cast_type = nil, **options)
|
15
|
+
#
|
15
16
|
# Defines an attribute with a type on this model. It will override the
|
16
17
|
# type of existing attributes if needed. This allows control over how
|
17
18
|
# values are converted to and from SQL when assigned to a model. It also
|
@@ -24,15 +25,17 @@ module ActiveRecord
|
|
24
25
|
# column which this will persist to.
|
25
26
|
#
|
26
27
|
# +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
|
27
|
-
# to be used for this attribute.
|
28
|
-
#
|
28
|
+
# to be used for this attribute. If this parameter is not passed, the previously
|
29
|
+
# defined type (if any) will be used.
|
30
|
+
# Otherwise, the type will be ActiveModel::Type::Value.
|
31
|
+
# See the examples below for more information about providing custom type objects.
|
29
32
|
#
|
30
33
|
# ==== Options
|
31
34
|
#
|
32
35
|
# The following options are accepted:
|
33
36
|
#
|
34
37
|
# +default+ The default value to use when no value is provided. If this option
|
35
|
-
# is not passed, the
|
38
|
+
# is not passed, the previously defined default value (if any) on the superclass or in the schema will be used.
|
36
39
|
# Otherwise, the default will be +nil+.
|
37
40
|
#
|
38
41
|
# +array+ (PostgreSQL only) specifies that the type should be an array (see the
|
@@ -134,7 +137,7 @@ module ActiveRecord
|
|
134
137
|
# expected API. It is recommended that your type objects inherit from an
|
135
138
|
# existing type, or from ActiveRecord::Type::Value
|
136
139
|
#
|
137
|
-
# class
|
140
|
+
# class PriceType < ActiveRecord::Type::Integer
|
138
141
|
# def cast(value)
|
139
142
|
# if !value.kind_of?(Numeric) && value.include?('$')
|
140
143
|
# price_in_dollars = value.gsub(/\$/, '').to_f
|
@@ -146,11 +149,11 @@ module ActiveRecord
|
|
146
149
|
# end
|
147
150
|
#
|
148
151
|
# # config/initializers/types.rb
|
149
|
-
# ActiveRecord::Type.register(:
|
152
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
150
153
|
#
|
151
154
|
# # app/models/store_listing.rb
|
152
155
|
# class StoreListing < ActiveRecord::Base
|
153
|
-
# attribute :price_in_cents, :
|
156
|
+
# attribute :price_in_cents, :price
|
154
157
|
# end
|
155
158
|
#
|
156
159
|
# store_listing = StoreListing.new(price_in_cents: '$10.00')
|
@@ -170,7 +173,7 @@ module ActiveRecord
|
|
170
173
|
# class Money < Struct.new(:amount, :currency)
|
171
174
|
# end
|
172
175
|
#
|
173
|
-
# class
|
176
|
+
# class PriceType < ActiveRecord::Type::Value
|
174
177
|
# def initialize(currency_converter:)
|
175
178
|
# @currency_converter = currency_converter
|
176
179
|
# end
|
@@ -185,12 +188,12 @@ module ActiveRecord
|
|
185
188
|
# end
|
186
189
|
#
|
187
190
|
# # config/initializers/types.rb
|
188
|
-
# ActiveRecord::Type.register(:
|
191
|
+
# ActiveRecord::Type.register(:price, PriceType)
|
189
192
|
#
|
190
193
|
# # app/models/product.rb
|
191
194
|
# class Product < ActiveRecord::Base
|
192
195
|
# currency_converter = ConversionRatesFromTheInternet.new
|
193
|
-
# attribute :price_in_bitcoins, :
|
196
|
+
# attribute :price_in_bitcoins, :price, currency_converter: currency_converter
|
194
197
|
# end
|
195
198
|
#
|
196
199
|
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
|
@@ -205,37 +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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
reload_schema_from_cache
|
213
|
-
|
214
|
-
case cast_type
|
215
|
-
when Symbol
|
216
|
-
cast_type = Type.lookup(cast_type, **options, adapter: Type.adapter_name_from(self))
|
217
|
-
when nil
|
218
|
-
if (prev_cast_type, prev_default = attributes_to_define_after_schema_loads[name])
|
219
|
-
default = prev_default if default == NO_DEFAULT_PROVIDED
|
220
|
-
else
|
221
|
-
prev_cast_type = -> subtype { subtype }
|
222
|
-
end
|
223
|
-
|
224
|
-
cast_type = if block_given?
|
225
|
-
-> subtype { yield Proc === prev_cast_type ? prev_cast_type[subtype] : prev_cast_type }
|
226
|
-
else
|
227
|
-
prev_cast_type
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
self.attributes_to_define_after_schema_loads =
|
232
|
-
attributes_to_define_after_schema_loads.merge(name => [cast_type, default])
|
233
|
-
end
|
211
|
+
#
|
212
|
+
#--
|
213
|
+
# Implemented by ActiveModel::AttributeRegistration#attribute.
|
234
214
|
|
235
215
|
# This is the low level API which sits beneath +attribute+. It only
|
236
216
|
# accepts type objects, and will do its work immediately instead of
|
237
|
-
# waiting for the schema to load.
|
238
|
-
# ClassMethods#attribute both call this under the hood. While this method
|
217
|
+
# waiting for the schema to load. While this method
|
239
218
|
# is provided so it can be used by plugin authors, application code
|
240
219
|
# should probably use ClassMethods#attribute.
|
241
220
|
#
|
@@ -260,14 +239,38 @@ module ActiveRecord
|
|
260
239
|
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
261
240
|
end
|
262
241
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
242
|
+
def _default_attributes # :nodoc:
|
243
|
+
@default_attributes ||= begin
|
244
|
+
attributes_hash = with_connection do |connection|
|
245
|
+
columns_hash.transform_values do |column|
|
246
|
+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
|
251
|
+
apply_pending_attribute_modifications(attribute_set)
|
252
|
+
attribute_set
|
268
253
|
end
|
269
254
|
end
|
270
255
|
|
256
|
+
##
|
257
|
+
# :method: type_for_attribute
|
258
|
+
# :call-seq: type_for_attribute(attribute_name, &block)
|
259
|
+
#
|
260
|
+
# See ActiveModel::Attributes::ClassMethods#type_for_attribute.
|
261
|
+
#
|
262
|
+
# This method will access the database and load the model's schema if
|
263
|
+
# necessary.
|
264
|
+
#--
|
265
|
+
# Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
|
266
|
+
|
267
|
+
##
|
268
|
+
protected
|
269
|
+
def reload_schema_from_cache(*)
|
270
|
+
reset_default_attributes!
|
271
|
+
super
|
272
|
+
end
|
273
|
+
|
271
274
|
private
|
272
275
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
273
276
|
private_constant :NO_DEFAULT_PROVIDED
|
@@ -287,6 +290,18 @@ module ActiveRecord
|
|
287
290
|
end
|
288
291
|
_default_attributes[name] = default_attribute
|
289
292
|
end
|
293
|
+
|
294
|
+
def reset_default_attributes
|
295
|
+
reload_schema_from_cache
|
296
|
+
end
|
297
|
+
|
298
|
+
def resolve_type_name(name, **options)
|
299
|
+
Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
|
300
|
+
end
|
301
|
+
|
302
|
+
def type_for_column(connection, column)
|
303
|
+
hook_attribute_type(column.name, super)
|
304
|
+
end
|
290
305
|
end
|
291
306
|
end
|
292
307
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/associations/nested_error"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
# = Active Record Autosave Association
|
5
7
|
#
|
@@ -315,7 +317,7 @@ module ActiveRecord
|
|
315
317
|
def validate_single_association(reflection)
|
316
318
|
association = association_instance_get(reflection.name)
|
317
319
|
record = association && association.reader
|
318
|
-
association_valid?(
|
320
|
+
association_valid?(association, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
319
321
|
end
|
320
322
|
|
321
323
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -324,7 +326,7 @@ module ActiveRecord
|
|
324
326
|
def validate_collection_association(reflection)
|
325
327
|
if association = association_instance_get(reflection.name)
|
326
328
|
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
|
327
|
-
records.
|
329
|
+
records.each { |record| association_valid?(association, record) }
|
328
330
|
end
|
329
331
|
end
|
330
332
|
end
|
@@ -332,40 +334,25 @@ module ActiveRecord
|
|
332
334
|
# Returns whether or not the association is valid and applies any errors to
|
333
335
|
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
|
334
336
|
# enabled records if they're marked_for_destruction? or destroyed.
|
335
|
-
def association_valid?(
|
336
|
-
return true if record.destroyed? || (
|
337
|
+
def association_valid?(association, record)
|
338
|
+
return true if record.destroyed? || (association.options[:autosave] && record.marked_for_destruction?)
|
337
339
|
|
338
340
|
context = validation_context if custom_validation_context?
|
339
341
|
|
340
342
|
unless valid = record.valid?(context)
|
341
|
-
if
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
errors.each { |error|
|
348
|
-
self.errors.import(
|
349
|
-
error,
|
350
|
-
attribute: attribute
|
351
|
-
)
|
352
|
-
}
|
343
|
+
if association.options[:autosave]
|
344
|
+
record.errors.each { |error|
|
345
|
+
self.errors.objects.append(
|
346
|
+
Associations::NestedError.new(association, error)
|
347
|
+
)
|
353
348
|
}
|
354
349
|
else
|
355
|
-
errors.add(reflection.name)
|
350
|
+
errors.add(association.reflection.name)
|
356
351
|
end
|
357
352
|
end
|
358
353
|
valid
|
359
354
|
end
|
360
355
|
|
361
|
-
def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
|
362
|
-
if indexed_attribute
|
363
|
-
"#{reflection.name}[#{index}].#{attribute}"
|
364
|
-
else
|
365
|
-
"#{reflection.name}.#{attribute}"
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
356
|
# Is used as an around_save callback to check while saving a collection
|
370
357
|
# association whether or not the parent was a new record before saving.
|
371
358
|
def around_save_collection_association
|
@@ -441,7 +428,9 @@ module ActiveRecord
|
|
441
428
|
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
442
429
|
def save_has_one_association(reflection)
|
443
430
|
association = association_instance_get(reflection.name)
|
444
|
-
|
431
|
+
return unless association && association.loaded?
|
432
|
+
|
433
|
+
record = association.load_target
|
445
434
|
|
446
435
|
if record && !record.destroyed?
|
447
436
|
autosave = reflection.options[:autosave]
|
@@ -458,7 +447,8 @@ module ActiveRecord
|
|
458
447
|
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
|
459
448
|
|
460
449
|
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
|
461
|
-
|
450
|
+
association_id = _read_attribute(primary_key)
|
451
|
+
record[foreign_key] = association_id unless record[foreign_key] == association_id
|
462
452
|
end
|
463
453
|
association.set_inverse_instance(record)
|
464
454
|
end
|
@@ -547,10 +537,6 @@ module ActiveRecord
|
|
547
537
|
end
|
548
538
|
end
|
549
539
|
|
550
|
-
def custom_validation_context?
|
551
|
-
validation_context && [:create, :update].exclude?(validation_context)
|
552
|
-
end
|
553
|
-
|
554
540
|
def _ensure_no_duplicate_errors
|
555
541
|
errors.uniq!
|
556
542
|
end
|
data/lib/active_record/base.rb
CHANGED
@@ -233,7 +233,7 @@ module ActiveRecord # :nodoc:
|
|
233
233
|
#
|
234
234
|
# Connections are usually created through
|
235
235
|
# {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
|
236
|
-
# by ActiveRecord::Base.
|
236
|
+
# by ActiveRecord::Base.lease_connection. All classes inheriting from ActiveRecord::Base will use this
|
237
237
|
# connection. But you can also set a class-specific connection. For example, if Course is an
|
238
238
|
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
239
239
|
# and Course and all of its subclasses will use this connection instead.
|
@@ -280,7 +280,7 @@ module ActiveRecord # :nodoc:
|
|
280
280
|
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
281
281
|
# instances in the current object space.
|
282
282
|
class Base
|
283
|
-
|
283
|
+
include ActiveModel::API
|
284
284
|
|
285
285
|
extend ActiveSupport::Benchmarkable
|
286
286
|
extend ActiveSupport::DescendantsTracker
|
@@ -304,7 +304,6 @@ module ActiveRecord # :nodoc:
|
|
304
304
|
include Scoping
|
305
305
|
include Sanitization
|
306
306
|
include AttributeAssignment
|
307
|
-
include ActiveModel::Conversion
|
308
307
|
include Integration
|
309
308
|
include Validations
|
310
309
|
include CounterCache
|