activerecord 7.1.4.1 → 7.2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +643 -2274
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +1 -1
  6. data/lib/active_record/associations/alias_tracker.rb +25 -19
  7. data/lib/active_record/associations/association.rb +15 -8
  8. data/lib/active_record/associations/belongs_to_association.rb +14 -7
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  12. data/lib/active_record/associations/builder/has_many.rb +3 -4
  13. data/lib/active_record/associations/builder/has_one.rb +3 -4
  14. data/lib/active_record/associations/collection_association.rb +7 -1
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/errors.rb +265 -0
  17. data/lib/active_record/associations/has_many_association.rb +1 -1
  18. data/lib/active_record/associations/has_many_through_association.rb +7 -1
  19. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -4
  21. data/lib/active_record/associations/nested_error.rb +47 -0
  22. data/lib/active_record/associations/preloader/association.rb +2 -1
  23. data/lib/active_record/associations/preloader/branch.rb +7 -1
  24. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  25. data/lib/active_record/associations/singular_association.rb +6 -0
  26. data/lib/active_record/associations/through_association.rb +1 -1
  27. data/lib/active_record/associations.rb +59 -292
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  30. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  31. data/lib/active_record/attribute_methods/read.rb +1 -13
  32. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  33. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  34. data/lib/active_record/attribute_methods.rb +54 -63
  35. data/lib/active_record/attributes.rb +61 -47
  36. data/lib/active_record/autosave_association.rb +12 -29
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/callbacks.rb +1 -1
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +270 -65
  42. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  43. data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -74
  44. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  45. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +15 -6
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +6 -0
  53. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  55. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  57. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  61. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +17 -11
  63. data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
  64. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  65. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  66. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  67. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  68. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  71. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  72. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -75
  73. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
  74. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  75. data/lib/active_record/connection_adapters.rb +121 -0
  76. data/lib/active_record/connection_handling.rb +56 -41
  77. data/lib/active_record/core.rb +86 -38
  78. data/lib/active_record/counter_cache.rb +18 -9
  79. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  80. data/lib/active_record/database_configurations/database_config.rb +19 -4
  81. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  82. data/lib/active_record/database_configurations/url_config.rb +20 -1
  83. data/lib/active_record/database_configurations.rb +1 -1
  84. data/lib/active_record/delegated_type.rb +24 -0
  85. data/lib/active_record/dynamic_matchers.rb +2 -2
  86. data/lib/active_record/encryption/encryptable_record.rb +3 -3
  87. data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
  88. data/lib/active_record/encryption/encryptor.rb +18 -3
  89. data/lib/active_record/encryption/key_provider.rb +1 -1
  90. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  91. data/lib/active_record/encryption/message_serializer.rb +4 -0
  92. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  93. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  94. data/lib/active_record/encryption.rb +2 -0
  95. data/lib/active_record/enum.rb +19 -2
  96. data/lib/active_record/errors.rb +46 -20
  97. data/lib/active_record/explain.rb +13 -24
  98. data/lib/active_record/fixtures.rb +37 -31
  99. data/lib/active_record/future_result.rb +8 -4
  100. data/lib/active_record/gem_version.rb +2 -2
  101. data/lib/active_record/inheritance.rb +4 -2
  102. data/lib/active_record/insert_all.rb +18 -15
  103. data/lib/active_record/integration.rb +4 -1
  104. data/lib/active_record/internal_metadata.rb +48 -34
  105. data/lib/active_record/locking/optimistic.rb +7 -6
  106. data/lib/active_record/log_subscriber.rb +0 -21
  107. data/lib/active_record/marshalling.rb +4 -1
  108. data/lib/active_record/message_pack.rb +1 -1
  109. data/lib/active_record/migration/command_recorder.rb +2 -3
  110. data/lib/active_record/migration/compatibility.rb +5 -3
  111. data/lib/active_record/migration/default_strategy.rb +4 -5
  112. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  113. data/lib/active_record/migration.rb +85 -76
  114. data/lib/active_record/model_schema.rb +32 -68
  115. data/lib/active_record/nested_attributes.rb +24 -5
  116. data/lib/active_record/normalization.rb +3 -7
  117. data/lib/active_record/persistence.rb +30 -352
  118. data/lib/active_record/query_cache.rb +19 -8
  119. data/lib/active_record/query_logs.rb +15 -0
  120. data/lib/active_record/querying.rb +21 -9
  121. data/lib/active_record/railtie.rb +42 -57
  122. data/lib/active_record/railties/controller_runtime.rb +13 -4
  123. data/lib/active_record/railties/databases.rake +40 -43
  124. data/lib/active_record/reflection.rb +98 -36
  125. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  126. data/lib/active_record/relation/batches.rb +14 -8
  127. data/lib/active_record/relation/calculations.rb +96 -63
  128. data/lib/active_record/relation/delegation.rb +8 -11
  129. data/lib/active_record/relation/finder_methods.rb +16 -2
  130. data/lib/active_record/relation/merger.rb +4 -6
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  132. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  133. data/lib/active_record/relation/predicate_builder.rb +3 -3
  134. data/lib/active_record/relation/query_methods.rb +224 -58
  135. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  136. data/lib/active_record/relation/spawn_methods.rb +2 -18
  137. data/lib/active_record/relation/where_clause.rb +7 -19
  138. data/lib/active_record/relation.rb +496 -72
  139. data/lib/active_record/result.rb +31 -44
  140. data/lib/active_record/runtime_registry.rb +39 -0
  141. data/lib/active_record/sanitization.rb +24 -19
  142. data/lib/active_record/schema.rb +8 -6
  143. data/lib/active_record/schema_dumper.rb +19 -9
  144. data/lib/active_record/schema_migration.rb +30 -14
  145. data/lib/active_record/scoping/named.rb +1 -0
  146. data/lib/active_record/signed_id.rb +20 -1
  147. data/lib/active_record/statement_cache.rb +7 -7
  148. data/lib/active_record/table_metadata.rb +1 -10
  149. data/lib/active_record/tasks/database_tasks.rb +81 -42
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  152. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  153. data/lib/active_record/test_fixtures.rb +86 -89
  154. data/lib/active_record/testing/query_assertions.rb +121 -0
  155. data/lib/active_record/timestamp.rb +2 -2
  156. data/lib/active_record/token_for.rb +22 -12
  157. data/lib/active_record/touch_later.rb +1 -1
  158. data/lib/active_record/transaction.rb +132 -0
  159. data/lib/active_record/transactions.rb +70 -14
  160. data/lib/active_record/translation.rb +0 -2
  161. data/lib/active_record/type/serialized.rb +1 -3
  162. data/lib/active_record/type_caster/connection.rb +4 -4
  163. data/lib/active_record/validations/associated.rb +9 -3
  164. data/lib/active_record/validations/uniqueness.rb +15 -10
  165. data/lib/active_record/validations.rb +4 -1
  166. data/lib/active_record.rb +148 -39
  167. data/lib/arel/alias_predication.rb +1 -1
  168. data/lib/arel/collectors/bind.rb +2 -0
  169. data/lib/arel/collectors/composite.rb +7 -0
  170. data/lib/arel/collectors/sql_string.rb +1 -1
  171. data/lib/arel/collectors/substitute_binds.rb +1 -1
  172. data/lib/arel/nodes/binary.rb +0 -6
  173. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  174. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  175. data/lib/arel/nodes/node.rb +4 -3
  176. data/lib/arel/nodes/sql_literal.rb +7 -0
  177. data/lib/arel/nodes.rb +2 -2
  178. data/lib/arel/predications.rb +1 -1
  179. data/lib/arel/select_manager.rb +1 -1
  180. data/lib/arel/tree_manager.rb +3 -2
  181. data/lib/arel/update_manager.rb +2 -1
  182. data/lib/arel/visitors/dot.rb +1 -0
  183. data/lib/arel/visitors/mysql.rb +9 -4
  184. data/lib/arel/visitors/postgresql.rb +1 -12
  185. data/lib/arel/visitors/sqlite.rb +25 -0
  186. data/lib/arel/visitors/to_sql.rb +29 -16
  187. data/lib/arel.rb +7 -3
  188. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  189. metadata +18 -12
