activerecord 7.1.5.2 → 8.1.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.
Files changed (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/digest"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  # = Active Record Connection Adapters Transaction State
@@ -89,7 +91,9 @@ module ActiveRecord
89
91
  raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
90
92
  @started = true
91
93
 
92
- @payload = @base_payload.dup
94
+ ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload)
95
+
96
+ @payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome.
93
97
  @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
94
98
  @handle.start
95
99
  end
@@ -104,28 +108,64 @@ module ActiveRecord
104
108
  end
105
109
 
106
110
  class NullTransaction # :nodoc:
107
- def initialize; end
108
111
  def state; end
109
112
  def closed?; true; end
110
113
  def open?; false; end
111
114
  def joinable?; false; end
115
+ def isolation; nil; end
112
116
  def add_record(record, _ = true); end
113
117
  def restartable?; false; end
114
118
  def dirty?; false; end
115
119
  def dirty!; end
116
120
  def invalidated?; false; end
117
121
  def invalidate!; end
122
+ def materialized?; false; end
123
+ def before_commit; yield; end
124
+ def after_commit; yield; end
125
+ def after_rollback; end
126
+ def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
127
+ def isolation=(_); end
118
128
  end
119
129
 
120
130
  class Transaction # :nodoc:
121
- attr_reader :connection, :state, :savepoint_name, :isolation_level
131
+ class Callback # :nodoc:
132
+ def initialize(event, callback)
133
+ @event = event
134
+ @callback = callback
135
+ end
136
+
137
+ def before_commit
138
+ @callback.call if @event == :before_commit
139
+ end
140
+
141
+ def after_commit
142
+ @callback.call if @event == :after_commit
143
+ end
144
+
145
+ def after_rollback
146
+ @callback.call if @event == :after_rollback
147
+ end
148
+ end
149
+
150
+ attr_reader :connection, :state, :savepoint_name, :isolation_level, :user_transaction
122
151
  attr_accessor :written
123
152
 
124
153
  delegate :invalidate!, :invalidated?, to: :@state
125
154
 
155
+ # Returns the isolation level if it was explicitly set, nil otherwise
156
+ def isolation
157
+ @isolation_level
158
+ end
159
+
160
+ def isolation=(isolation) # :nodoc:
161
+ @isolation_level = isolation
162
+ end
163
+
126
164
  def initialize(connection, isolation: nil, joinable: true, run_commit_callbacks: false)
165
+ super()
127
166
  @connection = connection
128
167
  @state = TransactionState.new
168
+ @callbacks = nil
129
169
  @records = nil
130
170
  @isolation_level = isolation
131
171
  @materialized = false
@@ -133,7 +173,8 @@ module ActiveRecord
133
173
  @run_commit_callbacks = run_commit_callbacks
134
174
  @lazy_enrollment_records = nil
135
175
  @dirty = false
136
- @instrumenter = TransactionInstrumenter.new(connection: connection)
176
+ @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION
177
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction)
137
178
  end
138
179
 
139
180
  def dirty!
@@ -144,6 +185,14 @@ module ActiveRecord
144
185
  @dirty
145
186
  end
146
187
 
188
+ def open?
189
+ !closed?
190
+ end
191
+
192
+ def closed?
193
+ @state.finalized?
194
+ end
195
+
147
196
  def add_record(record, ensure_finalize = true)
148
197
  @records ||= []
149
198
  if ensure_finalize
@@ -154,6 +203,30 @@ module ActiveRecord
154
203
  end
155
204
  end
156
205
 
206
+ def before_commit(&block)
207
+ if @state.finalized?
208
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
209
+ end
210
+
211
+ (@callbacks ||= []) << Callback.new(:before_commit, block)
212
+ end
213
+
214
+ def after_commit(&block)
215
+ if @state.finalized?
216
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
217
+ end
218
+
219
+ (@callbacks ||= []) << Callback.new(:after_commit, block)
220
+ end
221
+
222
+ def after_rollback(&block)
223
+ if @state.finalized?
224
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
225
+ end
226
+
227
+ (@callbacks ||= []) << Callback.new(:after_rollback, block)
228
+ end
229
+
157
230
  def records
158
231
  if @lazy_enrollment_records
159
232
  @records.concat @lazy_enrollment_records.values
