activerecord 7.1.5.1 → 7.2.0.beta1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -2445
  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 +9 -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 +6 -4
  15. data/lib/active_record/associations/collection_proxy.rb +14 -1
  16. data/lib/active_record/associations/has_many_association.rb +1 -1
  17. data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
  18. data/lib/active_record/associations/join_dependency.rb +5 -5
  19. data/lib/active_record/associations/nested_error.rb +47 -0
  20. data/lib/active_record/associations/preloader/association.rb +2 -1
  21. data/lib/active_record/associations/preloader/branch.rb +7 -1
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  23. data/lib/active_record/associations/singular_association.rb +6 -0
  24. data/lib/active_record/associations/through_association.rb +1 -1
  25. data/lib/active_record/associations.rb +33 -16
  26. data/lib/active_record/attribute_assignment.rb +1 -11
  27. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  29. data/lib/active_record/attribute_methods/primary_key.rb +23 -55
  30. data/lib/active_record/attribute_methods/read.rb +4 -16
  31. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  32. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +60 -71
  35. data/lib/active_record/attributes.rb +55 -42
  36. data/lib/active_record/autosave_association.rb +13 -32
  37. data/lib/active_record/base.rb +2 -3
  38. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
  39. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
  40. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
  41. data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
  42. data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
  43. data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  45. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
  46. data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
  47. data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
  48. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
  49. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  50. data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
  51. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
  52. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
  53. data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
  54. data/lib/active_record/connection_adapters/pool_config.rb +7 -6
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
  56. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  59. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
  61. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
  62. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
  63. data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
  64. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
  67. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  68. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  69. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
  72. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
  73. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
  74. data/lib/active_record/connection_adapters.rb +121 -0
  75. data/lib/active_record/connection_handling.rb +56 -41
  76. data/lib/active_record/core.rb +53 -37
  77. data/lib/active_record/counter_cache.rb +18 -9
  78. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  79. data/lib/active_record/database_configurations/database_config.rb +15 -4
  80. data/lib/active_record/database_configurations/hash_config.rb +38 -34
  81. data/lib/active_record/database_configurations/url_config.rb +20 -1
  82. data/lib/active_record/database_configurations.rb +1 -1
  83. data/lib/active_record/delegated_type.rb +24 -0
  84. data/lib/active_record/dynamic_matchers.rb +2 -2
  85. data/lib/active_record/encryption/encryptable_record.rb +2 -2
  86. data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
  87. data/lib/active_record/encryption/encryptor.rb +17 -2
  88. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  89. data/lib/active_record/encryption/message_serializer.rb +4 -0
  90. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  91. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  92. data/lib/active_record/encryption.rb +0 -2
  93. data/lib/active_record/enum.rb +10 -1
  94. data/lib/active_record/errors.rb +16 -11
  95. data/lib/active_record/explain.rb +13 -24
  96. data/lib/active_record/fixtures.rb +37 -31
  97. data/lib/active_record/future_result.rb +8 -4
  98. data/lib/active_record/gem_version.rb +3 -3
  99. data/lib/active_record/inheritance.rb +4 -2
  100. data/lib/active_record/insert_all.rb +18 -15
  101. data/lib/active_record/integration.rb +4 -1
  102. data/lib/active_record/internal_metadata.rb +48 -34
  103. data/lib/active_record/locking/optimistic.rb +7 -6
  104. data/lib/active_record/log_subscriber.rb +0 -21
  105. data/lib/active_record/marshalling.rb +1 -4
  106. data/lib/active_record/message_pack.rb +1 -1
  107. data/lib/active_record/migration/command_recorder.rb +2 -3
  108. data/lib/active_record/migration/compatibility.rb +5 -3
  109. data/lib/active_record/migration/default_strategy.rb +4 -5
  110. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  111. data/lib/active_record/migration.rb +85 -76
  112. data/lib/active_record/model_schema.rb +28 -68
  113. data/lib/active_record/nested_attributes.rb +13 -16
  114. data/lib/active_record/normalization.rb +3 -7
  115. data/lib/active_record/persistence.rb +30 -352
  116. data/lib/active_record/query_cache.rb +18 -6
  117. data/lib/active_record/query_logs.rb +15 -0
  118. data/lib/active_record/querying.rb +21 -9
  119. data/lib/active_record/railtie.rb +50 -62
  120. data/lib/active_record/railties/controller_runtime.rb +13 -4
  121. data/lib/active_record/railties/databases.rake +41 -44
  122. data/lib/active_record/reflection.rb +90 -35
  123. data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
  124. data/lib/active_record/relation/batches.rb +3 -3
  125. data/lib/active_record/relation/calculations.rb +94 -61
  126. data/lib/active_record/relation/delegation.rb +8 -11
  127. data/lib/active_record/relation/finder_methods.rb +16 -2
  128. data/lib/active_record/relation/merger.rb +4 -6
  129. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  130. data/lib/active_record/relation/predicate_builder.rb +3 -3
  131. data/lib/active_record/relation/query_methods.rb +196 -57
  132. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  133. data/lib/active_record/relation/spawn_methods.rb +2 -18
  134. data/lib/active_record/relation/where_clause.rb +7 -19
  135. data/lib/active_record/relation.rb +496 -72
  136. data/lib/active_record/result.rb +31 -44
  137. data/lib/active_record/runtime_registry.rb +39 -0
  138. data/lib/active_record/sanitization.rb +24 -19
  139. data/lib/active_record/schema.rb +8 -6
  140. data/lib/active_record/schema_dumper.rb +19 -9
  141. data/lib/active_record/schema_migration.rb +30 -14
  142. data/lib/active_record/signed_id.rb +11 -1
  143. data/lib/active_record/statement_cache.rb +7 -7
  144. data/lib/active_record/table_metadata.rb +1 -10
  145. data/lib/active_record/tasks/database_tasks.rb +76 -70
  146. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  147. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  148. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
  149. data/lib/active_record/test_fixtures.rb +81 -91
  150. data/lib/active_record/testing/query_assertions.rb +121 -0
  151. data/lib/active_record/timestamp.rb +1 -1
  152. data/lib/active_record/token_for.rb +22 -12
  153. data/lib/active_record/touch_later.rb +1 -1
  154. data/lib/active_record/transaction.rb +68 -0
  155. data/lib/active_record/transactions.rb +43 -14
  156. data/lib/active_record/translation.rb +0 -2
  157. data/lib/active_record/type/serialized.rb +1 -3
  158. data/lib/active_record/type_caster/connection.rb +4 -4
  159. data/lib/active_record/validations/associated.rb +9 -3
  160. data/lib/active_record/validations/uniqueness.rb +14 -10
  161. data/lib/active_record/validations.rb +4 -1
  162. data/lib/active_record.rb +149 -40
  163. data/lib/arel/alias_predication.rb +1 -1
  164. data/lib/arel/collectors/bind.rb +2 -0
  165. data/lib/arel/collectors/composite.rb +7 -0
  166. data/lib/arel/collectors/sql_string.rb +1 -1
  167. data/lib/arel/collectors/substitute_binds.rb +1 -1
  168. data/lib/arel/nodes/binary.rb +0 -6
  169. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  170. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  171. data/lib/arel/nodes/node.rb +4 -3
  172. data/lib/arel/nodes/sql_literal.rb +7 -0
  173. data/lib/arel/nodes.rb +2 -2
  174. data/lib/arel/predications.rb +1 -1
  175. data/lib/arel/select_manager.rb +1 -1
  176. data/lib/arel/tree_manager.rb +3 -2
  177. data/lib/arel/update_manager.rb +2 -1
  178. data/lib/arel/visitors/dot.rb +1 -0
  179. data/lib/arel/visitors/mysql.rb +9 -4
  180. data/lib/arel/visitors/postgresql.rb +1 -12
  181. data/lib/arel/visitors/to_sql.rb +29 -16
  182. data/lib/arel.rb +7 -3
  183. metadata +20 -15
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Assertions
5
+ module QueryAssertions
6
+ # Asserts that the number of SQL queries executed in the given block matches the expected count.
7
+ #
8
+ # # Check for exact number of queries
9
+ # assert_queries_count(1) { Post.first }
10
+ #
11
+ # # Check for any number of queries
12
+ # assert_queries_count { Post.first }
13
+ #
14
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
15
+ #
16
+ # assert_queries_count(1, include_schema: true) { Post.columns }
17
+ #
18
+ def assert_queries_count(count = nil, include_schema: false, &block)
19
+ ActiveRecord::Base.lease_connection.materialize_transactions
20
+
21
+ counter = SQLCounter.new
22
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
23
+ result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
24
+ queries = include_schema ? counter.log_all : counter.log
25
+ if count
26
+ assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
27
+ else
28
+ assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
29
+ end
30
+ result
31
+ end
32
+ end
33
+
34
+ # Asserts that no SQL queries are executed in the given block.
35
+ #
36
+ # assert_no_queries { post.comments }
37
+ #
38
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
39
+ #
40
+ # assert_no_queries(include_schema: true) { Post.columns }
41
+ #
42
+ def assert_no_queries(include_schema: false, &block)
43
+ assert_queries_count(0, include_schema: include_schema, &block)
44
+ end
45
+
46
+ # Asserts that the SQL queries executed in the given block match expected pattern.
47
+ #
48
+ # # Check for exact number of queries
49
+ # assert_queries_match(/LIMIT \?/, count: 1) { Post.first }
50
+ #
51
+ # # Check for any number of queries
52
+ # assert_queries_match(/LIMIT \?/) { Post.first }
53
+ #
54
+ # If the +:include_schema+ option is provided, any queries (including schema related)
55
+ # that match the matcher are considered.
56
+ #
57
+ # assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
58
+ #
59
+ def assert_queries_match(match, count: nil, include_schema: false, &block)
60
+ ActiveRecord::Base.lease_connection.materialize_transactions
61
+
62
+ counter = SQLCounter.new
63
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
64
+ result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
65
+ queries = include_schema ? counter.log_all : counter.log
66
+ matched_queries = queries.select { |query| match === query }
67
+
68
+ if count
69
+ assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
70
+ else
71
+ assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
72
+ end
73
+
74
+ result
75
+ end
76
+ end
77
+
78
+ # Asserts that no SQL queries matching the pattern are executed in the given block.
79
+ #
80
+ # assert_no_queries_match(/SELECT/i) { post.comments }
81
+ #
82
+ # If the +:include_schema+ option is provided, any queries (including schema related)
83
+ # that match the matcher are counted.
84
+ #
85
+ # assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
86
+ #
87
+ def assert_no_queries_match(match, include_schema: false, &block)
88
+ assert_queries_match(match, count: 0, include_schema: include_schema, &block)
89
+ end
90
+
91
+ class SQLCounter # :nodoc:
92
+ attr_reader :log_full, :log_all
93
+
94
+ def initialize
95
+ @log_full = []
96
+ @log_all = []
97
+ end
98
+
99
+ def log
100
+ @log_full.map(&:first)
101
+ end
102
+
103
+ def call(*, payload)
104
+ return if payload[:cached]
105
+
106
+ sql = payload[:sql]
107
+ @log_all << sql
108
+
109
+ unless payload[:name] == "SCHEMA"
110
+ bound_values = (payload[:binds] || []).map do |value|
111
+ value = value.value_for_database if value.respond_to?(:value_for_database)
112
+ value
113
+ end
114
+
115
+ @log_full << [sql, bound_values]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -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
@@ -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,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class Transaction
5
+ class Callback # :nodoc:
6
+ def initialize(event, callback)
7
+ @event = event
8
+ @callback = callback
9
+ end
10
+
11
+ def before_commit
12
+ @callback.call if @event == :before_commit
13
+ end
14
+
15
+ def after_commit
16
+ @callback.call if @event == :after_commit
17
+ end
18
+
19
+ def after_rollback
20
+ @callback.call if @event == :after_rollback
21
+ end
22
+ end
23
+
24
+ def initialize # :nodoc:
25
+ @callbacks = nil
26
+ end
27
+
28
+ # Registers a block to be called before the current transaction is fully committed.
29
+ #
30
+ # If there is no currently open transactions, the block is called immediately.
31
+ #
32
+ # If the current transaction has a parent transaction, the callback is transferred to
33
+ # the parent when the current transaction commits, or dropped when the current transaction
34
+ # is rolled back. This operation is repeated until the outermost transaction is reached.
35
+ def before_commit(&block)
36
+ (@callbacks ||= []) << Callback.new(:before_commit, block)
37
+ end
38
+
39
+ # Registers a block to be called after the current transaction is fully committed.
40
+ #
41
+ # If there is no currently open transactions, the block is called immediately.
42
+ #
43
+ # If the current transaction has a parent transaction, the callback is transferred to
44
+ # the parent when the current transaction commits, or dropped when the current transaction
45
+ # is rolled back. This operation is repeated until the outermost transaction is reached.
46
+ def after_commit(&block)
47
+ (@callbacks ||= []) << Callback.new(:after_commit, block)
48
+ end
49
+
50
+ # Registers a block to be called after the current transaction is rolled back.
51
+ #
52
+ # If there is no currently open transactions, the block is never called.
53
+ #
54
+ # If the current transaction is successfully committed but has a parent
55
+ # transaction, the callback is automatically added to the parent transaction.
56
+ #
57
+ # If the entire chain of nested transactions are all successfully committed,
58
+ # the block is never called.
59
+ def after_rollback(&block)
60
+ (@callbacks ||= []) << Callback.new(:after_rollback, block)
61
+ end
62
+
63
+ protected
64
+ def append_callbacks(callbacks)
65
+ (@callbacks ||= []).concat(callbacks)
66
+ end
67
+ end
68
+ end
@@ -198,9 +198,9 @@ module ActiveRecord
198
198
  # database error will occur because the savepoint has already been