@@ -77,7 +77,7 @@ module ActiveRecord
77
77
  end
78
78
 
79
79
  def current_time_from_proper_timezone
80
- connection.default_timezone == :utc ? Time.now.utc : Time.now
80
+ with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now }
81
81
  end
82
82
 
83
83
  protected
@@ -162,7 +162,7 @@ module ActiveRecord
162
162
 
163
163
  def max_updated_column_timestamp
164
164
  timestamp_attributes_for_update_in_model
165
- .filter_map { |attr| self[attr]&.to_time }
165
+ .filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
166
166
  .max
167
167
  end
168
168
 
@@ -35,6 +35,24 @@ module ActiveRecord
35
35
  end
36
36
  end
37
37
 
38
+ module RelationMethods
39
+ # Finds a record using a given +token+ for a predefined +purpose+. Returns
40
+ # +nil+ if the token is invalid or the record was not found.
41
+ def find_by_token_for(purpose, token)
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) }
44
+ end
45
+
46
+ # Finds a record using a given +token+ for a predefined +purpose+. Raises
47
+ # ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
48
+ # (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
49
+ # the token is valid but the record was not found.
50
+ def find_by_token_for!(purpose, token)
51
+ model.token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
52
+ (raise ActiveSupport::MessageVerifier::InvalidSignature)
53
+ end
54
+ end
55
+
38
56
  module ClassMethods