@@ -190,66 +263,85 @@ module ActiveRecord
190
263
  end
191
264
 
192
265
  def rollback_records
193
- return unless records
194
-
195
- ite = unique_records
266
+ if records
267
+ begin
268
+ ite = unique_records
196
269
 
197
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
270
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
198
271
 
199
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
200
- record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
201
- end
202
- ensure
203
- ite&.each do |i|
204
- i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
272
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
273
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
274
+ end
275
+ ensure
276
+ ite&.each do |i|
277
+ i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
278
+ end
279
+ end
205
280
  end
281
+
282
+ @callbacks&.each(&:after_rollback)
206
283
  end
207
284
 
208
285
  def before_commit_records
209
- return unless records
210
-
211
286
  if @run_commit_callbacks
212
- if ActiveRecord.before_committed_on_all_records
213
- ite = unique_records
287
+ if records
288
+ if ActiveRecord.before_committed_on_all_records
289
+ ite = unique_records
214
290
 
215
- instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
216
- candidates[record] = record
217
- end
291
+ instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
292
+ candidates[record] = record
293
+ end
218
294
 
219
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
220
- record.before_committed! if should_run_callbacks
295
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
296
+ record.before_committed! if should_run_callbacks
297
+ end
298
+ else
299
+ records.uniq.each(&:before_committed!)
221
300
  end
222
- else
223
- records.uniq.each(&:before_committed!)
224
301
  end
302
+
303
+ @callbacks&.each(&:before_commit)
225
304
  end
305
+ # Note: When @run_commit_callbacks is false #commit_records takes care of appending
306
+ # remaining callbacks to the parent transaction
226
307
  end
227
308
 
228
309
  def commit_records
229
- return unless records
310
+ if records
311
+ begin
312
+ ite = unique_records
230
313
 
231
- ite = unique_records
314
+ if @run_commit_callbacks
315
+ instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
232
316
 
233
- if @run_commit_callbacks
234
- instances_to_run_callbacks_on = prepare_instances_to_run_callbacks_on(ite)
235
-
236
- run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
237
- record.committed!(should_run_callbacks: should_run_callbacks)
238
- end
239
- else
240
- while record = ite.shift
241
- # if not running callbacks, only adds the record to the parent transaction
242
- connection.add_transaction_record(record)
317
+ run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
318
+ record.committed!(should_run_callbacks: should_run_callbacks)
319
+ end
320
+ else
321
+ while record = ite.shift
322
+ # if not running callbacks, only adds the record to the parent transaction
323
+ connection.add_transaction_record(record)
324
+ end
325
+ end
326
+ ensure
327
+ ite&.each { |i| i.committed!(should_run_callbacks: false) }
243
328
  end
244
329
  end
245
- ensure
246
- ite&.each { |i| i.committed!(should_run_callbacks: false) }
330
+
331
+ if @run_commit_callbacks
332
+ @callbacks&.each(&:after_commit)
333
+ elsif @callbacks
334
+ connection.current_transaction.append_callbacks(@callbacks)
335
+ end
247
336
  end
248
337
 
249
338
  def full_rollback?; true; end
250
339
  def joinable?; @joinable; end
251
- def closed?; false; end
252
- def open?; !closed?; end
340
+
341
+ protected
342
+ def append_callbacks(callbacks) # :nodoc:
343
+ (@callbacks ||= []).concat(callbacks)
344
+ end
253
345
 
254
346
  private
255
347
  def unique_records
@@ -305,7 +397,7 @@ module ActiveRecord
305
397
  @parent.state.add_child(@state)
306
398
  end
307
399
 
308
- delegate :materialize!, :materialized?, :restart, to: :@parent
400
+ delegate :materialize!, :materialized?, :restart, :isolation, to: :@parent
309
401
 
310
402
  def rollback
311
403
  @state.rollback!
@@ -324,6 +416,7 @@ module ActiveRecord
324
416
  def initialize(connection, savepoint_name, parent_transaction, **options)
325
417
  super(connection, **options)
326
418
 
419
+ @parent_transaction = parent_transaction
327
420
  parent_transaction.state.add_child(@state)
328
421
 
329
422
  if isolation_level
@@ -333,6 +426,15 @@ module ActiveRecord
333
426
  @savepoint_name = savepoint_name
