activerecord 7.0.4 → 7.1.5.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 +1971 -1243
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -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 +20 -4
- 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 +20 -14
- data/lib/active_record/associations/collection_proxy.rb +20 -10
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- 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/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- 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 +333 -222
- 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 +53 -35
- 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 +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -26
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- data/lib/active_record/base.rb +7 -2
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- 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 +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
- 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 +155 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
- 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 -14
- 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 +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- 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 +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -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 +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- 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 +365 -61
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
- 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 +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +33 -19
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +59 -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 +9 -11
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +105 -7
- data/lib/active_record/migration/compatibility.rb +163 -58
- 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/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +271 -114
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +195 -42
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- 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 +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +14 -9
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +189 -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 +232 -81
- 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 +10 -7
- 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 +408 -76
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +50 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- 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/signed_id.rb +7 -5
- data/lib/active_record/store.rb +9 -9
- 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 +152 -108
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- 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/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/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +130 -17
- 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 +1 -9
- 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/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- 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 +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -4,7 +4,7 @@ require "active_support/inflector"
|
|
4
4
|
require "active_support/core_ext/hash/indifferent_access"
|
5
5
|
|
6
6
|
module ActiveRecord
|
7
|
-
#
|
7
|
+
# = Single table inheritance
|
8
8
|
#
|
9
9
|
# Active Record allows inheritance by storing the name of the class in a column that by
|
10
10
|
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
|
@@ -32,8 +32,9 @@ module ActiveRecord
|
|
32
32
|
# be triggered. In that case, it'll work just like normal subclasses with no special magic
|
33
33
|
# for differentiating between them or reloading the right type with find.
|
34
34
|
#
|
35
|
-
# Note, all the attributes for all the cases are kept in the same table.
|
36
|
-
#
|
35
|
+
# Note, all the attributes for all the cases are kept in the same table.
|
36
|
+
# Read more:
|
37
|
+
# * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
37
38
|
#
|
38
39
|
module Inheritance
|
39
40
|
extend ActiveSupport::Concern
|
@@ -93,14 +94,24 @@ module ActiveRecord
|
|
93
94
|
:true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
|
94
95
|
end
|
95
96
|
|
96
|
-
# Returns the class
|
97
|
-
#
|
97
|
+
# Returns the first class in the inheritance hierarchy that descends from either an
|
98
|
+
# abstract class or from <tt>ActiveRecord::Base</tt>.
|
98
99
|
#
|
99
|
-
#
|
100
|
-
# through some arbitrarily deep hierarchy, B.base_class will return A.
|
100
|
+
# Consider the following behaviour:
|
101
101
|
#
|
102
|
-
#
|
103
|
-
#
|
102
|
+
# class ApplicationRecord < ActiveRecord::Base
|
103
|
+
# self.abstract_class = true
|
104
|
+
# end
|
105
|
+
# class Shape < ApplicationRecord
|
106
|
+
# self.abstract_class = true
|
107
|
+
# end
|
108
|
+
# Polygon = Class.new(Shape)
|
109
|
+
# Square = Class.new(Polygon)
|
110
|
+
#
|
111
|
+
# ApplicationRecord.base_class # => ApplicationRecord
|
112
|
+
# Shape.base_class # => Shape
|
113
|
+
# Polygon.base_class # => Polygon
|
114
|
+
# Square.base_class # => Polygon
|
104
115
|
attr_reader :base_class
|
105
116
|
|
106
117
|
# Returns whether the class is a base class.
|
@@ -116,7 +127,7 @@ module ActiveRecord
|
|
116
127
|
# true.
|
117
128
|
# +ApplicationRecord+, for example, is generated as an abstract class.
|
118
129
|
#
|
119
|
-
# Consider the following default
|
130
|
+
# Consider the following default behavior:
|
120
131
|
#
|
121
132
|
# Shape = Class.new(ActiveRecord::Base)
|
122
133
|
# Polygon = Class.new(Shape)
|
@@ -210,12 +221,6 @@ module ActiveRecord
|
|
210
221
|
end
|
211
222
|
end
|
212
223
|
|
213
|
-
def inherited(subclass)
|
214
|
-
subclass.set_base_class
|
215
|
-
subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
|
216
|
-
super
|
217
|
-
end
|
218
|
-
|
219
224
|
def dup # :nodoc:
|
220
225
|
# `initialize_dup` / `initialize_copy` don't work when defined
|
221
226
|
# in the `singleton_class`.
|
@@ -277,6 +282,15 @@ module ActiveRecord
|
|
277
282
|
end
|
278
283
|
|
279
284
|
private
|
285
|
+
def inherited(subclass)
|
286
|
+
super
|
287
|
+
subclass.set_base_class
|
288
|
+
subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
|
289
|
+
subclass.class_eval do
|
290
|
+
@finder_needs_type_condition = nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
280
294
|
# Called by +instantiate+ to decide which class to use for a new
|
281
295
|
# record instance. For single-table inheritance, we check the record
|
282
296
|
# for a +type+ column and return the corresponding class.
|
@@ -8,16 +8,20 @@ module ActiveRecord
|
|
8
8
|
attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
|
9
9
|
|
10
10
|
def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
11
|
-
|
12
|
-
|
13
|
-
@model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
|
11
|
+
@model, @connection, @inserts = model, model.connection, inserts.map(&:stringify_keys)
|
14
12
|
@on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
|
15
13
|
@record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
|
16
14
|
|
17
15
|
disallow_raw_sql!(on_duplicate)
|
18
16
|
disallow_raw_sql!(returning)
|
19
17
|
|
20
|
-
|
18
|
+
if @inserts.empty?
|
19
|
+
@keys = []
|
20
|
+
else
|
21
|
+
resolve_sti
|
22
|
+
resolve_attribute_aliases
|
23
|
+
@keys = @inserts.first.keys
|
24
|
+
end
|
21
25
|
|
22
26
|
if model.scope_attributes?
|
23
27
|
@scope_attributes = model.scope_attributes
|
@@ -28,13 +32,15 @@ module ActiveRecord
|
|
28
32
|
@returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
|
29
33
|
@returning = false if @returning == []
|
30
34
|
|
31
|
-
@unique_by = find_unique_index_for(unique_by)
|
32
|
-
@on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
|
35
|
+
@unique_by = find_unique_index_for(@unique_by)
|
33
36
|
|
37
|
+
configure_on_duplicate_update_logic
|
34
38
|
ensure_valid_options_for_connection!
|
35
39
|
end
|
36
40
|
|
37
41
|
def execute
|
42
|
+
return ActiveRecord::Result.empty if inserts.empty?
|
43
|
+
|
38
44
|
message = +"#{model} "
|
39
45
|
message << "Bulk " if inserts.many?
|
40
46
|
message << (on_duplicate == :update ? "Upsert" : "Insert")
|
@@ -76,7 +82,7 @@ module ActiveRecord
|
|
76
82
|
@record_timestamps
|
77
83
|
end
|
78
84
|
|
79
|
-
# TODO: Consider
|
85
|
+
# TODO: Consider renaming this method, as it only conditionally extends keys, not always
|
80
86
|
def keys_including_timestamps
|
81
87
|
@keys_including_timestamps ||= if record_timestamps?
|
82
88
|
keys + model.all_timestamp_attributes_in_model
|
@@ -88,6 +94,34 @@ module ActiveRecord
|
|
88
94
|
private
|
89
95
|
attr_reader :scope_attributes
|
90
96
|
|
97
|
+
def has_attribute_aliases?(attributes)
|
98
|
+
attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def resolve_sti
|
102
|
+
return if model.descends_from_active_record?
|
103
|
+
|
104
|
+
sti_type = model.sti_name
|
105
|
+
@inserts = @inserts.map do |insert|
|
106
|
+
insert.reverse_merge(model.inheritance_column.to_s => sti_type)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolve_attribute_aliases
|
111
|
+
return unless has_attribute_aliases?(@inserts.first)
|
112
|
+
|
113
|
+
@inserts = @inserts.map do |insert|
|
114
|
+
insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
|
115
|
+
end
|
116
|
+
|
117
|
+
@update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
|
118
|
+
@unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
|
119
|
+
end
|
120
|
+
|
121
|
+
def resolve_attribute_alias(attribute)
|
122
|
+
model.attribute_alias(attribute) || attribute
|
123
|
+
end
|
124
|
+
|
91
125
|
def configure_on_duplicate_update_logic
|
92
126
|
if custom_update_sql_provided? && update_only.present?
|
93
127
|
raise ArgumentError, "You can't set :update_only and provide custom update SQL via :on_duplicate at the same time"
|
@@ -99,6 +133,8 @@ module ActiveRecord
|
|
99
133
|
elsif custom_update_sql_provided?
|
100
134
|
@update_sql = on_duplicate
|
101
135
|
@on_duplicate = :update
|
136
|
+
elsif @on_duplicate == :update && updatable_columns.empty?
|
137
|
+
@on_duplicate = :skip
|
102
138
|
end
|
103
139
|
end
|
104
140
|
|
@@ -115,8 +151,9 @@ module ActiveRecord
|
|
115
151
|
|
116
152
|
name_or_columns = unique_by || model.primary_key
|
117
153
|
match = Array(name_or_columns).map(&:to_s)
|
154
|
+
sorted_match = match.sort
|
118
155
|
|
119
|
-
if index = unique_indexes.find { |i| match.include?(i.name) || i.columns ==
|
156
|
+
if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
|
120
157
|
index
|
121
158
|
elsif match == primary_keys
|
122
159
|
unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
|
@@ -212,7 +249,13 @@ module ActiveRecord
|
|
212
249
|
if insert_all.returning.is_a?(String)
|
213
250
|
insert_all.returning
|
214
251
|
else
|
215
|
-
|
252
|
+
Array(insert_all.returning).map do |attribute|
|
253
|
+
if model.attribute_alias?(attribute)
|
254
|
+
"#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
|
255
|
+
else
|
256
|
+
quote_column(attribute)
|
257
|
+
end
|
258
|
+
end.join(",")
|
216
259
|
end
|
217
260
|
end
|
218
261
|
|
@@ -271,7 +314,11 @@ module ActiveRecord
|
|
271
314
|
end
|
272
315
|
|
273
316
|
def quote_columns(columns)
|
274
|
-
columns.map(
|
317
|
+
columns.map { |column| quote_column(column) }
|
318
|
+
end
|
319
|
+
|
320
|
+
def quote_column(column)
|
321
|
+
connection.quote_column_name(column)
|
275
322
|
end
|
276
323
|
end
|
277
324
|
end
|
@@ -10,7 +10,7 @@ module ActiveRecord
|
|
10
10
|
##
|
11
11
|
# :singleton-method:
|
12
12
|
# Indicates the format used to generate the timestamp in the cache key, if
|
13
|
-
# versioning is off. Accepts any of the symbols in
|
13
|
+
# versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
|
14
14
|
#
|
15
15
|
# This is +:usec+, by default.
|
16
16
|
class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
# Indicates whether to use a stable #cache_key method that is accompanied
|
21
21
|
# by a changing version in the #cache_version method.
|
22
22
|
#
|
23
|
-
# This is +true+, by default on Rails 5.2 and above.
|
23
|
+
# This is +true+, by default on \Rails 5.2 and above.
|
24
24
|
class_attribute :cache_versioning, instance_writer: false, default: false
|
25
25
|
|
26
26
|
##
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
# Indicates whether to use a stable #cache_key method that is accompanied
|
29
29
|
# by a changing version in the #cache_version method on collections.
|
30
30
|
#
|
31
|
-
# This is +false+, by default until Rails 6.1.
|
31
|
+
# This is +false+, by default until \Rails 6.1.
|
32
32
|
class_attribute :collection_cache_versioning, instance_writer: false, default: false
|
33
33
|
end
|
34
34
|
|
@@ -55,8 +55,8 @@ module ActiveRecord
|
|
55
55
|
# user = User.find_by(name: 'Phusion')
|
56
56
|
# user_path(user) # => "/users/Phusion"
|
57
57
|
def to_param
|
58
|
-
|
59
|
-
id
|
58
|
+
return unless id
|
59
|
+
Array(id).join(self.class.param_delimiter)
|
60
60
|
end
|
61
61
|
|
62
62
|
# Returns a stable cache key that can be used to identify this record.
|
@@ -64,7 +64,7 @@ module ActiveRecord
|
|
64
64
|
# Product.new.cache_key # => "products/new"
|
65
65
|
# Product.find(5).cache_key # => "products/5"
|
66
66
|
#
|
67
|
-
# If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
|
67
|
+
# If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
|
68
68
|
# the cache key will also include a version.
|
69
69
|
#
|
70
70
|
# Product.cache_versioning = false
|
@@ -106,7 +106,7 @@ module ActiveRecord
|
|
106
106
|
timestamp.utc.to_fs(cache_timestamp_format)
|
107
107
|
end
|
108
108
|
elsif self.class.has_attribute?("updated_at")
|
109
|
-
raise ActiveModel::MissingAttributeError, "missing attribute
|
109
|
+
raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
@@ -178,7 +178,7 @@ module ActiveRecord
|
|
178
178
|
def can_use_fast_cache_version?(timestamp)
|
179
179
|
timestamp.is_a?(String) &&
|
180
180
|
cache_timestamp_format == :usec &&
|
181
|
-
|
181
|
+
self.class.connection.default_timezone == :utc &&
|
182
182
|
!updated_at_came_from_user?
|
183
183
|
end
|
184
184
|
|
@@ -9,52 +9,142 @@ module ActiveRecord
|
|
9
9
|
#
|
10
10
|
# This is enabled by default. To disable this functionality set
|
11
11
|
# `use_metadata_table` to false in your database configuration.
|
12
|
-
class InternalMetadata
|
13
|
-
|
12
|
+
class InternalMetadata # :nodoc:
|
13
|
+
class NullInternalMetadata # :nodoc:
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
def enabled?
|
17
|
-
ActiveRecord::Base.connection.use_metadata_table?
|
18
|
-
end
|
16
|
+
attr_reader :connection, :arel_table
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def initialize(connection)
|
19
|
+
@connection = connection
|
20
|
+
@arel_table = Arel::Table.new(table_name)
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def enabled?
|
24
|
+
connection.use_metadata_table?
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
def primary_key
|
28
|
+
"key"
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_key
|
32
|
+
"value"
|
33
|
+
end
|
34
|
+
|
35
|
+
def table_name
|
36
|
+
"#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
|
37
|
+
end
|
30
38
|
|
31
|
-
|
39
|
+
def []=(key, value)
|
40
|
+
return unless enabled?
|
41
|
+
|
42
|
+
update_or_create_entry(key, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](key)
|
46
|
+
return unless enabled?
|
47
|
+
|
48
|
+
if entry = select_entry(key)
|
49
|
+
entry[value_key]
|
32
50
|
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_all_entries
|
54
|
+
dm = Arel::DeleteManager.new(arel_table)
|
55
|
+
|
56
|
+
connection.delete(dm, "#{self.class} Destroy")
|
57
|
+
end
|
58
|
+
|
59
|
+
def count
|
60
|
+
sm = Arel::SelectManager.new(arel_table)
|
61
|
+
sm.project(*Arel::Nodes::Count.new([Arel.star]))
|
62
|
+
|
63
|
+
connection.select_values(sm, "#{self.class} Count").first
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_table_and_set_flags(environment, schema_sha1 = nil)
|
67
|
+
return unless enabled?
|
68
|
+
|
69
|
+
create_table
|
70
|
+
update_or_create_entry(:environment, environment)
|
71
|
+
update_or_create_entry(:schema_sha1, schema_sha1) if schema_sha1
|
72
|
+
end
|
33
73
|
|
34
|
-
|
35
|
-
|
74
|
+
# Creates an internal metadata table with columns +key+ and +value+
|
75
|
+
def create_table
|
76
|
+
return unless enabled?
|
36
77
|
|
37
|
-
|
78
|
+
unless connection.table_exists?(table_name)
|
79
|
+
connection.create_table(table_name, id: false) do |t|
|
80
|
+
t.string :key, **connection.internal_string_options_for_primary_key
|
81
|
+
t.string :value
|
82
|
+
t.timestamps
|
83
|
+
end
|
38
84
|
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def drop_table
|
88
|
+
return unless enabled?
|
89
|
+
|
90
|
+
connection.drop_table table_name, if_exists: true
|
91
|
+
end
|
92
|
+
|
93
|
+
def table_exists?
|
94
|
+
connection.schema_cache.data_source_exists?(table_name)
|
95
|
+
end
|
39
96
|
|
40
|
-
|
41
|
-
def
|
42
|
-
|
97
|
+
private
|
98
|
+
def update_or_create_entry(key, value)
|
99
|
+
entry = select_entry(key)
|
43
100
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
101
|
+
if entry
|
102
|
+
if entry[value_key] != value
|
103
|
+
update_entry(key, value)
|
104
|
+
else
|
105
|
+
entry[value_key]
|
49
106
|
end
|
107
|
+
else
|
108
|
+
create_entry(key, value)
|
50
109
|
end
|
51
110
|
end
|
52
111
|
|
53
|
-
def
|
54
|
-
|
112
|
+
def current_time
|
113
|
+
connection.default_timezone == :utc ? Time.now.utc : Time.now
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_entry(key, value)
|
117
|
+
im = Arel::InsertManager.new(arel_table)
|
118
|
+
im.insert [
|
119
|
+
[arel_table[primary_key], key],
|
120
|
+
[arel_table[value_key], value],
|
121
|
+
[arel_table[:created_at], current_time],
|
122
|
+
[arel_table[:updated_at], current_time]
|
123
|
+
]
|
55
124
|
|
56
|
-
connection.
|
125
|
+
connection.insert(im, "#{self.class} Create", primary_key, key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_entry(key, new_value)
|
129
|
+
um = Arel::UpdateManager.new(arel_table)
|
130
|
+
um.set [
|
131
|
+
[arel_table[value_key], new_value],
|
132
|
+
[arel_table[:updated_at], current_time]
|
133
|
+
]
|
134
|
+
|
135
|
+
um.where(arel_table[primary_key].eq(key))
|
136
|
+
|
137
|
+
connection.update(um, "#{self.class} Update")
|
138
|
+
end
|
139
|
+
|
140
|
+
def select_entry(key)
|
141
|
+
sm = Arel::SelectManager.new(arel_table)
|
142
|
+
sm.project(Arel::Nodes::SqlLiteral.new("*"))
|
143
|
+
sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
|
144
|
+
sm.order(arel_table[primary_key].asc)
|
145
|
+
sm.limit = 1
|
146
|
+
|
147
|
+
connection.select_all(sm, "#{self.class} Load").first
|
57
148
|
end
|
58
|
-
end
|
59
149
|
end
|
60
150
|
end
|
@@ -2,19 +2,19 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Locking
|
5
|
-
# == What is Optimistic Locking
|
5
|
+
# == What is \Optimistic \Locking
|
6
6
|
#
|
7
7
|
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
|
8
8
|
# conflicts with the data. It does this by checking whether another process has made changes to a record since
|
9
|
-
# it was opened, an
|
9
|
+
# it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
|
10
10
|
# and the update is ignored.
|
11
11
|
#
|
12
|
-
# Check out
|
12
|
+
# Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
|
13
13
|
#
|
14
14
|
# == Usage
|
15
15
|
#
|
16
16
|
# Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
|
17
|
-
# record increments the +lock_version+
|
17
|
+
# record increments the integer column +lock_version+ and the locking facilities ensure that records instantiated twice
|
18
18
|
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
|
19
19
|
#
|
20
20
|
# p1 = Person.find(1)
|
@@ -69,6 +69,11 @@ module ActiveRecord
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
+
def initialize_dup(other) # :nodoc:
|
73
|
+
super
|
74
|
+
_clear_locking_column if locking_enabled?
|
75
|
+
end
|
76
|
+
|
72
77
|
private
|
73
78
|
def _create_record(attribute_names = self.attribute_names)
|
74
79
|
if locking_enabled?
|
@@ -91,8 +96,7 @@ module ActiveRecord
|
|
91
96
|
locking_column = self.class.locking_column
|
92
97
|
lock_attribute_was = @attributes[locking_column]
|
93
98
|
|
94
|
-
update_constraints =
|
95
|
-
update_constraints[locking_column] = _lock_value_for_database(locking_column)
|
99
|
+
update_constraints = _query_constraints_hash
|
96
100
|
|
97
101
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
98
102
|
attribute_names << locking_column
|
@@ -118,16 +122,9 @@ module ActiveRecord
|
|
118
122
|
end
|
119
123
|
|
120
124
|
def destroy_row
|
121
|
-
|
122
|
-
|
123
|
-
locking_column = self.class.locking_column
|
125
|
+
affected_rows = super
|
124
126
|
|
125
|
-
|
126
|
-
delete_constraints[locking_column] = _lock_value_for_database(locking_column)
|
127
|
-
|
128
|
-
affected_rows = self.class._delete_record(delete_constraints)
|
129
|
-
|
130
|
-
if affected_rows != 1
|
127
|
+
if locking_enabled? && affected_rows != 1
|
131
128
|
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
132
129
|
end
|
133
130
|
|
@@ -142,6 +139,18 @@ module ActiveRecord
|
|
142
139
|
end
|
143
140
|
end
|
144
141
|
|
142
|
+
def _clear_locking_column
|
143
|
+
self[self.class.locking_column] = nil
|
144
|
+
clear_attribute_change(self.class.locking_column)
|
145
|
+
end
|
146
|
+
|
147
|
+
def _query_constraints_hash
|
148
|
+
return super unless locking_enabled?
|
149
|
+
|
150
|
+
locking_column = self.class.locking_column
|
151
|
+
super.merge(locking_column => _lock_value_for_database(locking_column))
|
152
|
+
end
|
153
|
+
|
145
154
|
module ClassMethods
|
146
155
|
DEFAULT_LOCKING_COLUMN = "lock_version"
|
147
156
|
|
@@ -159,10 +168,7 @@ module ActiveRecord
|
|
159
168
|
end
|
160
169
|
|
161
170
|
# The version column used for optimistic locking. Defaults to +lock_version+.
|
162
|
-
|
163
|
-
@locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
|
164
|
-
@locking_column
|
165
|
-
end
|
171
|
+
attr_reader :locking_column
|
166
172
|
|
167
173
|
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
168
174
|
def reset_locking_column
|
@@ -182,6 +188,14 @@ module ActiveRecord
|
|
182
188
|
end
|
183
189
|
super
|
184
190
|
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def inherited(base)
|
194
|
+
super
|
195
|
+
base.class_eval do
|
196
|
+
@locking_column = DEFAULT_LOCKING_COLUMN
|
197
|
+
end
|
198
|
+
end
|
185
199
|
end
|
186
200
|
end
|
187
201
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Locking
|
5
|
+
# = \Pessimistic \Locking
|
6
|
+
#
|
5
7
|
# Locking::Pessimistic provides support for row-level locking using
|
6
8
|
# SELECT ... FOR UPDATE and other lock types.
|
7
9
|
#
|
@@ -71,6 +73,7 @@ module ActiveRecord
|
|
71
73
|
Locking a record with unpersisted changes is not supported. Use
|
72
74
|
`save` to persist the changes, or `reload` to discard them
|
73
75
|
explicitly.
|
76
|
+
Changed attributes: #{changed.map(&:inspect).join(', ')}.
|
74
77
|
MSG
|
75
78
|
end
|
76
79
|
|
@@ -79,8 +82,8 @@ module ActiveRecord
|
|
79
82
|
self
|
80
83
|
end
|
81
84
|
|
82
|
-
# Wraps the passed block in a transaction,
|
83
|
-
# before yielding. You can pass the SQL locking clause
|
85
|
+
# Wraps the passed block in a transaction, reloading the object with a
|
86
|
+
# lock before yielding. You can pass the SQL locking clause
|
84
87
|
# as an optional argument (see #lock!).
|
85
88
|
#
|
86
89
|
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
@@ -7,16 +7,24 @@ module ActiveRecord
|
|
7
7
|
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
8
8
|
|
9
9
|
def self.runtime=(value)
|
10
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
11
|
+
ActiveRecord::LogSubscriber.runtime= is deprecated and will be removed in Rails 7.2.
|
12
|
+
MSG
|
10
13
|
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
11
14
|
end
|
12
15
|
|
13
16
|
def self.runtime
|
14
|
-
ActiveRecord
|
17
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
18
|
+
ActiveRecord::LogSubscriber.runtime is deprecated and will be removed in Rails 7.2.
|
19
|
+
MSG
|
20
|
+
ActiveRecord::RuntimeRegistry.sql_runtime
|
15
21
|
end
|
16
22
|
|
17
23
|
def self.reset_runtime
|
18
|
-
|
19
|
-
|
24
|
+
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
25
|
+
ActiveRecord::LogSubscriber.reset_runtime is deprecated and will be removed in Rails 7.2.
|
26
|
+
MSG
|
27
|
+
ActiveRecord::RuntimeRegistry.reset
|
20
28
|
end
|
21
29
|
|
22
30
|
def strict_loading_violation(event)
|
@@ -26,11 +34,9 @@ module ActiveRecord
|
|
26
34
|
color(reflection.strict_loading_violation_message(owner), RED)
|
27
35
|
end
|
28
36
|
end
|
37
|
+
subscribe_log_level :strict_loading_violation, :debug
|
29
38
|
|
30
39
|
def sql(event)
|
31
|
-
self.class.runtime += event.duration
|
32
|
-
return unless logger.debug?
|
33
|
-
|
34
40
|
payload = event.payload
|
35
41
|
|
36
42
|
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
@@ -66,10 +72,11 @@ module ActiveRecord
|
|
66
72
|
end
|
67
73
|
|
68
74
|
name = colorize_payload_name(name, payload[:name])
|
69
|
-
sql = color(sql, sql_color(sql), true) if colorize_logging
|
75
|
+
sql = color(sql, sql_color(sql), bold: true) if colorize_logging
|
70
76
|
|
71
77
|
debug " #{name} #{sql}#{binds}"
|
72
78
|
end
|
79
|
+
subscribe_log_level :sql, :debug
|
73
80
|
|
74
81
|
private
|
75
82
|
def type_casted_binds(casted_binds)
|
@@ -93,9 +100,9 @@ module ActiveRecord
|
|
93
100
|
|
94
101
|
def colorize_payload_name(name, payload_name)
|
95
102
|
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
96
|
-
color(name, MAGENTA, true)
|
103
|
+
color(name, MAGENTA, bold: true)
|
97
104
|
else
|
98
|
-
color(name, CYAN, true)
|
105
|
+
color(name, CYAN, bold: true)
|
99
106
|
end
|
100
107
|
end
|
101
108
|
|
@@ -133,15 +140,25 @@ module ActiveRecord
|
|
133
140
|
end
|
134
141
|
|
135
142
|
def log_query_source
|
136
|
-
source =
|
143
|
+
source = query_source_location
|
137
144
|
|
138
145
|
if source
|
139
146
|
logger.debug(" ↳ #{source}")
|
140
147
|
end
|
141
148
|
end
|
142
149
|
|
143
|
-
|
144
|
-
|
150
|
+
if Thread.respond_to?(:each_caller_location)
|
151
|
+
def query_source_location
|
152
|
+
Thread.each_caller_location do |location|
|
153
|
+
frame = backtrace_cleaner.clean_frame(location)
|
154
|
+
return frame if frame
|
155
|
+
end
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
else
|
159
|
+
def query_source_location
|
160
|
+
backtrace_cleaner.clean(caller(1).lazy).first
|
161
|
+
end
|
145
162
|
end
|
146
163
|
|
147
164
|
def filter(name, value)
|