39
57
  # Defines the behavior of tokens generated for a specific +purpose+.
40
58
  # A token can be generated by calling TokenFor#generate_token_for on a
@@ -85,20 +103,12 @@ module ActiveRecord
85
103
  self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
86
104
  end
87
105
 
88
- # Finds a record using a given +token+ for a predefined +purpose+. Returns
89
- # +nil+ if the token is invalid or the record was not found.
90
- def find_by_token_for(purpose, token)
91
- raise UnknownPrimaryKey.new(self) unless primary_key
92
- token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(primary_key => id) }
106
+ def find_by_token_for(purpose, token) # :nodoc:
107
+ all.find_by_token_for(purpose, token)
93
108
  end
94
109
 
95
- # Finds a record using a given +token+ for a predefined +purpose+. Raises
96
- # ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
97
- # (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
98
- # the token is valid but the record was not found.
99
- def find_by_token_for!(purpose, token)
100
- token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
101
- (raise ActiveSupport::MessageVerifier::InvalidSignature)
110
+ def find_by_token_for!(purpose, token) # :nodoc:
111
+ all.find_by_token_for!(purpose, token)
102
112
  end
103
113
  end
104
114
 
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  end
65
65
 
66
66
  def has_defer_touch_attrs?
67
- defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
67
+ @_defer_touch_attrs.present?
68
68
  end
69
69
  end
70
70
  end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/digest"