334
427
  end
335
428
 
429
+ # Delegates to parent transaction's isolation level
430
+ def isolation
431
+ @parent_transaction.isolation
432
+ end
433
+
434
+ def isolation=(isolation) # :nodoc:
435
+ @parent_transaction.isolation = isolation
436
+ end
437
+
336
438
  def materialize!
337
439
  connection.create_savepoint(savepoint_name)
338
440
  super
@@ -349,7 +451,7 @@ module ActiveRecord
349
451
 
350
452
  def rollback
351
453
  unless @state.invalidated?
352
- connection.rollback_to_savepoint(savepoint_name) if materialized?
454
+ connection.rollback_to_savepoint(savepoint_name) if materialized? && connection.active?
353
455
  end
354
456
  @state.rollback!
355
457
  @instrumenter.finish(:rollback) if materialized?
@@ -367,10 +469,14 @@ module ActiveRecord
367
469
  # = Active Record Real \Transaction
368
470
  class RealTransaction < Transaction
369
471
  def materialize!
370
- if isolation_level
371
- connection.begin_isolated_db_transaction(isolation_level)
472
+ if joinable?
473
+ if isolation_level
474
+ connection.begin_isolated_db_transaction(isolation_level)
475
+ else
476
+ connection.begin_db_transaction
477
+ end
372
478
  else
373
- connection.begin_db_transaction
479
+ connection.begin_deferred_transaction(isolation_level)
374
480
  end
375
481
 
376
482
  super
@@ -391,13 +497,19 @@ module ActiveRecord
391
497
  end
392
498
 
393
499
  def rollback
394
- connection.rollback_db_transaction if materialized?
500
+ if materialized?
501
+ connection.rollback_db_transaction
502
+ connection.reset_isolation_level if isolation_level
503
+ end
395
504
  @state.full_rollback!
396
505
  @instrumenter.finish(:rollback) if materialized?
397
506
  end
398
507
 
399
508
  def commit
400
- connection.commit_db_transaction if materialized?
509
+ if materialized?
510
+ connection.commit_db_transaction
511
+ connection.reset_isolation_level if isolation_level
512
+ end
401
513
  @state.full_commit!
402
514
  @instrumenter.finish(:commit) if materialized?
403
515
  end
@@ -529,12 +641,11 @@ module ActiveRecord
529
641
  end
530
642
 
531
643
  def within_new_transaction(isolation: nil, joinable: true)
644
+ isolation ||= @connection.pool.pool_transaction_isolation_level
532
645
  @connection.lock.synchronize do
533
646
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
534
647
  begin
535
- ret = yield
536
- completed = true
537
- ret
648
+ yield transaction.user_transaction
538
649
  rescue Exception => error
539
650
  rollback_transaction
540
651
  after_failure_actions(transaction, error)
@@ -542,24 +653,8 @@ module ActiveRecord
542
653
  raise
543
654
  ensure
544
655
  unless error
545
- # In 7.1 we enforce timeout >= 0.4.0 which no longer use throw, so we can
546
- # go back to the original behavior of committing on non-local return.
547
- # If users are using throw, we assume it's not an error case.
548
- completed = true if ActiveRecord.commit_transaction_on_non_local_return
549
-
550
656
  if Thread.current.status == "aborting"
551
657
  rollback_transaction
552
- elsif !completed && transaction.written
553
- ActiveRecord.deprecator.warn(<<~EOW)
554
- A transaction is being rolled back because the transaction block was
555
- exited using `return`, `break` or `throw`.
556
- In Rails 7.2 this transaction will be committed instead.
557
- To opt-in to the new behavior now and suppress this warning
558
- you can set:
559
-
560
- Rails.application.config.active_record.commit_transaction_on_non_local_return = true
561
- EOW
562
- rollback_transaction
563
658
  else
564
659
  begin
565
660
  commit_transaction
@@ -590,7 +685,7 @@ module ActiveRecord
590
685
  end
591
686
 
592
687
  private
593
- NULL_TRANSACTION = NullTransaction.new
688
+ NULL_TRANSACTION = NullTransaction.new.freeze
594
689
 
595
690
  # Deallocate invalidated prepared statements outside of the transaction
596
691
  def after_failure_actions(transaction, error)