activerecord 7.1.5.1 → 7.2.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 +645 -2329
- 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 +14 -7
- 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 +7 -1
- 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 +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- 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 +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- 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.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- 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 +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
- 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 +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- 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 +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- 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/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 +15 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- 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 +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +85 -37
- data/lib/active_record/counter_cache.rb +18 -9
- 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 +38 -34
- 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 +24 -0
- 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 +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- 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/enum.rb +18 -1
- 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 +8 -4
- data/lib/active_record/gem_version.rb +2 -2
- 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 +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -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 +31 -68
- data/lib/active_record/nested_attributes.rb +11 -3
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +37 -55
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- 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 +96 -63
- 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.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- 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 +496 -72
- data/lib/active_record/result.rb +31 -44
- 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 +69 -41
- 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 +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- 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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- 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 +3 -2
- 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/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +16 -10
@@ -18,74 +18,45 @@ module ActiveRecord
|
|
18
18
|
# Returns the primary key column's value. If the primary key is composite,
|
19
19
|
# returns an array of the primary key column values.
|
20
20
|
def id
|
21
|
-
|
22
|
-
|
23
|
-
@primary_key.map { |pk| _read_attribute(pk) }
|
21
|
+
_read_attribute(@primary_key)
|
24
22
|
end
|
25
23
|
|
26
24
|
def primary_key_values_present? # :nodoc:
|
27
|
-
return id.all? if self.class.composite_primary_key?
|
28
|
-
|
29
25
|
!!id
|
30
26
|
end
|
31
27
|
|
32
28
|
# Sets the primary key column's value. If the primary key is composite,
|
33
29
|
# raises TypeError when the set value not enumerable.
|
34
30
|
def id=(value)
|
35
|
-
|
36
|
-
raise TypeError, "Expected value matching #{self.class.primary_key.inspect}, got #{value.inspect}." unless value.is_a?(Enumerable)
|
37
|
-
@primary_key.zip(value) { |attr, value| _write_attribute(attr, value) }
|
38
|
-
else
|
39
|
-
_write_attribute(@primary_key, value)
|
40
|
-
end
|
31
|
+
_write_attribute(@primary_key, value)
|
41
32
|
end
|
42
33
|
|
43
34
|
# Queries the primary key column's value. If the primary key is composite,
|
44
35
|
# all primary key column values must be queryable.
|
45
36
|
def id?
|
46
|
-
|
47
|
-
@primary_key.all? { |col| _query_attribute(col) }
|
48
|
-
else
|
49
|
-
_query_attribute(@primary_key)
|
50
|
-
end
|
37
|
+
_query_attribute(@primary_key)
|
51
38
|
end
|
52
39
|
|
53
40
|
# Returns the primary key column's value before type cast. If the primary key is composite,
|
54
41
|
# returns an array of primary key column values before type cast.
|
55
42
|
def id_before_type_cast
|
56
|
-
|
57
|
-
@primary_key.map { |col| attribute_before_type_cast(col) }
|
58
|
-
else
|
59
|
-
attribute_before_type_cast(@primary_key)
|
60
|
-
end
|
43
|
+
attribute_before_type_cast(@primary_key)
|
61
44
|
end
|
62
45
|
|
63
46
|
# Returns the primary key column's previous value. If the primary key is composite,
|
64
47
|
# returns an array of primary key column previous values.
|
65
48
|
def id_was
|
66
|
-
|
67
|
-
@primary_key.map { |col| attribute_was(col) }
|
68
|
-
else
|
69
|
-
attribute_was(@primary_key)
|
70
|
-
end
|
49
|
+
attribute_was(@primary_key)
|
71
50
|
end
|
72
51
|
|
73
52
|
# Returns the primary key column's value from the database. If the primary key is composite,
|
74
53
|
# returns an array of primary key column values from database.
|
75
54
|
def id_in_database
|
76
|
-
|
77
|
-
@primary_key.map { |col| attribute_in_database(col) }
|
78
|
-
else
|
79
|
-
attribute_in_database(@primary_key)
|
80
|
-
end
|
55
|
+
attribute_in_database(@primary_key)
|
81
56
|
end
|
82
57
|
|
83
58
|
def id_for_database # :nodoc:
|
84
|
-
|
85
|
-
@primary_key.map { |col| @attributes[col].value_for_database }
|
86
|
-
else
|
87
|
-
@attributes[@primary_key].value_for_database
|
88
|
-
end
|
59
|
+
@attributes[@primary_key].value_for_database
|
89
60
|
end
|
90
61
|
|
91
62
|
private
|
@@ -109,20 +80,19 @@ module ActiveRecord
|
|
109
80
|
# Overwriting will negate any effect of the +primary_key_prefix_type+
|
110
81
|
# setting, though.
|
111
82
|
def primary_key
|
112
|
-
if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
113
|
-
@primary_key = reset_primary_key
|
114
|
-
end
|
83
|
+
reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
115
84
|
@primary_key
|
116
85
|
end
|
117
86
|
|
118
87
|
def composite_primary_key? # :nodoc:
|
119
|
-
|
88
|
+
reset_primary_key if PRIMARY_KEY_NOT_SET.equal?(@primary_key)
|
89
|
+
@composite_primary_key
|
120
90
|
end
|
121
91
|
|
122
92
|
# Returns a quoted version of the primary key name, used to construct
|
123
93
|
# SQL statements.
|
124
94
|
def quoted_primary_key
|
125
|
-
@quoted_primary_key ||=
|
95
|
+
@quoted_primary_key ||= adapter_class.quote_column_name(primary_key)
|
126
96
|
end
|
127
97
|
|
128
98
|
def reset_primary_key # :nodoc:
|
@@ -138,12 +108,10 @@ module ActiveRecord
|
|
138
108
|
base_name.foreign_key(false)
|
139
109
|
elsif base_name && primary_key_prefix_type == :table_name_with_underscore
|
140
110
|
base_name.foreign_key
|
111
|
+
elsif ActiveRecord::Base != self && table_exists?
|
112
|
+
schema_cache.primary_keys(table_name)
|
141
113
|
else
|
142
|
-
|
143
|
-
connection.schema_cache.primary_keys(table_name)
|
144
|
-
else
|
145
|
-
"id"
|
146
|
-
end
|
114
|
+
"id"
|
147
115
|
end
|
148
116
|
end
|
149
117
|
|
@@ -163,25 +131,25 @@ module ActiveRecord
|
|
163
131
|
#
|
164
132
|
# Project.primary_key # => "foo_id"
|
165
133
|
def primary_key=(value)
|
166
|
-
@primary_key
|
134
|
+
@primary_key = if value.is_a?(Array)
|
135
|
+
@composite_primary_key = true
|
136
|
+
include CompositePrimaryKey
|
137
|
+
@primary_key = value.map { |v| -v.to_s }.freeze
|
138
|
+
elsif value
|
139
|
+
-value.to_s
|
140
|
+
end
|
167
141
|
@quoted_primary_key = nil
|
168
142
|
@attributes_builder = nil
|
169
143
|
end
|
170
144
|
|
171
145
|
private
|
172
|
-
def derive_primary_key(value)
|
173
|
-
return unless value
|
174
|
-
|
175
|
-
return -value.to_s unless value.is_a?(Array)
|
176
|
-
|
177
|
-
value.map { |v| -v.to_s }.freeze
|
178
|
-
end
|
179
|
-
|
180
146
|
def inherited(base)
|
181
147
|
super
|
182
148
|
base.class_eval do
|
183
149
|
@primary_key = PRIMARY_KEY_NOT_SET
|
150
|
+
@composite_primary_key = false
|
184
151
|
@quoted_primary_key = nil
|
152
|
+
@attributes_builder = nil
|
185
153
|
end
|
186
154
|
end
|
187
155
|
end
|
@@ -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
|
@@ -73,14 +73,15 @@ module ActiveRecord
|
|
73
73
|
end
|
74
74
|
|
75
75
|
module ClassMethods # :nodoc:
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
private
|
77
|
+
def hook_attribute_type(name, cast_type)
|
78
|
+
if create_time_zone_conversion_attribute?(name, cast_type)
|
79
|
+
cast_type = TimeZoneConverter.new(cast_type)
|
80
|
+
end
|
81
|
+
|
82
|
+
super
|
79
83
|
end
|
80
|
-
super
|
81
|
-
end
|
82
84
|
|
83
|
-
private
|
84
85
|
def create_time_zone_conversion_attribute?(name, cast_type)
|
85
86
|
enabled_for_column = time_zone_aware_attributes &&
|
86
87
|
!skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
@@ -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,25 +77,6 @@ module ActiveRecord
|
|
64
77
|
# alias attributes in Active Record are lazily generated
|
65
78
|
end
|
66
79
|
|
67
|
-
def generate_alias_attributes # :nodoc:
|
68
|
-
superclass.generate_alias_attributes unless superclass == Base
|
69
|
-
return false if @alias_attributes_mass_generated
|
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
|
82
|
-
end
|
83
|
-
true
|
84
|
-
end
|
85
|
-
|
86
80
|
def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
|
87
81
|
attribute_method_patterns.each do |pattern|
|
88
82
|
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
@@ -90,64 +84,64 @@ module ActiveRecord
|
|
90
84
|
attribute_method_patterns_cache.clear
|
91
85
|
end
|
92
86
|
|
93
|
-
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
94
|
-
method_name = pattern.method_name(new_name).to_s
|
95
|
-
target_name = pattern.method_name(old_name).to_s
|
87
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
96
88
|
old_name = old_name.to_s
|
97
89
|
|
98
|
-
method_defined = method_defined?(target_name) || private_method_defined?(target_name)
|
99
|
-
manually_defined = method_defined &&
|
100
|
-
!self.instance_method(target_name).owner.is_a?(GeneratedAttributeMethods)
|
101
|
-
reserved_method_name = ::ActiveRecord::AttributeMethods.dangerous_attribute_methods.include?(target_name)
|
102
|
-
|
103
90
|
if !abstract_class? && !has_attribute?(old_name)
|
104
|
-
|
105
|
-
|
106
|
-
if should_warn
|
107
|
-
ActiveRecord.deprecator.warn(
|
108
|
-
"#{self} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
|
109
|
-
"Starting in Rails 7.2, alias_attribute with non-attribute targets will raise. " \
|
110
|
-
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
|
111
|
-
)
|
112
|
-
end
|
113
|
-
super
|
114
|
-
elsif manually_defined && !reserved_method_name
|
115
|
-
aliased_method_redefined_as_well = method_defined_within?(method_name, self)
|
116
|
-
return if aliased_method_redefined_as_well
|
117
|
-
|
118
|
-
ActiveRecord.deprecator.warn(
|
119
|
-
"#{self} model aliases `#{old_name}` and has a method called `#{target_name}` defined. " \
|
120
|
-
"Starting in Rails 7.2 `#{method_name}` will not be calling `#{target_name}` anymore. " \
|
121
|
-
"You may want to additionally define `#{method_name}` to preserve the current behavior."
|
122
|
-
)
|
123
|
-
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."
|
124
93
|
else
|
125
94
|
define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
|
126
95
|
end
|
127
96
|
end
|
128
97
|
|
98
|
+
def attribute_methods_generated? # :nodoc:
|
99
|
+
@attribute_methods_generated
|
100
|
+
end
|
101
|
+
|
129
102
|
# Generates all the attribute related methods for columns in the database
|
130
103
|
# accessors, mutators and query methods.
|
131
104
|
def define_attribute_methods # :nodoc:
|
132
105
|
return false if @attribute_methods_generated
|
133
106
|
# Use a mutex; we don't want two threads simultaneously trying to define
|
134
107
|
# attribute methods.
|
135
|
-
|
108
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
136
109
|
return false if @attribute_methods_generated
|
110
|
+
|
137
111
|
superclass.define_attribute_methods unless base_class?
|
138
|
-
|
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
|
+
|
139
119
|
@attribute_methods_generated = true
|
120
|
+
|
121
|
+
generate_alias_attributes
|
140
122
|
end
|
141
123
|
true
|
142
124
|
end
|
143
125
|
|
144
|
-
def
|
145
|
-
|
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
|
137
|
+
end
|
138
|
+
|
139
|
+
@alias_attributes_mass_generated = true
|
146
140
|
end
|
147
141
|
|
148
142
|
def undefine_attribute_methods # :nodoc:
|
149
|
-
|
150
|
-
super if
|
143
|
+
GeneratedAttributeMethods::LOCK.synchronize do
|
144
|
+
super if @attribute_methods_generated
|
151
145
|
@attribute_methods_generated = false
|
152
146
|
@alias_attributes_mass_generated = false
|
153
147
|
end
|
@@ -298,9 +292,7 @@ module ActiveRecord
|
|
298
292
|
|
299
293
|
# If the result is true then check for the select case.
|
300
294
|
# For queries selecting a subset of columns, return false for unselected columns.
|
301
|
-
|
302
|
-
# have been allocated but not yet initialized.
|
303
|
-
if defined?(@attributes)
|
295
|
+
if @attributes
|
304
296
|
if name = self.class.symbol_column_to_string(name.to_sym)
|
305
297
|
return _has_attribute?(name)
|
306
298
|
end
|
@@ -483,14 +475,14 @@ module ActiveRecord
|
|
483
475
|
unless self.class.attribute_methods_generated?
|
484
476
|
if self.class.method_defined?(name)
|
485
477
|
# The method is explicitly defined in the model, but calls a generated
|
486
|
-
# method with super. So we must resume the call chain at the right
|
478
|
+
# method with super. So we must resume the call chain at the right step.
|
487
479
|
last_method = method(name)
|
488
480
|
last_method = last_method.super_method while last_method.super_method
|
489
481
|
self.class.define_attribute_methods
|
490
482
|
if last_method.super_method
|
491
483
|
return last_method.super_method.call(...)
|
492
484
|
end
|
493
|
-
elsif self.class.define_attribute_methods
|
485
|
+
elsif self.class.define_attribute_methods
|
494
486
|
# Some attribute methods weren't generated yet, we retry the call
|
495
487
|
return public_send(name, ...)
|
496
488
|
end
|
@@ -500,8 +492,7 @@ module ActiveRecord
|
|
500
492
|
end
|
501
493
|
|
502
494
|
def attribute_method?(attr_name)
|
503
|
-
|
504
|
-
defined?(@attributes) && @attributes.key?(attr_name)
|
495
|
+
@attributes&.key?(attr_name)
|
505
496
|
end
|
506
497
|
|
507
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,12 @@ 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
|
-
# This
|
236
|
-
#
|
237
|
-
# waiting for the schema to load. Automatic schema detection and
|
238
|
-
# ClassMethods#attribute both call this under the hood. While this method
|
215
|
+
# This API only accepts type objects, and will do its work immediately instead of
|
216
|
+
# waiting for the schema to load. While this method
|
239
217
|
# is provided so it can be used by plugin authors, application code
|
240
218
|
# should probably use ClassMethods#attribute.
|
241
219
|
#
|
@@ -260,14 +238,38 @@ module ActiveRecord
|
|
260
238
|
define_default_attribute(name, default, cast_type, from_user: user_provided_default)
|
261
239
|
end
|
262
240
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
241
|
+
def _default_attributes # :nodoc:
|
242
|
+
@default_attributes ||= begin
|
243
|
+
attributes_hash = with_connection do |connection|
|
244
|
+
columns_hash.transform_values do |column|
|
245
|
+
ActiveModel::Attribute.from_database(column.name, column.default, type_for_column(connection, column))
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
attribute_set = ActiveModel::AttributeSet.new(attributes_hash)
|
250
|
+
apply_pending_attribute_modifications(attribute_set)
|
251
|
+
attribute_set
|
268
252
|
end
|
269
253
|
end
|
270
254
|
|
255
|
+
##
|
256
|
+
# :method: type_for_attribute
|
257
|
+
# :call-seq: type_for_attribute(attribute_name, &block)
|
258
|
+
#
|
259
|
+
# See ActiveModel::Attributes::ClassMethods#type_for_attribute.
|
260
|
+
#
|
261
|
+
# This method will access the database and load the model's schema if
|
262
|
+
# necessary.
|
263
|
+
#--
|
264
|
+
# Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
|
265
|
+
|
266
|
+
##
|
267
|
+
protected
|
268
|
+
def reload_schema_from_cache(*)
|
269
|
+
reset_default_attributes!
|
270
|
+
super
|
271
|
+
end
|
272
|
+
|
271
273
|
private
|
272
274
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
273
275
|
private_constant :NO_DEFAULT_PROVIDED
|
@@ -287,6 +289,18 @@ module ActiveRecord
|
|
287
289
|
end
|
288
290
|
_default_attributes[name] = default_attribute
|
289
291
|
end
|
292
|
+
|
293
|
+
def reset_default_attributes
|
294
|
+
reload_schema_from_cache
|
295
|
+
end
|
296
|
+
|
297
|
+
def resolve_type_name(name, **options)
|
298
|
+
Type.lookup(name, **options, adapter: Type.adapter_name_from(self))
|
299
|
+
end
|
300
|
+
|
301
|
+
def type_for_column(connection, column)
|
302
|
+
hook_attribute_type(column.name, super)
|
303
|
+
end
|
290
304
|
end
|
291
305
|
end
|
292
306
|
end
|