199
199
  # automatically released. The following example demonstrates the problem:
200
200
  #
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
201
+ # Model.lease_connection.transaction do # BEGIN
202
+ # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
203
+ # Model.lease_connection.create_table(...) # active_record_1 now automatically released
204
204
  # end # RELEASE SAVEPOINT active_record_1
205
205
  # # ^^^^ BOOM! database error!
206
206
  # end
@@ -209,7 +209,14 @@ module ActiveRecord
209
209
  module ClassMethods
210
210
  # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
211
211
  def transaction(**options, &block)
212
- connection.transaction(**options, &block)
212
+ with_connection do |connection|
213
+ connection.transaction(**options, &block)
214
+ end
215
+ end
216
+
217
+ # Returns the current transaction. See ActiveRecord::Transactions API docs.
218
+ def current_transaction
219
+ connection_pool.active_connection&.current_transaction || ConnectionAdapters::TransactionManager::NULL_TRANSACTION
213
220
  end
214
221
 
215
222
  def before_commit(*args, &block) # :nodoc:
@@ -266,6 +273,25 @@ module ActiveRecord
266
273
  set_callback(:rollback, :after, *args, &block)
267
274
  end
268
275
 
276
+ # Similar to ActiveSupport::Callbacks::ClassMethods#set_callback, but with
277
+ # support for options available on #after_commit and #after_rollback callbacks.
278
+ def set_callback(name, *filter_list, &block)
279
+ options = filter_list.extract_options!
280
+ filter_list << options
281
+
282
+ if name.in?([:commit, :rollback]) && options[:on]
283
+ fire_on = Array(options[:on])
284
+ assert_valid_transaction_action(fire_on)
285
+ options[:if] = [
286
+ -> { transaction_include_any_action?(fire_on) },
287
+ *options[:if]
288
+ ]
289
+ end
290
+
291
+
292
+ super(name, *filter_list, &block)
293
+ end
294
+
269
295
  private
