activerecord 7.2.4 → 8.1.4
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 +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -32
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -36,11 +36,26 @@ module ActiveRecord
|
|
|
36
36
|
class_attribute :pre_loaded_fixtures, default: false
|
|
37
37
|
class_attribute :lock_threads, default: true
|
|
38
38
|
class_attribute :fixture_sets, default: {}
|
|
39
|
+
class_attribute :database_transactions_config, default: {}
|
|
39
40
|
|
|
40
41
|
ActiveSupport.run_load_hooks(:active_record_fixtures, self)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
module ClassMethods
|
|
45
|
+
# Do not use transactional tests for the given database. This overrides
|
|
46
|
+
# the default setting as defined by `use_transactional_tests`, which
|
|
47
|
+
# applies to all database connection pools not explicitly configured here.
|
|
48
|
+
def skip_transactional_tests_for_database(database_name)
|
|
49
|
+
use_transactional_tests_for_database(database_name, false)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Enable or disable transactions per database. This overrides the default
|
|
53
|
+
# setting as defined by `use_transactional_tests`, which applies to all
|
|
54
|
+
# database connection pools not explicitly configured here.
|
|
55
|
+
def use_transactional_tests_for_database(database_name, enabled = true)
|
|
56
|
+
self.database_transactions_config = database_transactions_config.merge(database_name => enabled)
|
|
57
|
+
end
|
|
58
|
+
|
|
44
59
|
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
|
|
45
60
|
#
|
|
46
61
|
# Examples:
|
|
@@ -106,7 +121,8 @@ module ActiveRecord
|
|
|
106
121
|
|
|
107
122
|
private
|
|
108
123
|
def run_in_transaction?
|
|
109
|
-
|
|
124
|
+
has_explicit_config = database_transactions_config.any? { |_, enabled| enabled }
|
|
125
|
+
(use_transactional_tests || has_explicit_config) &&
|
|
110
126
|
!self.class.uses_transaction?(name)
|
|
111
127
|
end
|
|
112
128
|
|
|
@@ -137,12 +153,15 @@ module ActiveRecord
|
|
|
137
153
|
invalidate_already_loaded_fixtures
|
|
138
154
|
@loaded_fixtures = load_fixtures(config)
|
|
139
155
|
end
|
|
156
|
+
setup_asynchronous_queries_session
|
|
140
157
|
|
|
141
158
|
# Instantiate fixtures for every test if requested.
|
|
142
159
|
instantiate_fixtures if use_instantiated_fixtures
|
|
143
160
|
end
|
|
144
161
|
|
|
145
162
|
def teardown_fixtures
|
|
163
|
+
teardown_asynchronous_queries_session
|
|
164
|
+
|
|
146
165
|
# Rollback changes if a transaction is active.
|
|
147
166
|
if run_in_transaction?
|
|
148
167
|
teardown_transactional_fixtures
|
|
@@ -154,15 +173,31 @@ module ActiveRecord
|
|
|
154
173
|
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
155
174
|
end
|
|
156
175
|
|
|
176
|
+
def setup_asynchronous_queries_session
|
|
177
|
+
@_async_queries_session = ActiveRecord::Base.asynchronous_queries_tracker.start_session
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def teardown_asynchronous_queries_session
|
|
181
|
+
ActiveRecord::Base.asynchronous_queries_tracker.finalize_session(true) if @_async_queries_session
|
|
182
|
+
end
|
|
183
|
+
|
|
157
184
|
def invalidate_already_loaded_fixtures
|
|
158
185
|
@@already_loaded_fixtures.clear
|
|
159
186
|
end
|
|
160
187
|
|
|
188
|
+
def transactional_tests_for_pool?(pool)
|
|
189
|
+
database_transactions_config.fetch(pool.db_config.name.to_sym, use_transactional_tests)
|
|
190
|
+
end
|
|
191
|
+
|
|
161
192
|
def setup_transactional_fixtures
|
|
162
193
|
setup_shared_connection_pool
|
|
163
194
|
|
|
164
195
|
# Begin transactions for connections already established
|
|
165
196
|
@fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
|
|
197
|
+
|
|
198
|
+
# Filter to pools that want to use transactions
|
|
199
|
+
@fixture_connection_pools.select! { |pool| transactional_tests_for_pool?(pool) }
|
|
200
|
+
|
|
166
201
|
@fixture_connection_pools.each do |pool|
|
|
167
202
|
pool.pin_connection!(lock_threads)
|
|
168
203
|
pool.lease_connection
|
|
@@ -178,7 +213,8 @@ module ActiveRecord
|
|
|
178
213
|
if pool
|
|
179
214
|
setup_shared_connection_pool
|
|
180
215
|
|
|
181
|
-
|
|
216
|
+
# Don't begin a transaction if we've already done so, or are not using them for this pool
|
|
217
|
+
if !@fixture_connection_pools.include?(pool) && transactional_tests_for_pool?(pool)
|
|
182
218
|
pool.pin_connection!(lock_threads)
|
|
183
219
|
pool.lease_connection
|
|
184
220
|
@fixture_connection_pools << pool
|
|
@@ -190,6 +226,7 @@ module ActiveRecord
|
|
|
190
226
|
|
|
191
227
|
def teardown_transactional_fixtures
|
|
192
228
|
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
|
229
|
+
|
|
193
230
|
unless @fixture_connection_pools.map(&:unpin_connection!).all?
|
|
194
231
|
# Something caused the transaction to be committed or rolled back
|
|
195
232
|
# We can no longer trust the database is in a clean state.
|
|
@@ -226,19 +263,28 @@ module ActiveRecord
|
|
|
226
263
|
|
|
227
264
|
def teardown_shared_connection_pool
|
|
228
265
|
handler = ActiveRecord::Base.connection_handler
|
|
266
|
+
removed_pool_configs = []
|
|
267
|
+
pool_managers = handler.send(:connection_name_to_pool_manager)
|
|
229
268
|
|
|
230
269
|
@saved_pool_configs.each_pair do |name, shards|
|
|
231
|
-
pool_manager =
|
|
270
|
+
pool_manager = pool_managers[name]
|
|
232
271
|
shards.each_pair do |shard_name, roles|
|
|
233
272
|
roles.each_pair do |role, pool_config|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
273
|
+
if pool_manager.get_pool_config(role, shard_name)
|
|
274
|
+
pool_manager.set_pool_config(role, shard_name, pool_config)
|
|
275
|
+
else
|
|
276
|
+
removed_pool_configs << pool_config
|
|
277
|
+
end
|
|
237
278
|
end
|
|
238
279
|
end
|
|
239
280
|
end
|
|
240
281
|
|
|
241
282
|
@saved_pool_configs.clear
|
|
283
|
+
|
|
284
|
+
remaining_pool_configs = pool_managers.values.flat_map(&:pool_configs)
|
|
285
|
+
removed_pool_configs.compact.uniq.each do |pool_config|
|
|
286
|
+
pool_config.disconnect! unless remaining_pool_configs.include?(pool_config)
|
|
287
|
+
end
|
|
242
288
|
end
|
|
243
289
|
|
|
244
290
|
def load_fixtures(config)
|
|
@@ -11,12 +11,18 @@ module ActiveRecord
|
|
|
11
11
|
# # Check for any number of queries
|
|
12
12
|
# assert_queries_count { Post.first }
|
|
13
13
|
#
|
|
14
|
-
#
|
|
14
|
+
# Any unmaterialized transactions will be materialized to ensure only
|
|
15
|
+
# queries attempted intside the block are counted.
|
|
16
|
+
#
|
|
17
|
+
# If the +:include_schema+ option is provided, any queries (including
|
|
18
|
+
# schema related) are counted. Setting this option also skips leasing a
|
|
19
|
+
# connection to materialize pending transactions since we want to count
|
|
20
|
+
# queries executed at connection open (e.g., type map).
|
|
15
21
|
#
|
|
16
22
|
# assert_queries_count(1, include_schema: true) { Post.columns }
|
|
17
23
|
#
|
|
18
24
|
def assert_queries_count(count = nil, include_schema: false, &block)
|
|
19
|
-
ActiveRecord::Base.lease_connection.materialize_transactions
|
|
25
|
+
ActiveRecord::Base.lease_connection.materialize_transactions unless include_schema
|
|
20
26
|
|
|
21
27
|
counter = SQLCounter.new
|
|
22
28
|
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
@@ -169,8 +169,10 @@ module ActiveRecord
|
|
|
169
169
|
# Clear attributes and changed_attributes
|
|
170
170
|
def clear_timestamp_attributes
|
|
171
171
|
all_timestamp_attributes_in_model.each do |attribute_name|
|
|
172
|
-
self[attribute_name]
|
|
173
|
-
|
|
172
|
+
if self[attribute_name]
|
|
173
|
+
self[attribute_name] = nil
|
|
174
|
+
clear_attribute_change(attribute_name)
|
|
175
|
+
end
|
|
174
176
|
end
|
|
175
177
|
end
|
|
176
178
|
end
|
|
@@ -40,7 +40,7 @@ module ActiveRecord
|
|
|
40
40
|
# +nil+ if the token is invalid or the record was not found.
|
|
41
41
|
def find_by_token_for(purpose, token)
|
|
42
42
|
raise UnknownPrimaryKey.new(self) unless model.primary_key
|
|
43
|
-
model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => id) }
|
|
43
|
+
model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => [id]) }
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# Finds a record using a given +token+ for a predefined +purpose+. Raises
|
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
|
26
26
|
# # We are inside a real and not finalized transaction.
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
|
-
# Closed transactions are
|
|
29
|
+
# Closed transactions are +blank?+ too.
|
|
30
30
|
#
|
|
31
31
|
# == Callbacks
|
|
32
32
|
#
|
|
@@ -101,9 +101,6 @@ module ActiveRecord
|
|
|
101
101
|
#
|
|
102
102
|
# If the entire chain of nested transactions are all successfully committed,
|
|
103
103
|
# the block is never called.
|
|
104
|
-
#
|
|
105
|
-
# If the transaction is already finalized, attempting to register a callback
|
|
106
|
-
# will raise ActiveRecord::ActiveRecordError.
|
|
107
104
|
def after_rollback(&block)
|
|
108
105
|
@internal_transaction&.after_rollback(&block)
|
|
109
106
|
end
|
|
@@ -115,7 +112,7 @@ module ActiveRecord
|
|
|
115
112
|
|
|
116
113
|
# Returns true if the transaction doesn't exist or is finalized.
|
|
117
114
|
def closed?
|
|
118
|
-
@internal_transaction.nil? || @internal_transaction.
|
|
115
|
+
@internal_transaction.nil? || @internal_transaction.closed?
|
|
119
116
|
end
|
|
120
117
|
|
|
121
118
|
alias_method :blank?, :closed?
|
|
@@ -219,22 +219,41 @@ module ActiveRecord
|
|
|
219
219
|
# database error will occur because the savepoint has already been
|
|
220
220
|
# automatically released. The following example demonstrates the problem:
|
|
221
221
|
#
|
|
222
|
-
# Model.
|
|
223
|
-
# Model.
|
|
224
|
-
# Model.lease_connection.create_table(...)
|
|
225
|
-
# end
|
|
226
|
-
#
|
|
227
|
-
# end
|
|
222
|
+
# Model.transaction do # BEGIN
|
|
223
|
+
# Model.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
|
|
224
|
+
# Model.lease_connection.create_table(...) # active_record_1 now automatically released
|
|
225
|
+
# end # RELEASE SAVEPOINT active_record_1
|
|
226
|
+
# end # ^^^^ BOOM! database error!
|
|
228
227
|
#
|
|
229
228
|
# Note that "TRUNCATE" is also a MySQL DDL statement!
|
|
230
229
|
module ClassMethods
|
|
231
230
|
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
|
|
232
231
|
def transaction(**options, &block)
|
|
233
232
|
with_connection do |connection|
|
|
234
|
-
connection.
|
|
233
|
+
connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
|
|
234
|
+
connection.transaction(**options, &block)
|
|
235
|
+
end
|
|
235
236
|
end
|
|
236
237
|
end
|
|
237
238
|
|
|
239
|
+
# Makes all transactions the current pool use the isolation level initiated within the block.
|
|
240
|
+
def with_pool_transaction_isolation_level(isolation_level, &block)
|
|
241
|
+
if current_transaction.open?
|
|
242
|
+
raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
old_level = connection_pool.pool_transaction_isolation_level
|
|
246
|
+
connection_pool.pool_transaction_isolation_level = isolation_level
|
|
247
|
+
yield
|
|
248
|
+
ensure
|
|
249
|
+
connection_pool.pool_transaction_isolation_level = old_level
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Returns the default isolation level for the connection pool, set earlier by #with_pool_transaction_isolation_level.
|
|
253
|
+
def pool_transaction_isolation_level
|
|
254
|
+
connection_pool.pool_transaction_isolation_level
|
|
255
|
+
end
|
|
256
|
+
|
|
238
257
|
# Returns a representation of the current transaction state,
|
|
239
258
|
# which can be a top level transaction, a savepoint, or the absence of a transaction.
|
|
240
259
|
#
|
|
@@ -247,7 +266,7 @@ module ActiveRecord
|
|
|
247
266
|
end
|
|
248
267
|
|
|
249
268
|
def before_commit(*args, &block) # :nodoc:
|
|
250
|
-
set_options_for_callbacks!(args)
|
|
269
|
+
set_options_for_callbacks!(:before, args)
|
|
251
270
|
set_callback(:before_commit, :before, *args, &block)
|
|
252
271
|
end
|
|
253
272
|
|
|
@@ -264,31 +283,31 @@ module ActiveRecord
|
|
|
264
283
|
# after_commit :do_bar_baz, on: [:update, :destroy]
|
|
265
284
|
#
|
|
266
285
|
def after_commit(*args, &block)
|
|
267
|
-
set_options_for_callbacks!(
|
|
286
|
+
set_options_for_callbacks!(:after, args)
|
|
268
287
|
set_callback(:commit, :after, *args, &block)
|
|
269
288
|
end
|
|
270
289
|
|
|
271
290
|
# Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
|
|
272
291
|
def after_save_commit(*args, &block)
|
|
273
|
-
set_options_for_callbacks!(args, on: [ :create, :update ]
|
|
292
|
+
set_options_for_callbacks!(:after, args, on: [ :create, :update ])
|
|
274
293
|
set_callback(:commit, :after, *args, &block)
|
|
275
294
|
end
|
|
276
295
|
|
|
277
296
|
# Shortcut for <tt>after_commit :hook, on: :create</tt>.
|
|
278
297
|
def after_create_commit(*args, &block)
|
|
279
|
-
set_options_for_callbacks!(args, on: :create
|
|
298
|
+
set_options_for_callbacks!(:after, args, on: :create)
|
|
280
299
|
set_callback(:commit, :after, *args, &block)
|
|
281
300
|
end
|
|
282
301
|
|
|
283
302
|
# Shortcut for <tt>after_commit :hook, on: :update</tt>.
|
|
284
303
|
def after_update_commit(*args, &block)
|
|
285
|
-
set_options_for_callbacks!(args, on: :update
|
|
304
|
+
set_options_for_callbacks!(:after, args, on: :update)
|
|
286
305
|
set_callback(:commit, :after, *args, &block)
|
|
287
306
|
end
|
|
288
307
|
|
|
289
308
|
# Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
|
|
290
309
|
def after_destroy_commit(*args, &block)
|
|
291
|
-
set_options_for_callbacks!(args, on: :destroy
|
|
310
|
+
set_options_for_callbacks!(:after, args, on: :destroy)
|
|
292
311
|
set_callback(:commit, :after, *args, &block)
|
|
293
312
|
end
|
|
294
313
|
|
|
@@ -296,7 +315,7 @@ module ActiveRecord
|
|
|
296
315
|
#
|
|
297
316
|
# Please check the documentation of #after_commit for options.
|
|
298
317
|
def after_rollback(*args, &block)
|
|
299
|
-
set_options_for_callbacks!(
|
|
318
|
+
set_options_for_callbacks!(:after, args)
|
|
300
319
|
set_callback(:rollback, :after, *args, &block)
|
|
301
320
|
end
|
|
302
321
|
|
|
@@ -320,16 +339,11 @@ module ActiveRecord
|
|
|
320
339
|
end
|
|
321
340
|
|
|
322
341
|
private
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
{}
|
|
328
|
-
end
|
|
329
|
-
end
|
|
342
|
+
def set_options_for_callbacks!(position, args, enforced_options = {})
|
|
343
|
+
options = args.extract_options!
|
|
344
|
+
enforced_options.merge!(enforced_prepend_option(position, options))
|
|
345
|
+
options.merge!(enforced_options)
|
|
330
346
|
|
|
331
|
-
def set_options_for_callbacks!(args, enforced_options = {})
|
|
332
|
-
options = args.extract_options!.merge!(enforced_options)
|
|
333
347
|
args << options
|
|
334
348
|
|
|
335
349
|
if options[:on]
|
|
@@ -342,6 +356,14 @@ module ActiveRecord
|
|
|
342
356
|
end
|
|
343
357
|
end
|
|
344
358
|
|
|
359
|
+
def enforced_prepend_option(position, options)
|
|
360
|
+
if position == :after && ActiveRecord.run_after_transaction_callbacks_in_order_defined
|
|
361
|
+
{ prepend: !options[:prepend] }
|
|
362
|
+
else
|
|
363
|
+
{}
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
345
367
|
def assert_valid_transaction_action(actions)
|
|
346
368
|
if (actions - ACTIONS).any?
|
|
347
369
|
raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
|
|
@@ -408,18 +430,20 @@ module ActiveRecord
|
|
|
408
430
|
# instance.
|
|
409
431
|
def with_transaction_returning_status
|
|
410
432
|
self.class.with_connection do |connection|
|
|
411
|
-
|
|
412
|
-
|
|
433
|
+
connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
|
|
434
|
+
status = nil
|
|
435
|
+
ensure_finalize = !connection.transaction_open?
|
|
413
436
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
437
|
+
connection.transaction do
|
|
438
|
+
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
|
439
|
+
remember_transaction_record_state
|
|
417
440
|
|
|
418
|
-
|
|
419
|
-
|
|
441
|
+
status = yield
|
|
442
|
+
raise ActiveRecord::Rollback unless status
|
|
443
|
+
end
|
|
444
|
+
@_last_transaction_return_status = status
|
|
445
|
+
status
|
|
420
446
|
end
|
|
421
|
-
@_last_transaction_return_status = status
|
|
422
|
-
status
|
|
423
447
|
end
|
|
424
448
|
end
|
|
425
449
|
|
|
@@ -26,6 +26,7 @@ module ActiveRecord
|
|
|
26
26
|
if block
|
|
27
27
|
@mapping[key] = block
|
|
28
28
|
else
|
|
29
|
+
value.freeze
|
|
29
30
|
@mapping[key] = proc { value }
|
|
30
31
|
end
|
|
31
32
|
@cache.clear
|
|
@@ -50,7 +51,7 @@ module ActiveRecord
|
|
|
50
51
|
|
|
51
52
|
private
|
|
52
53
|
def perform_fetch(type, *args, &block)
|
|
53
|
-
@mapping.fetch(type, block).call(type, *args)
|
|
54
|
+
@mapping.fetch(type, block).call(type, *args).freeze
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
|
|
3
5
|
module ActiveRecord
|
|
4
6
|
module Type
|
|
5
7
|
class Json < ActiveModel::Type::Value
|
|
@@ -11,11 +13,20 @@ module ActiveRecord
|
|
|
11
13
|
|
|
12
14
|
def deserialize(value)
|
|
13
15
|
return value unless value.is_a?(::String)
|
|
14
|
-
|
|
16
|
+
begin
|
|
17
|
+
ActiveSupport::JSON.decode(value)
|
|
18
|
+
rescue JSON::ParserError => e
|
|
19
|
+
# NOTE: This may hide json with duplicate keys. We don't really want to just ignore it
|
|
20
|
+
# but it's the best we can do in order to still allow updating columns that somehow already
|
|
21
|
+
# contain invalid json from some other source.
|
|
22
|
+
# See https://github.com/rails/rails/pull/55536
|
|
23
|
+
ActiveSupport.error_reporter.report(e, source: "application.active_record")
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
15
26
|
end
|
|
16
27
|
|
|
17
28
|
def serialize(value)
|
|
18
|
-
ActiveSupport::JSON.
|
|
29
|
+
ActiveSupport::JSON::Encoding.encode_without_escape(value) unless value.nil?
|
|
19
30
|
end
|
|
20
31
|
|
|
21
32
|
def changed_in_place?(raw_old_value, new_value)
|
|
@@ -9,9 +9,10 @@ module ActiveRecord
|
|
|
9
9
|
|
|
10
10
|
attr_reader :subtype, :coder
|
|
11
11
|
|
|
12
|
-
def initialize(subtype, coder)
|
|
12
|
+
def initialize(subtype, coder, comparable: false)
|
|
13
13
|
@subtype = subtype
|
|
14
14
|
@coder = coder
|
|
15
|
+
@comparable = comparable
|
|
15
16
|
super(subtype)
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -34,9 +35,15 @@ module ActiveRecord
|
|
|
34
35
|
|
|
35
36
|
def changed_in_place?(raw_old_value, value)
|
|
36
37
|
return false if value.nil?
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
if @comparable
|
|
40
|
+
old_value = deserialize(raw_old_value)
|
|
41
|
+
old_value != value
|
|
42
|
+
else
|
|
43
|
+
raw_new_value = encoded(value)
|
|
44
|
+
raw_old_value.nil? != raw_new_value.nil? ||
|
|
45
|
+
subtype.changed_in_place?(raw_old_value, raw_new_value)
|
|
46
|
+
end
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def accessor
|
|
@@ -58,6 +65,11 @@ module ActiveRecord
|
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
private
|
|
68
|
+
# Prevent Ruby 4.0 "delegator does not forward private method" warning.
|
|
69
|
+
# Kernel#inspect calls instance_variables_to_inspect which, without this,
|
|
70
|
+
# triggers Delegator#respond_to_missing? for a private method.
|
|
71
|
+
define_method(:instance_variables_to_inspect, Kernel.instance_method(:instance_variables))
|
|
72
|
+
|
|
61
73
|
def default_value?(value)
|
|
62
74
|
value == coder.load(nil)
|
|
63
75
|
end
|
|
@@ -19,7 +19,8 @@ module ActiveRecord
|
|
|
19
19
|
if schema_cache.data_source_exists?(table_name)
|
|
20
20
|
column = schema_cache.columns_hash(table_name)[attr_name.to_s]
|
|
21
21
|
if column
|
|
22
|
-
|
|
22
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
23
|
+
type = @klass.with_connection { |c| column.fetch_cast_type(c) }
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -7,7 +7,7 @@ module ActiveRecord
|
|
|
7
7
|
context = record_validation_context_for_association(record)
|
|
8
8
|
|
|
9
9
|
if Array(value).reject { |association| valid_object?(association, context) }.any?
|
|
10
|
-
record.errors.add(attribute, :invalid, **options
|
|
10
|
+
record.errors.add(attribute, :invalid, **options, value: value)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -54,17 +54,17 @@ module ActiveRecord
|
|
|
54
54
|
private
|
|
55
55
|
# The check for an existing value should be run from a class that
|
|
56
56
|
# isn't abstract. This means working down from the current class
|
|
57
|
-
# (self), to the first non-abstract class.
|
|
58
|
-
# their subclasses, we have to build the hierarchy between self and
|
|
59
|
-
# the record's class.
|
|
57
|
+
# (self), to the first non-abstract class.
|
|
60
58
|
def find_finder_class_for(record)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
current_class = record.class
|
|
60
|
+
found_class = nil
|
|
61
|
+
loop do
|
|
62
|
+
found_class = current_class unless current_class.abstract_class?
|
|
63
|
+
break if current_class == @klass
|
|
64
|
+
current_class = current_class.superclass
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
found_class
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def validation_needed?(klass, record, attribute)
|