4
+
5
+ module ActiveRecord
6
+ # Class specifies the interface to interact with the current transaction state.
7
+ #
8
+ # It can either map to an actual transaction/savepoint, or represent the
9
+ # absence of a transaction.
10
+ #
11
+ # == State
12
+ #
13
+ # We say that a transaction is _finalized_ when it wraps a real transaction
14
+ # that has been either committed or rolled back.
15
+ #
16
+ # A transaction is _open_ if it wraps a real transaction that is not finalized.
17
+ #
18
+ # On the other hand, a transaction is _closed_ when it is not open. That is,
19
+ # when it represents absence of transaction, or it wraps a real but finalized
20
+ # one.
21
+ #
22
+ # You can check whether a transaction is open or closed with the +open?+ and
23
+ # +closed?+ predicates:
24
+ #
25
+ # if Article.current_transaction.open?
26
+ # # We are inside a real and not finalized transaction.
27
+ # end
28
+ #
29
+ # Closed transactions are `blank?` too.
30
+ #
31
+ # == Callbacks
32
+ #
33
+ # After updating the database state, you may sometimes need to perform some extra work, or reflect these
34
+ # changes in a remote system like clearing or updating a cache:
35
+ #
36
+ # def publish_article(article)
37
+ # article.update!(published: true)
38
+ # NotificationService.article_published(article)
39
+ # end
40
+ #
41
+ # The above code works but has one important flaw, which is that it no longer works properly if called inside
42
+ # a transaction, as it will interact with the remote system before the changes are persisted:
43
+ #
44
+ # Article.transaction do
45
+ # article = create_article(article)
46
+ # publish_article(article)
47
+ # end
48
+ #
49
+ # The callbacks offered by ActiveRecord::Transaction allow to rewriting this method in a way that is compatible
50
+ # with transactions:
51
+ #
52
+ # def publish_article(article)
53
+ # article.update!(published: true)
54
+ # Article.current_transaction.after_commit do
55
+ # NotificationService.article_published(article)
56
+ # end
57
+ # end
58
+ #
59
+ # In the above example, if +publish_article+ is called inside a transaction, the callback will be invoked
60
+ # after the transaction is successfully committed, and if called outside a transaction, the callback will be invoked
61
+ # immediately.
62
+ #
63
+ # == Caveats
64
+ #
65
+ # When using after_commit callbacks, it is important to note that if the callback raises an error, the transaction
66
+ # won't be rolled back as it was already committed. Relying solely on these to synchronize state between multiple
67
+ # systems may lead to consistency issues.
68
+ class Transaction
69
+ def initialize(internal_transaction) # :nodoc:
70
+ @internal_transaction = internal_transaction
71
+ @uuid = nil
72
+ end
73
+
74
+ # Registers a block to be called after the transaction is fully committed.
75
+ #
76
+ # If there is no currently open transactions, the block is called
77
+ # immediately, unless the transaction is finalized, in which case attempting
78
+ # to register the callback raises ActiveRecord::ActiveRecordError.
79
+ #
80
+ # If the transaction has a parent transaction, the callback is transferred to
81
+ # the parent when the current transaction commits, or dropped when the current transaction
82
+ # is rolled back. This operation is repeated until the outermost transaction is reached.
83
+ #
84
+ # If the callback raises an error, the transaction remains committed.
85
+ def after_commit(&block)
86
+ if @internal_transaction.nil?
87
+ yield
88
+ else
89
+ @internal_transaction.after_commit(&block)
90
+ end
91
+ end
92
+
93
+ # Registers a block to be called after the transaction is rolled back.
94
+ #
95
+ # If there is no currently open transactions, the block is not called. But
96
+ # if the transaction is finalized, attempting to register the callback
97
+ # raises ActiveRecord::ActiveRecordError.
98
+ #
99
+ # If the transaction is successfully committed but has a parent
100
+ # transaction, the callback is automatically added to the parent transaction.
101
+ #
102
+ # If the entire chain of nested transactions are all successfully committed,
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
+ def after_rollback(&block)
108
+ @internal_transaction&.after_rollback(&block)
109
+ end
110
+
111
+ # Returns true if the transaction exists and isn't finalized yet.
112
+ def open?
113
+ !closed?
114
+ end
115
+
116
+ # Returns true if the transaction doesn't exist or is finalized.
117
+ def closed?
118
+ @internal_transaction.nil? || @internal_transaction.state.finalized?
119
+ end
120
+
121
+ alias_method :blank?, :closed?
122
+
123
+ # Returns a UUID for this transaction or +nil+ if no transaction is open.
124
+ def uuid
125
+ if @internal_transaction
126
+ @uuid ||= Digest::UUID.uuid_v4
127
+ end
128
+ end
129
+
130
+ NULL_TRANSACTION = new(nil).freeze
131
+ end
132
+ end
@@ -188,6 +188,27 @@ module ActiveRecord
188
188
  # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from
189
189
  # within a transaction could trigger the cache to be regenerated before the database is updated.
190
190
  #
191
+ # ==== NOTE: Callbacks are deduplicated per callback by filter.
192
+ #
193
+ # Trying to define multiple callbacks with the same filter will result in a single callback being run.
194
+ #
195
+ # For example:
196
+ #
197
+ # after_commit :do_something
198
+ # after_commit :do_something # only the last one will be called
199
+ #
200
+ # This applies to all variations of <tt>after_*_commit</tt> callbacks as well.
201
+ #
202
+ # after_commit :do_something
203
+ # after_create_commit :do_something
204
+ # after_save_commit :do_something
205
+ #
206
+ # It is recommended to use the +on:+ option to specify when the callback should be run.
207
+ #
208
+ # after_commit :do_something, on: [:create, :update]
209
+ #
210
+ # This is equivalent to using +after_create_commit+ and +after_update_commit+, but will not be deduplicated.
211
+ #
191
212
  # === Caveats
192
213
  #
193
214
  # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
@@ -198,9 +219,9 @@ module ActiveRecord
198
219
  # database error will occur because the savepoint has already been
199
220
  # automatically released. The following example demonstrates the problem:
200
221
  #
201
- # Model.connection.transaction do # BEGIN
202
- # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
203
- # Model.connection.create_table(...) # active_record_1 now automatically released
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
204
225
  # end # RELEASE SAVEPOINT active_record_1
