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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +643 -2274
- 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 +15 -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 +7 -1
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +4 -4
- 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 +59 -292
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods.rb +54 -63
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +12 -29
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- 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 +270 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +189 -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 +15 -6
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -44
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +40 -10
- 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 +6 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- 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 +17 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- 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 +125 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- 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 +86 -38
- 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 +19 -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 +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +24 -4
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- 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 +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- 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 +2 -2
- 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 +4 -1
- 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 +32 -68
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +42 -57
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +40 -43
- data/lib/active_record/reflection.rb +98 -36
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- 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/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +224 -58
- 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/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -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 +81 -42
- 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 +86 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +148 -39
- 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/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +18 -12
@@ -77,7 +77,7 @@ module ActiveRecord
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def current_time_from_proper_timezone
|
80
|
-
|
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]
|
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
|
-
|
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,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.
|
202
|
-
# Model.
|
203
|
-
# Model.
|
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
|
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
|
-
|
358
|
-
|
359
|
-
|
410
|
+
self.class.with_connection do |connection|
|
411
|
+
status = nil
|
412
|
+
ensure_finalize = !connection.transaction_open?
|
360
413
|
|
361
|
-
|
362
|
-
|
363
|
-
|
414
|
+
connection.transaction do
|
415
|
+
add_to_transaction(ensure_finalize || has_transactional_callbacks?)
|
416
|
+
remember_transaction_record_state
|
364
417
|
|
365
|
-
|
366
|
-
|
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
|
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?
|
@@ -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
|
|
@@ -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.
|
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
|
-
|
114
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|