270
296
  def prepend_option
271
297
  if ActiveRecord.run_after_transaction_callbacks_in_order_defined
@@ -354,18 +380,19 @@ module ActiveRecord
354
380
  # This method is available within the context of an ActiveRecord::Base
355
381
  # instance.
356
382
  def with_transaction_returning_status
357
- status = nil
358
- connection = self.class.connection
359
- ensure_finalize = !connection.transaction_open?
383
+ self.class.with_connection do |connection|
384
+ status = nil
385
+ ensure_finalize = !connection.transaction_open?
360
386
 
361
- connection.transaction do
362
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
363
- remember_transaction_record_state
387
+ connection.transaction do
388
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
389
+ remember_transaction_record_state
364
390
 
365
- status = yield
366
- raise ActiveRecord::Rollback unless status
391
+ status = yield
392
+ raise ActiveRecord::Rollback unless status
393
+ end
394
+ status
367
395
  end
368
- status
369
396
  end
370
397
 
371
398
  def trigger_transactional_callbacks? # :nodoc:
@@ -457,7 +484,9 @@ module ActiveRecord
457
484
  # Add the record to the current transaction so that the #after_rollback and #after_commit
458
485
  # callbacks can be called.
459
486
  def add_to_transaction(ensure_finalize = true)
460
- self.class.connection.add_transaction_record(self, ensure_finalize)
487
+ self.class.with_connection do |connection|
488
+ connection.add_transaction_record(self, ensure_finalize)
489
+ end
461
490
  end