205
226
  # # ^^^^ BOOM! database error!
206
227
  # end
@@ -209,7 +230,20 @@ module ActiveRecord
209
230
  module ClassMethods
210
231
  # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
211
232
  def transaction(**options, &block)
212
- connection.transaction(**options, &block)
233
+ with_connection do |connection|
234
+ connection.transaction(**options, &block)
235
+ end
236
+ end
237
+
238
+ # Returns a representation of the current transaction state,
239
+ # which can be a top level transaction, a savepoint, or the absence of a transaction.
240
+ #
241
+ # An object is always returned, whether or not a transaction is currently active.
242
+ # To check if a transaction was opened, use <tt>current_transaction.open?</tt>.
243
+ #
244
+ # See the ActiveRecord::Transaction documentation for detailed behavior.
245
+ def current_transaction
246
+ connection_pool.active_connection&.current_transaction&.user_transaction || Transaction::NULL_TRANSACTION
213
247
  end
214
248
 
215
249
  def before_commit(*args, &block) # :nodoc:
@@ -266,6 +300,25 @@ module ActiveRecord
266
300
  set_callback(:rollback, :after, *args, &block)
267
301
  end
268
302
 
303
+ # Similar to ActiveSupport::Callbacks::ClassMethods#set_callback, but with
304
+ # support for options available on #after_commit and #after_rollback callbacks.
305
+ def set_callback(name, *filter_list, &block)
306
+ options = filter_list.extract_options!
307
+ filter_list << options
308
+
309
+ if name.in?([:commit, :rollback]) && options[:on]
310
+ fire_on = Array(options[:on])
311
+ assert_valid_transaction_action(fire_on)
312
+ options[:if] = [
313
+ -> { transaction_include_any_action?(fire_on) },
314
+ *options[:if]
315
+ ]
316
+ end
317
+
318
+
319
+ super(name, *filter_list, &block)
320
+ end
321
+
269
322
  private
270
323
  def prepend_option
271
324
  if ActiveRecord.run_after_transaction_callbacks_in_order_defined
@@ -354,18 +407,19 @@ module ActiveRecord
354
407
  # This method is available within the context of an ActiveRecord::Base
355
408
  # instance.
356
409
  def with_transaction_returning_status
357
- status = nil
358
- connection = self.class.connection
359
- ensure_finalize = !connection.transaction_open?
410
+ self.class.with_connection do |connection|
411
+ status = nil
412
+ ensure_finalize = !connection.transaction_open?
360
413
 
361
- connection.transaction do
362
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
363
- remember_transaction_record_state
414
+ connection.transaction do
415
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
416
+ remember_transaction_record_state
364
417
 
365
- status = yield
366
- raise ActiveRecord::Rollback unless status
418
+ status = yield
419
+ raise ActiveRecord::Rollback unless status
420
+ end
421
+ status
367
422
  end
368
- status
369
423
  end
370
424
 
371
425
  def trigger_transactional_callbacks? # :nodoc:
@@ -457,7 +511,9 @@ module ActiveRecord
457
511
  # Add the record to the current transaction so that the #after_rollback and #after_commit
458
512
  # callbacks can be called.
459
513
  def add_to_transaction(ensure_finalize = true)
460
- self.class.connection.add_transaction_record(self, ensure_finalize)
514
+ self.class.with_connection do |connection|
515
+ connection.add_transaction_record(self, ensure_finalize)
516
+ end
461
517
  end
462
518
 
463
519
  def has_transactional_callbacks?
@@ -2,8 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Translation
5
- include ActiveModel::Translation
6
-
7
5
  # Set the lookup ancestors for ActiveModel.
8
6
  def lookup_ancestors # :nodoc:
9
7
  klass = self
@@ -30,9 +30,7 @@ module ActiveRecord
30
30
  end
31
31
  end
32
32
 
33
- def inspect
34
- Kernel.instance_method(:inspect).bind_call(self)
35
- end
33
+ define_method(:inspect, Kernel.instance_method(:inspect))
36
34
 
37
35
  def changed_in_place?(raw_old_value, value)
38
36
  return false if value.nil?
@@ -14,18 +14,18 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  def type_for_attribute(attr_name)
17
- schema_cache = connection.schema_cache
17
+ schema_cache = @klass.schema_cache
18
18
 
19
19
  if schema_cache.data_source_exists?(table_name)
