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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -2445
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +76 -70
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- 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
|
@@ -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
|
-
|
89
|
-
|
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
|
-
|
96
|
-
|
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
|
|
@@ -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.
|
202
|
-
# Model.
|
203
|
-
# Model.
|
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
|
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
|
-
|
358
|
-
|
359
|
-
|
383
|
+
self.class.with_connection do |connection|
|
384
|
+
status = nil
|
385
|
+
ensure_finalize = !connection.transaction_open?
|
360
386
|
|
361
|
-
|
362
|
-
|
363
|
-
|
387
|
+
connection.transaction do
|
388
|
+
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
389
|
+
remember_transaction_record_state
|
364
390
|
|
365
|
-
|
366
|
-
|
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
|
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?
|
@@ -14,18 +14,18 @@ module ActiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def type_for_attribute(attr_name)
|
17
|
-
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|