activerecord 7.0.0 → 7.1.0
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 +1607 -1040
- data/MIT-LICENSE +1 -1
- data/README.rdoc +17 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +345 -219
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +136 -148
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +114 -27
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +104 -9
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration.rb +271 -117
- data/lib/active_record/model_schema.rb +82 -50
- data/lib/active_record/nested_attributes.rb +23 -3
- data/lib/active_record/normalization.rb +159 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +127 -61
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +142 -143
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +177 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +200 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +429 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +50 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/json"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module TokenFor
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :token_definitions, instance_accessor: false, instance_predicate: false, default: {}
|
11
|
+
class_attribute :generated_token_verifier, instance_accessor: false, instance_predicate: false
|
12
|
+
end
|
13
|
+
|
14
|
+
TokenDefinition = Struct.new(:defining_class, :purpose, :expires_in, :block) do # :nodoc:
|
15
|
+
def full_purpose
|
16
|
+
@full_purpose ||= [defining_class.name, purpose, expires_in].join("\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def message_verifier
|
20
|
+
defining_class.generated_token_verifier
|
21
|
+
end
|
22
|
+
|
23
|
+
def payload_for(model)
|
24
|
+
block ? [model.id, model.instance_eval(&block).as_json] : [model.id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_token(model)
|
28
|
+
message_verifier.generate(payload_for(model), expires_in: expires_in, purpose: full_purpose)
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve_token(token)
|
32
|
+
payload = message_verifier.verified(token, purpose: full_purpose)
|
33
|
+
model = yield(payload[0]) if payload
|
34
|
+
model if model && payload_for(model) == payload
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
# Defines the behavior of tokens generated for a specific +purpose+.
|
40
|
+
# A token can be generated by calling TokenFor#generate_token_for on a
|
41
|
+
# record. Later, that record can be fetched by calling #find_by_token_for
|
42
|
+
# (or #find_by_token_for!) with the same purpose and token.
|
43
|
+
#
|
44
|
+
# Tokens are signed so that they are tamper-proof. Thus they can be
|
45
|
+
# exposed to outside world as, for example, password reset tokens.
|
46
|
+
#
|
47
|
+
# By default, tokens do not expire. They can be configured to expire by
|
48
|
+
# specifying a duration via the +expires_in+ option. The duration becomes
|
49
|
+
# part of the token's signature, so changing the value of +expires_in+
|
50
|
+
# will automatically invalidate previously generated tokens.
|
51
|
+
#
|
52
|
+
# A block may also be specified. When generating a token with
|
53
|
+
# TokenFor#generate_token_for, the block will be evaluated in the context
|
54
|
+
# of the record, and its return value will be embedded in the token as
|
55
|
+
# JSON. Later, when fetching the record with #find_by_token_for, the block
|
56
|
+
# will be evaluated again in the context of the fetched record. If the two
|
57
|
+
# JSON values do not match, the token will be treated as invalid. Note
|
58
|
+
# that the value returned by the block <b>should not contain sensitive
|
59
|
+
# information</b> because it will be embedded in the token as
|
60
|
+
# <b>human-readable plaintext JSON</b>.
|
61
|
+
#
|
62
|
+
# ==== Examples
|
63
|
+
#
|
64
|
+
# class User < ActiveRecord::Base
|
65
|
+
# has_secure_password
|
66
|
+
#
|
67
|
+
# generates_token_for :password_reset, expires_in: 15.minutes do
|
68
|
+
# # Last 10 characters of password salt, which changes when password is updated:
|
69
|
+
# password_salt&.last(10)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# user = User.first
|
74
|
+
#
|
75
|
+
# token = user.generate_token_for(:password_reset)
|
76
|
+
# User.find_by_token_for(:password_reset, token) # => user
|
77
|
+
# # 16 minutes later...
|
78
|
+
# User.find_by_token_for(:password_reset, token) # => nil
|
79
|
+
#
|
80
|
+
# token = user.generate_token_for(:password_reset)
|
81
|
+
# User.find_by_token_for(:password_reset, token) # => user
|
82
|
+
# user.update!(password: "new password")
|
83
|
+
# User.find_by_token_for(:password_reset, token) # => nil
|
84
|
+
def generates_token_for(purpose, expires_in: nil, &block)
|
85
|
+
self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
|
86
|
+
end
|
87
|
+
|
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) }
|
93
|
+
end
|
94
|
+
|
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)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Generates a token for a predefined +purpose+.
|
106
|
+
#
|
107
|
+
# Use ClassMethods#generates_token_for to define a token purpose and
|
108
|
+
# behavior.
|
109
|
+
def generate_token_for(purpose)
|
110
|
+
self.class.token_definitions.fetch(purpose).generate_token(self)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -24,9 +24,13 @@ module ActiveRecord
|
|
24
24
|
@_new_record_before_last_commit ||= false
|
25
25
|
|
26
26
|
# touch the parents as we are not calling the after_save callbacks
|
27
|
-
self.class.reflect_on_all_associations
|
27
|
+
self.class.reflect_on_all_associations.each do |r|
|
28
28
|
if touch = r.options[:touch]
|
29
|
-
|
29
|
+
if r.macro == :belongs_to
|
30
|
+
ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch)
|
31
|
+
elsif r.macro == :has_one
|
32
|
+
ActiveRecord::Associations::Builder::HasOne.touch_record(self, r.name, touch)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -42,6 +46,11 @@ module ActiveRecord
|
|
42
46
|
end
|
43
47
|
|
44
48
|
private
|
49
|
+
def init_internals
|
50
|
+
super
|
51
|
+
@_defer_touch_attrs = nil
|
52
|
+
end
|
53
|
+
|
45
54
|
def surreptitiously_touch(attr_names)
|
46
55
|
attr_names.each do |attr_name|
|
47
56
|
_write_attribute(attr_name, @_touch_time)
|
@@ -57,9 +66,5 @@ module ActiveRecord
|
|
57
66
|
def has_defer_touch_attrs?
|
58
67
|
defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
|
59
68
|
end
|
60
|
-
|
61
|
-
def belongs_to_touch_method
|
62
|
-
:touch_later
|
63
|
-
end
|
64
69
|
end
|
65
70
|
end
|
@@ -13,7 +13,9 @@ module ActiveRecord
|
|
13
13
|
scope: [:kind, :name]
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
attr_accessor :_new_record_before_last_commit # :nodoc:
|
17
|
+
|
18
|
+
# = Active Record \Transactions
|
17
19
|
#
|
18
20
|
# \Transactions are protective blocks where SQL statements are only permanent
|
19
21
|
# if they can all succeed as one atomic action. The classic example is a
|
@@ -98,7 +100,8 @@ module ActiveRecord
|
|
98
100
|
# catch those in your application code.
|
99
101
|
#
|
100
102
|
# One exception is the ActiveRecord::Rollback exception, which will trigger
|
101
|
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
|
103
|
+
# a ROLLBACK when raised, but not be re-raised by the transaction block. Any
|
104
|
+
# other exception will be re-raised.
|
102
105
|
#
|
103
106
|
# *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
|
104
107
|
# inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
|
@@ -227,31 +230,31 @@ module ActiveRecord
|
|
227
230
|
# after_commit :do_bar_baz, on: [:update, :destroy]
|
228
231
|
#
|
229
232
|
def after_commit(*args, &block)
|
230
|
-
set_options_for_callbacks!(args)
|
233
|
+
set_options_for_callbacks!(args, prepend_option)
|
231
234
|
set_callback(:commit, :after, *args, &block)
|
232
235
|
end
|
233
236
|
|
234
237
|
# Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
|
235
238
|
def after_save_commit(*args, &block)
|
236
|
-
set_options_for_callbacks!(args, on: [ :create, :update ])
|
239
|
+
set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option)
|
237
240
|
set_callback(:commit, :after, *args, &block)
|
238
241
|
end
|
239
242
|
|
240
243
|
# Shortcut for <tt>after_commit :hook, on: :create</tt>.
|
241
244
|
def after_create_commit(*args, &block)
|
242
|
-
set_options_for_callbacks!(args, on: :create)
|
245
|
+
set_options_for_callbacks!(args, on: :create, **prepend_option)
|
243
246
|
set_callback(:commit, :after, *args, &block)
|
244
247
|
end
|
245
248
|
|
246
249
|
# Shortcut for <tt>after_commit :hook, on: :update</tt>.
|
247
250
|
def after_update_commit(*args, &block)
|
248
|
-
set_options_for_callbacks!(args, on: :update)
|
251
|
+
set_options_for_callbacks!(args, on: :update, **prepend_option)
|
249
252
|
set_callback(:commit, :after, *args, &block)
|
250
253
|
end
|
251
254
|
|
252
255
|
# Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
|
253
256
|
def after_destroy_commit(*args, &block)
|
254
|
-
set_options_for_callbacks!(args, on: :destroy)
|
257
|
+
set_options_for_callbacks!(args, on: :destroy, **prepend_option)
|
255
258
|
set_callback(:commit, :after, *args, &block)
|
256
259
|
end
|
257
260
|
|
@@ -259,11 +262,19 @@ module ActiveRecord
|
|
259
262
|
#
|
260
263
|
# Please check the documentation of #after_commit for options.
|
261
264
|
def after_rollback(*args, &block)
|
262
|
-
set_options_for_callbacks!(args)
|
265
|
+
set_options_for_callbacks!(args, prepend_option)
|
263
266
|
set_callback(:rollback, :after, *args, &block)
|
264
267
|
end
|
265
268
|
|
266
269
|
private
|
270
|
+
def prepend_option
|
271
|
+
if ActiveRecord.run_after_transaction_callbacks_in_order_defined
|
272
|
+
{ prepend: true }
|
273
|
+
else
|
274
|
+
{}
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
267
278
|
def set_options_for_callbacks!(args, enforced_options = {})
|
268
279
|
options = args.extract_options!.merge!(enforced_options)
|
269
280
|
args << options
|
@@ -336,9 +347,9 @@ module ActiveRecord
|
|
336
347
|
@_trigger_update_callback = @_trigger_destroy_callback = false if force_restore_state
|
337
348
|
end
|
338
349
|
|
339
|
-
# Executes
|
340
|
-
# status flag. If the status is true the transaction is committed,
|
341
|
-
# a ROLLBACK is issued. In any case the status flag is returned.
|
350
|
+
# Executes a block within a transaction and captures its return value as a
|
351
|
+
# status flag. If the status is true, the transaction is committed,
|
352
|
+
# otherwise a ROLLBACK is issued. In any case, the status flag is returned.
|
342
353
|
#
|
343
354
|
# This method is available within the context of an ActiveRecord::Base
|
344
355
|
# instance.
|
@@ -365,6 +376,13 @@ module ActiveRecord
|
|
365
376
|
private
|
366
377
|
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
|
367
378
|
|
379
|
+
def init_internals
|
380
|
+
super
|
381
|
+
@_start_transaction_state = nil
|
382
|
+
@_committed_already_called = nil
|
383
|
+
@_new_record_before_last_commit = nil
|
384
|
+
end
|
385
|
+
|
368
386
|
# Save the new record state and id of a record so it can be restored later if a transaction fails.
|
369
387
|
def remember_transaction_record_state
|
370
388
|
@_start_transaction_state ||= {
|
@@ -406,8 +424,16 @@ module ActiveRecord
|
|
406
424
|
end
|
407
425
|
@mutations_from_database = nil
|
408
426
|
@mutations_before_last_save = nil
|
409
|
-
if
|
410
|
-
|
427
|
+
if self.class.composite_primary_key?
|
428
|
+
if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) }
|
429
|
+
@primary_key.zip(restore_state[:id]).each do |col, val|
|
430
|
+
@attributes.write_from_user(col, val)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
else
|
434
|
+
if @attributes.fetch_value(@primary_key) != restore_state[:id]
|
435
|
+
@attributes.write_from_user(@primary_key, restore_state[:id])
|
436
|
+
end
|
411
437
|
end
|
412
438
|
freeze if restore_state[:frozen?]
|
413
439
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_model/type/registry"
|
4
|
-
|
5
3
|
module ActiveRecord
|
6
4
|
# :stopdoc:
|
7
5
|
module Type
|
@@ -12,7 +10,6 @@ module ActiveRecord
|
|
12
10
|
|
13
11
|
def initialize_copy(other)
|
14
12
|
@registrations = @registrations.dup
|
15
|
-
super
|
16
13
|
end
|
17
14
|
|
18
15
|
def add_modifier(options, klass, **args)
|
@@ -56,11 +53,7 @@ module ActiveRecord
|
|
56
53
|
end
|
57
54
|
|
58
55
|
def call(_registry, *args, adapter: nil, **kwargs)
|
59
|
-
|
60
|
-
block.call(*args, **kwargs)
|
61
|
-
else
|
62
|
-
block.call(*args)
|
63
|
-
end
|
56
|
+
block.call(*args, **kwargs)
|
64
57
|
end
|
65
58
|
|
66
59
|
def matches?(type_name, *args, **kwargs)
|
@@ -4,12 +4,17 @@ module ActiveRecord
|
|
4
4
|
module Type
|
5
5
|
module Internal
|
6
6
|
module Timezone
|
7
|
+
def initialize(timezone: nil, **kwargs)
|
8
|
+
super(**kwargs)
|
9
|
+
@timezone = timezone
|
10
|
+
end
|
11
|
+
|
7
12
|
def is_utc?
|
8
|
-
|
13
|
+
default_timezone == :utc
|
9
14
|
end
|
10
15
|
|
11
16
|
def default_timezone
|
12
|
-
ActiveRecord.default_timezone
|
17
|
+
@timezone || ActiveRecord.default_timezone
|
13
18
|
end
|
14
19
|
end
|
15
20
|
end
|
@@ -55,6 +55,10 @@ module ActiveRecord
|
|
55
55
|
coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
|
56
56
|
end
|
57
57
|
|
58
|
+
def serialized? # :nodoc:
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
58
62
|
private
|
59
63
|
def default_value?(value)
|
60
64
|
value == coder.load(nil)
|
@@ -63,11 +67,11 @@ module ActiveRecord
|
|
63
67
|
def encoded(value)
|
64
68
|
return if default_value?(value)
|
65
69
|
payload = coder.dump(value)
|
66
|
-
if payload && binary?
|
67
|
-
|
68
|
-
|
70
|
+
if payload && @subtype.binary?
|
71
|
+
ActiveModel::Type::Binary::Data.new(payload)
|
72
|
+
else
|
73
|
+
payload
|
69
74
|
end
|
70
|
-
payload
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
@@ -14,7 +14,7 @@ module ActiveRecord
|
|
14
14
|
module ClassMethods
|
15
15
|
# Validates that the specified attributes are not present (as defined by
|
16
16
|
# Object#present?). If the attribute is an association, the associated object
|
17
|
-
# is considered
|
17
|
+
# is also considered not present if it is marked for destruction.
|
18
18
|
#
|
19
19
|
# See ActiveModel::Validations::HelperMethods.validates_absence_of for more information.
|
20
20
|
def validates_absence_of(*attr_names)
|
@@ -42,14 +42,14 @@ module ActiveRecord
|
|
42
42
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
43
43
|
# <tt>on: :custom_validation_context</tt> or
|
44
44
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
45
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
45
|
+
# * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
|
46
46
|
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
47
47
|
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
48
48
|
# proc or string should return or evaluate to a +true+ or +false+ value.
|
49
|
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
49
|
+
# * <tt>:unless</tt> - Specifies a method, proc, or string to call to
|
50
50
|
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
51
51
|
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
52
|
-
# method, proc or string should return or evaluate to a +true+ or +false+
|
52
|
+
# method, proc, or string should return or evaluate to a +true+ or +false+
|
53
53
|
# value.
|
54
54
|
def validates_associated(*attr_names)
|
55
55
|
validates_with AssociatedValidator, _merge_attributes(attr_names)
|
@@ -21,10 +21,11 @@ module ActiveRecord
|
|
21
21
|
|
22
22
|
module ClassMethods
|
23
23
|
# Validates whether the value of the specified attribute is numeric by
|
24
|
-
# trying to convert it to a float with Kernel.Float (if
|
25
|
-
# is +false+) or applying it to the regular
|
26
|
-
# (if <tt>only_integer</tt> is set to
|
27
|
-
# defaults to the column's precision
|
24
|
+
# trying to convert it to a float with +Kernel.Float+ (if
|
25
|
+
# <tt>only_integer</tt> is +false+) or applying it to the regular
|
26
|
+
# expression <tt>/\A[\+\-]?\d+\z/</tt> (if <tt>only_integer</tt> is set to
|
27
|
+
# +true+). +Kernel.Float+ precision defaults to the column's precision
|
28
|
+
# value or 15.
|
28
29
|
#
|
29
30
|
# See ActiveModel::Validations::HelperMethods.validates_numericality_of for more information.
|
30
31
|
def validates_numericality_of(*attr_names)
|
@@ -13,9 +13,8 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
module ClassMethods
|
15
15
|
# Validates that the specified attributes are not blank (as defined by
|
16
|
-
# Object#blank?)
|
17
|
-
#
|
18
|
-
# on save.
|
16
|
+
# Object#blank?). If the attribute is an association, the associated object
|
17
|
+
# is also considered blank if it is marked for destruction.
|
19
18
|
#
|
20
19
|
# class Person < ActiveRecord::Base
|
21
20
|
# has_one :face
|
@@ -25,41 +24,19 @@ module ActiveRecord
|
|
25
24
|
# The face attribute must be in the object and it cannot be blank or marked
|
26
25
|
# for destruction.
|
27
26
|
#
|
28
|
-
# If you want to validate the presence of a boolean field (where the real values
|
29
|
-
# are true and false), you will want to use
|
30
|
-
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
|
31
|
-
#
|
32
|
-
# This is due to the way Object#blank? handles boolean values:
|
33
|
-
# <tt>false.blank? # => true</tt>.
|
34
|
-
#
|
35
27
|
# This validator defers to the Active Model validation for presence, adding the
|
36
28
|
# check to see that an associated object is not marked for destruction. This
|
37
29
|
# prevents the parent object from validating successfully and saving, which then
|
38
30
|
# deletes the associated object, thus putting the parent object into an invalid
|
39
31
|
# state.
|
40
32
|
#
|
33
|
+
# See ActiveModel::Validations::HelperMethods.validates_presence_of for
|
34
|
+
# more information.
|
35
|
+
#
|
41
36
|
# NOTE: This validation will not fail while using it with an association
|
42
37
|
# if the latter was assigned but not valid. If you want to ensure that
|
43
38
|
# it is both present and valid, you also need to use
|
44
39
|
# {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated].
|
45
|
-
#
|
46
|
-
# Configuration options:
|
47
|
-
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
|
48
|
-
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
|
49
|
-
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
50
|
-
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
51
|
-
# <tt>on: :custom_validation_context</tt> or
|
52
|
-
# <tt>on: [:create, :custom_validation_context]</tt>)
|
53
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
|
54
|
-
# the validation should occur (e.g. <tt>if: :allow_validation</tt>, or
|
55
|
-
# <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
|
56
|
-
# or string should return or evaluate to a +true+ or +false+ value.
|
57
|
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
|
58
|
-
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
59
|
-
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
|
60
|
-
# proc or string should return or evaluate to a +true+ or +false+ value.
|
61
|
-
# * <tt>:strict</tt> - Specifies whether validation should be strict.
|
62
|
-
# See ActiveModel::Validations#validates! for more information.
|
63
40
|
def validates_presence_of(*attr_names)
|
64
41
|
validates_with PresenceValidator, _merge_attributes(attr_names)
|
65
42
|
end
|
@@ -20,10 +20,12 @@ module ActiveRecord
|
|
20
20
|
finder_class = find_finder_class_for(record)
|
21
21
|
value = map_enum_attribute(finder_class, attribute, value)
|
22
22
|
|
23
|
+
return if record.persisted? && !validation_needed?(finder_class, record, attribute)
|
24
|
+
|
23
25
|
relation = build_relation(finder_class, attribute, value)
|
24
26
|
if record.persisted?
|
25
27
|
if finder_class.primary_key
|
26
|
-
relation = relation.where.not(finder_class.primary_key => record.id_in_database)
|
28
|
+
relation = relation.where.not(finder_class.primary_key => [record.id_in_database])
|
27
29
|
else
|
28
30
|
raise UnknownPrimaryKey.new(finder_class, "Cannot validate uniqueness for persisted record without primary key.")
|
29
31
|
end
|
@@ -64,6 +66,48 @@ module ActiveRecord
|
|
64
66
|
class_hierarchy.detect { |klass| !klass.abstract_class? }
|
65
67
|
end
|
66
68
|
|
69
|
+
def validation_needed?(klass, record, attribute)
|
70
|
+
return true if options[:conditions] || options.key?(:case_sensitive)
|
71
|
+
|
72
|
+
scope = Array(options[:scope])
|
73
|
+
attributes = scope + [attribute]
|
74
|
+
attributes = resolve_attributes(record, attributes)
|
75
|
+
|
76
|
+
return true if attributes.any? { |attr| record.attribute_changed?(attr) ||
|
77
|
+
record.read_attribute(attr).nil? }
|
78
|
+
|
79
|
+
!covered_by_unique_index?(klass, record, attribute, scope)
|
80
|
+
end
|
81
|
+
|
82
|
+
def covered_by_unique_index?(klass, record, attribute, scope)
|
83
|
+
@covered ||= self.attributes.map(&:to_s).select do |attr|
|
84
|
+
attributes = scope + [attr]
|
85
|
+
attributes = resolve_attributes(record, attributes)
|
86
|
+
|
87
|
+
klass.connection.schema_cache.indexes(klass.table_name).any? do |index|
|
88
|
+
index.unique &&
|
89
|
+
index.where.nil? &&
|
90
|
+
(Array(index.columns) - attributes).empty?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@covered.include?(attribute.to_s)
|
95
|
+
end
|
96
|
+
|
97
|
+
def resolve_attributes(record, attributes)
|
98
|
+
attributes.flat_map do |attribute|
|
99
|
+
reflection = record.class._reflect_on_association(attribute)
|
100
|
+
|
101
|
+
if reflection.nil?
|
102
|
+
attribute.to_s
|
103
|
+
elsif reflection.polymorphic?
|
104
|
+
[reflection.foreign_key, reflection.foreign_type]
|
105
|
+
else
|
106
|
+
reflection.foreign_key
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
67
111
|
def build_relation(klass, attribute, value)
|
68
112
|
relation = klass.unscoped
|
69
113
|
comparison = relation.bind_attribute(attribute, value) do |attr, bind|
|
@@ -166,14 +210,14 @@ module ActiveRecord
|
|
166
210
|
# attribute is +nil+ (default is +false+).
|
167
211
|
# * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
|
168
212
|
# attribute is blank (default is +false+).
|
169
|
-
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
213
|
+
# * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
|
170
214
|
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
171
215
|
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
172
216
|
# proc or string should return or evaluate to a +true+ or +false+ value.
|
173
|
-
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
|
217
|
+
# * <tt>:unless</tt> - Specifies a method, proc, or string to call to
|
174
218
|
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
175
219
|
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
176
|
-
# method, proc or string should return or evaluate to a +true+ or +false+
|
220
|
+
# method, proc, or string should return or evaluate to a +true+ or +false+
|
177
221
|
# value.
|
178
222
|
#
|
179
223
|
# === Concurrency and integrity
|
@@ -221,7 +265,7 @@ module ActiveRecord
|
|
221
265
|
# When the database catches such a duplicate insertion,
|
222
266
|
# {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid
|
223
267
|
# exception. You can either choose to let this error propagate (which
|
224
|
-
# will result in the default Rails exception page being shown), or you
|
268
|
+
# will result in the default \Rails exception page being shown), or you
|
225
269
|
# can catch it and restart the transaction (e.g. by telling the user
|
226
270
|
# that the title already exists, and asking them to re-enter the title).
|
227
271
|
# This technique is also known as
|
@@ -236,6 +280,7 @@ module ActiveRecord
|
|
236
280
|
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
|
237
281
|
#
|
238
282
|
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter.
|
283
|
+
# * ActiveRecord::ConnectionAdapters::TrilogyAdapter.
|
239
284
|
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter.
|
240
285
|
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.
|
241
286
|
def validates_uniqueness_of(*attr_names)
|
@@ -30,10 +30,12 @@ module ActiveRecord
|
|
30
30
|
|
31
31
|
# = Active Record \Validations
|
32
32
|
#
|
33
|
-
# Active Record includes the majority of its validations from ActiveModel::Validations
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
33
|
+
# Active Record includes the majority of its validations from ActiveModel::Validations.
|
34
|
+
#
|
35
|
+
# In Active Record, all validations are performed on save by default.
|
36
|
+
# Validations accept the <tt>:on</tt> argument to define the context where
|
37
|
+
# the validations are active. Active Record will pass either the context of
|
38
|
+
# <tt>:create</tt> or <tt>:update</tt> depending on whether the model is a
|
37
39
|
# {new_record?}[rdoc-ref:Persistence#new_record?].
|
38
40
|
module Validations
|
39
41
|
extend ActiveSupport::Concern
|
@@ -60,6 +62,8 @@ module ActiveRecord
|
|
60
62
|
#
|
61
63
|
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
|
62
64
|
# {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to <tt>:update</tt> if it is not.
|
65
|
+
# If the argument is an array of contexts, <tt>post.valid?([:create, :update])</tt>, the validations are
|
66
|
+
# run within multiple contexts.
|
63
67
|
#
|
64
68
|
# \Validations with no <tt>:on</tt> option will run no matter the context. \Validations with
|
65
69
|
# some <tt>:on</tt> option will only run in the specified context.
|