20
20
  column = schema_cache.columns_hash(table_name)[attr_name.to_s]
21
- type = connection.lookup_cast_type_from_column(column) if column
21
+ if column
22
+ type = @klass.with_connection { |connection| connection.lookup_cast_type_from_column(column) }
23
+ end
22
24
  end
23
25
 
24
26
  type || Type.default_value
25
27
  end
26
28
 
27
- delegate :connection, to: :@klass, private: true
28
-
29
29
  private
30
30
  attr_reader :table_name
31
31
  end
@@ -4,14 +4,20 @@ module ActiveRecord
4
4
  module Validations
5
5
  class AssociatedValidator < ActiveModel::EachValidator # :nodoc:
6
6
  def validate_each(record, attribute, value)
7
- if Array(value).reject { |r| valid_object?(r) }.any?
7
+ context = record_validation_context_for_association(record)
8
+
9
+ if Array(value).reject { |association| valid_object?(association, context) }.any?
8
10
  record.errors.add(attribute, :invalid, **options.merge(value: value))
9
11
  end
10
12
  end
11
13
 
12
14
  private
13
- def valid_object?(record)
14
- (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?
15
+ def valid_object?(record, context)
16
+ (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid?(context)
17
+ end
18
+
19
+ def record_validation_context_for_association(record)
20
+ record.custom_validation_context? ? record.validation_context : nil
15
21
  end
16
22
  end
17
23
 
@@ -14,6 +14,7 @@ module ActiveRecord
14
14
  end
15
15
  super
16
16
  @klass = options[:class]
17
+ @klass = @klass.superclass if @klass.singleton_class?
17
18
  end
18
19
 
19
20
  def validate_each(record, attribute, value)
@@ -84,7 +85,7 @@ module ActiveRecord
84
85
  attributes = scope + [attr]
85
86
  attributes = resolve_attributes(record, attributes)
86
87
 
87
- klass.connection.schema_cache.indexes(klass.table_name).any? do |index|
88
+ klass.schema_cache.indexes(klass.table_name).any? do |index|
88
89
  index.unique &&
89
90
  index.where.nil? &&
90
91
  (Array(index.columns) - attributes).empty?
@@ -110,16 +111,20 @@ module ActiveRecord
110
111
 
111
112
  def build_relation(klass, attribute, value)
112
113
  relation = klass.unscoped
113
- comparison = relation.bind_attribute(attribute, value) do |attr, bind|
114
- return relation.none! if bind.unboundable?
114
+ # TODO: Add case-sensitive / case-insensitive operators to Arel
115
+ # to no longer need to checkout a connection here.
116
+ comparison = klass.with_connection do |connection|
117
+ relation.bind_attribute(attribute, value) do |attr, bind|
118
+ return relation.none! if bind.unboundable?
115
119
 
116
- if !options.key?(:case_sensitive) || bind.nil?
117
- klass.connection.default_uniqueness_comparison(attr, bind)
118
- elsif options[:case_sensitive]
119
- klass.connection.case_sensitive_comparison(attr, bind)
120
- else
121
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
122
- klass.connection.case_insensitive_comparison(attr, bind)
120
+ if !options.key?(:case_sensitive) || bind.nil?
121
+ connection.default_uniqueness_comparison(attr, bind)
122
+ elsif options[:case_sensitive]
123
+ connection.case_sensitive_comparison(attr, bind)
124
+ else
125
+ # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
126
+ connection.case_insensitive_comparison(attr, bind)
127
+ end
123
128
  end
124
129
  end
125
130
 
@@ -39,7 +39,6 @@ module ActiveRecord
39
39
  # {new_record?}[rdoc-ref:Persistence#new_record?].
40
40
  module Validations
41
41
  extend ActiveSupport::Concern
42
- include ActiveModel::Validations
43
42
 
44
43
  # The validation process on save can be skipped by passing <tt>validate: false</tt>.
45
44
  # The validation context can be changed by passing <tt>context: context</tt>.
@@ -75,6 +74,10 @@ module ActiveRecord
75
74
 
76
75
  alias_method :validate, :valid?
77
76
 
77
+ def custom_validation_context? # :nodoc:
78
+ validation_context && [:create, :update].exclude?(validation_context)
79
+ end
80
+
78
81
  private
79
82
  def default_validation_context
80
83
  new_record? ? :create : :update