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.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. 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
- use_transactional_tests &&
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
- unless @fixture_connection_pools.include?(pool)
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 = handler.send(:connection_name_to_pool_manager)[name]
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
- next unless pool_manager.get_pool_config(role, shard_name)
235
-
236
- pool_manager.set_pool_config(role, shard_name, pool_config)
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
- # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
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] = nil
173
- clear_attribute_change(attribute_name)
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 `blank?` too.
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.state.finalized?
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.lease_connection.transaction do # BEGIN
223
- # Model.lease_connection.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
- # # ^^^^ BOOM! database error!
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.transaction(**options, &block)
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!(args, prepend_option)
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 ], **prepend_option)
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, **prepend_option)
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, **prepend_option)
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, **prepend_option)
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!(args, prepend_option)
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 prepend_option
324
- if ActiveRecord.run_after_transaction_callbacks_in_order_defined
325
- { prepend: true }
326
- else
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
- status = nil
412
- ensure_finalize = !connection.transaction_open?
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
- connection.transaction do
415
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
416
- remember_transaction_record_state
437
+ connection.transaction do
438
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
439
+ remember_transaction_record_state
417
440
 
418
- status = yield
419
- raise ActiveRecord::Rollback unless status
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
@@ -16,6 +16,13 @@ module ActiveRecord
16
16
  def default_timezone
17
17
  @timezone || ActiveRecord.default_timezone
18
18
  end
19
+
20
+ def ==(other)
21
+ super(other) && timezone == other.timezone
22
+ end
23
+
24
+ protected
25
+ attr_reader :timezone
19
26
  end
20
27
  end
21
28
  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
- ActiveSupport::JSON.decode(value) rescue nil
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.encode(value) unless value.nil?
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
- raw_new_value = encoded(value)
38
- raw_old_value.nil? != raw_new_value.nil? ||
39
- subtype.changed_in_place?(raw_old_value, raw_new_value)
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
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  end
47
47
 
48
48
  if matching_pair
49
- matching_pair.last.call(lookup_key)
49
+ matching_pair.last.call(lookup_key).freeze
50
50
  elsif @parent
51
51
  @parent.perform_fetch(lookup_key, &block)
52
52
  else
@@ -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
- type = @klass.with_connection { |connection| connection.lookup_cast_type_from_column(column) }
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.merge(value: value))
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. Since classes don't know
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
- class_hierarchy = [record.class]
62
-
63
- while class_hierarchy.first != @klass
64
- class_hierarchy.unshift(class_hierarchy.first.superclass)
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
- class_hierarchy.detect { |klass| !klass.abstract_class? }
67
+ found_class
68
68
  end
69
69
 
70
70
  def validation_needed?(klass, record, attribute)