462
491
 
463
492
  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
 
@@ -84,7 +84,7 @@ module ActiveRecord
84
84
  attributes = scope + [attr]
85
85
  attributes = resolve_attributes(record, attributes)
86
86
 
87
- klass.connection.schema_cache.indexes(klass.table_name).any? do |index|
87
+ klass.schema_cache.indexes(klass.table_name).any? do |index|
88
88
  index.unique &&
89
89
  index.where.nil? &&
90
90
  (Array(index.columns) - attributes).empty?
@@ -110,16 +110,20 @@ module ActiveRecord
110
110
 
111
111
  def build_relation(klass, attribute, value)
112
112
  relation = klass.unscoped
113
- comparison = relation.bind_attribute(attribute, value) do |attr, bind|
114
- return relation.none! if bind.unboundable?
113
+ # TODO: Add case-sensitive / case-insensitive operators to Arel
114
+ # to no longer need to checkout a connection here.
115
+ comparison = klass.with_connection do |connection|
116
+ relation.bind_attribute(attribute, value) do |attr, bind|
117
+ return relation.none! if bind.unboundable?
115
118
 
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)
119
+ if !options.key?(:case_sensitive) || bind.nil?
120
+ connection.default_uniqueness_comparison(attr, bind)
121
+ elsif options[:case_sensitive]
122
+ connection.case_sensitive_comparison(attr, bind)
123
+ else
124
+ # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
125
+ connection.case_insensitive_comparison(attr, bind)
126
+ end
123
127
  end
124
128
  end
125
129
 
@@ -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