activerecord 7.0.0.alpha2 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +539 -11
- data/lib/active_record/associations/association.rb +2 -8
- data/lib/active_record/associations/builder/collection_association.rb +9 -2
- data/lib/active_record/associations/collection_association.rb +10 -2
- data/lib/active_record/associations/join_dependency.rb +6 -2
- data/lib/active_record/associations/preloader/association.rb +68 -48
- data/lib/active_record/associations/preloader/batch.rb +3 -6
- data/lib/active_record/associations/preloader/through_association.rb +19 -9
- data/lib/active_record/associations/preloader.rb +14 -24
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/associations.rb +16 -3
- data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
- data/lib/active_record/attribute_methods/dirty.rb +9 -1
- data/lib/active_record/attribute_methods.rb +7 -5
- data/lib/active_record/autosave_association.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
- data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/column.rb +4 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/pool_config.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
- data/lib/active_record/connection_handling.rb +31 -19
- data/lib/active_record/core.rb +13 -24
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +0 -9
- data/lib/active_record/database_configurations/hash_config.rb +40 -8
- data/lib/active_record/database_configurations.rb +2 -27
- data/lib/active_record/delegated_type.rb +19 -0
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
- data/lib/active_record/encryption/message_serializer.rb +11 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +8 -1
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/fixture_set/table_row.rb +1 -1
- data/lib/active_record/fixtures.rb +1 -9
- data/lib/active_record/future_result.rb +2 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +52 -15
- data/lib/active_record/integration.rb +3 -2
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/pessimistic.rb +9 -3
- data/lib/active_record/log_subscriber.rb +8 -1
- data/lib/active_record/middleware/shard_selector.rb +60 -0
- data/lib/active_record/migration.rb +2 -2
- data/lib/active_record/model_schema.rb +1 -28
- data/lib/active_record/nested_attributes.rb +11 -10
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +99 -21
- data/lib/active_record/query_logs.rb +18 -83
- data/lib/active_record/railtie.rb +11 -1
- data/lib/active_record/railties/databases.rake +4 -91
- data/lib/active_record/reflection.rb +22 -6
- data/lib/active_record/relation/calculations.rb +1 -10
- data/lib/active_record/relation/finder_methods.rb +0 -13
- data/lib/active_record/relation/query_methods.rb +5 -14
- data/lib/active_record/relation/record_fetch_warning.rb +5 -7
- data/lib/active_record/relation/where_clause.rb +2 -15
- data/lib/active_record/relation.rb +11 -15
- data/lib/active_record/result.rb +0 -5
- data/lib/active_record/runtime_registry.rb +10 -12
- data/lib/active_record/schema_dumper.rb +7 -0
- data/lib/active_record/schema_migration.rb +4 -0
- data/lib/active_record/scoping.rb +34 -22
- data/lib/active_record/suppressor.rb +11 -15
- data/lib/active_record/tasks/database_tasks.rb +18 -44
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record.rb +41 -33
- data/lib/arel/crud.rb +12 -2
- data/lib/arel/delete_manager.rb +16 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/nodes/delete_statement.rb +5 -1
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/update_statement.rb +5 -1
- data/lib/arel/nodes.rb +1 -0
- data/lib/arel/predications.rb +10 -2
- data/lib/arel/update_manager.rb +16 -0
- data/lib/arel/visitors/mysql.rb +2 -1
- data/lib/arel/visitors/to_sql.rb +15 -0
- data/lib/arel.rb +1 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +18 -12
@@ -2,50 +2,13 @@
|
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module LegacyYamlAdapter # :nodoc:
|
5
|
-
def self.convert(
|
5
|
+
def self.convert(coder)
|
6
6
|
return coder unless coder.is_a?(Psych::Coder)
|
7
7
|
|
8
8
|
case coder["active_record_yaml_version"]
|
9
9
|
when 1, 2 then coder
|
10
10
|
else
|
11
|
-
|
12
|
-
YAML loading from legacy format older than Rails 5.0 is deprecated
|
13
|
-
and will be removed in Rails 7.0.
|
14
|
-
MSG
|
15
|
-
if coder["attributes"].is_a?(ActiveModel::AttributeSet)
|
16
|
-
Rails420.convert(klass, coder)
|
17
|
-
else
|
18
|
-
Rails41.convert(klass, coder)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
module Rails420 # :nodoc:
|
24
|
-
def self.convert(klass, coder)
|
25
|
-
attribute_set = coder["attributes"]
|
26
|
-
|
27
|
-
klass.attribute_names.each do |attr_name|
|
28
|
-
attribute = attribute_set[attr_name]
|
29
|
-
if attribute.type.is_a?(Delegator)
|
30
|
-
type_from_klass = klass.type_for_attribute(attr_name)
|
31
|
-
attribute_set[attr_name] = attribute.with_type(type_from_klass)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
coder
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
module Rails41 # :nodoc:
|
40
|
-
def self.convert(klass, coder)
|
41
|
-
attributes = klass.attributes_builder
|
42
|
-
.build_from_database(coder["attributes"])
|
43
|
-
new_record = coder["attributes"][klass.primary_key].blank?
|
44
|
-
|
45
|
-
{
|
46
|
-
"attributes" => attributes,
|
47
|
-
"new_record" => new_record,
|
48
|
-
}
|
11
|
+
raise("Active Record doesn't know how to load YAML with this format.")
|
49
12
|
end
|
50
13
|
end
|
51
14
|
end
|
@@ -81,9 +81,15 @@ module ActiveRecord
|
|
81
81
|
|
82
82
|
# Wraps the passed block in a transaction, locking the object
|
83
83
|
# before yielding. You can pass the SQL locking clause
|
84
|
-
# as argument (see <tt
|
85
|
-
|
86
|
-
|
84
|
+
# as an optional argument (see <tt>#lock!</tt>).
|
85
|
+
#
|
86
|
+
# You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,
|
87
|
+
# and <tt>joinable:</tt> to the wrapping transaction (see
|
88
|
+
# <tt>ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction</tt>).
|
89
|
+
def with_lock(*args)
|
90
|
+
transaction_opts = args.extract_options!
|
91
|
+
lock = args.present? ? args.first : true
|
92
|
+
transaction(**transaction_opts) do
|
87
93
|
lock!(lock)
|
88
94
|
yield
|
89
95
|
end
|
@@ -51,7 +51,10 @@ module ActiveRecord
|
|
51
51
|
|
52
52
|
binds = []
|
53
53
|
payload[:binds].each_with_index do |attr, i|
|
54
|
-
|
54
|
+
attribute_name = attr.respond_to?(:name) ? attr.name : attr[i].name
|
55
|
+
filtered_params = filter(attribute_name, casted_params[i])
|
56
|
+
|
57
|
+
binds << render_bind(attr, filtered_params)
|
55
58
|
end
|
56
59
|
binds = binds.inspect
|
57
60
|
binds.prepend(" ")
|
@@ -135,6 +138,10 @@ module ActiveRecord
|
|
135
138
|
def extract_query_source_location(locations)
|
136
139
|
backtrace_cleaner.clean(locations.lazy).first
|
137
140
|
end
|
141
|
+
|
142
|
+
def filter(name, value)
|
143
|
+
ActiveRecord::Base.inspection_filter.filter_param(name, value)
|
144
|
+
end
|
138
145
|
end
|
139
146
|
end
|
140
147
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Middleware
|
5
|
+
# The ShardSelector Middleware provides a framework for automatically
|
6
|
+
# swapping shards. Rails provides a basic framework to determine which
|
7
|
+
# shard to switch to and allows for applications to write custom strategies
|
8
|
+
# for swapping if needed.
|
9
|
+
#
|
10
|
+
# The ShardSelector takes a set of options (currently only `lock` is supported)
|
11
|
+
# that can be used by the middleware to alter behavior. `lock` is
|
12
|
+
# true by default and will prohibit the request from switching shards once
|
13
|
+
# inside the block. If `lock` is false, then shard swapping will be allowed.
|
14
|
+
# For tenant based sharding, `lock` should always be true to prevent application
|
15
|
+
# code from mistakenly switching between tenants.
|
16
|
+
#
|
17
|
+
# Options can be set in the config:
|
18
|
+
#
|
19
|
+
# config.active_record.shard_selector = { lock: true }
|
20
|
+
#
|
21
|
+
# Applications must also provide the code for the resolver as it depends on application
|
22
|
+
# specific models. An example resolver would look like this:
|
23
|
+
#
|
24
|
+
# config.active_record.shard_resolver = ->(request) {
|
25
|
+
# subdomain = request.subdomain
|
26
|
+
# tenant = Tenant.find_by_subdomain!(subdomain)
|
27
|
+
# tenant.shard
|
28
|
+
# }
|
29
|
+
class ShardSelector
|
30
|
+
def initialize(app, resolver, options = {})
|
31
|
+
@app = app
|
32
|
+
@resolver = resolver
|
33
|
+
@options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :resolver, :options
|
37
|
+
|
38
|
+
def call(env)
|
39
|
+
request = ActionDispatch::Request.new(env)
|
40
|
+
|
41
|
+
shard = selected_shard(request)
|
42
|
+
|
43
|
+
set_shard(shard) do
|
44
|
+
@app.call(env)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def selected_shard(request)
|
50
|
+
resolver.call(request)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_shard(shard, &block)
|
54
|
+
ActiveRecord::Base.connected_to(shard: shard.to_sym) do
|
55
|
+
ActiveRecord::Base.prohibit_shard_swapping(options.fetch(:lock, true), &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1001,7 +1001,7 @@ module ActiveRecord
|
|
1001
1001
|
# Determines the version number of the next migration.
|
1002
1002
|
def next_migration_number(number)
|
1003
1003
|
if ActiveRecord.timestamped_migrations
|
1004
|
-
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
|
1004
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S").to_i, ("%.14d" % number).to_i].max
|
1005
1005
|
else
|
1006
1006
|
SchemaMigration.normalize_migration_number(number)
|
1007
1007
|
end
|
@@ -1080,7 +1080,7 @@ module ActiveRecord
|
|
1080
1080
|
# 0 then an empty array will be returned and no migrations
|
1081
1081
|
# will be run.
|
1082
1082
|
#
|
1083
|
-
# If the +current_version+ in the schema is
|
1083
|
+
# If the +current_version+ in the schema is greater than
|
1084
1084
|
# the +target_version+, then +down+ will be run.
|
1085
1085
|
#
|
1086
1086
|
# If none of the conditions are met, +up+ will be run with
|
@@ -571,7 +571,6 @@ module ActiveRecord
|
|
571
571
|
@columns_hash.each do |name, column|
|
572
572
|
type = connection.lookup_cast_type_from_column(column)
|
573
573
|
type = _convert_type_from_options(type)
|
574
|
-
warn_if_deprecated_type(column)
|
575
574
|
define_attribute(
|
576
575
|
name,
|
577
576
|
type,
|
@@ -595,7 +594,7 @@ module ActiveRecord
|
|
595
594
|
@schema_loaded = false
|
596
595
|
@attribute_names = nil
|
597
596
|
@yaml_encoder = nil
|
598
|
-
|
597
|
+
subclasses.each do |descendant|
|
599
598
|
descendant.send(:reload_schema_from_cache)
|
600
599
|
end
|
601
600
|
end
|
@@ -630,32 +629,6 @@ module ActiveRecord
|
|
630
629
|
type
|
631
630
|
end
|
632
631
|
end
|
633
|
-
|
634
|
-
def warn_if_deprecated_type(column)
|
635
|
-
return if attributes_to_define_after_schema_loads.key?(column.name)
|
636
|
-
return unless column.respond_to?(:array?)
|
637
|
-
|
638
|
-
if column.array?
|
639
|
-
array_arguments = ", array: true"
|
640
|
-
else
|
641
|
-
array_arguments = ""
|
642
|
-
end
|
643
|
-
|
644
|
-
if column.sql_type.start_with?("interval")
|
645
|
-
precision_arguments = column.precision.presence && ", precision: #{column.precision}"
|
646
|
-
ActiveSupport::Deprecation.warn(<<~WARNING)
|
647
|
-
The behavior of the `:interval` type will be changing in Rails 7.0
|
648
|
-
to return an `ActiveSupport::Duration` object. If you'd like to keep
|
649
|
-
the old behavior, you can add this line to #{self.name} model:
|
650
|
-
|
651
|
-
attribute :#{column.name}, :string#{precision_arguments}#{array_arguments}
|
652
|
-
|
653
|
-
If you'd like the new behavior today, you can add this line:
|
654
|
-
|
655
|
-
attribute :#{column.name}, :interval#{precision_arguments}#{array_arguments}
|
656
|
-
WARNING
|
657
|
-
end
|
658
|
-
end
|
659
632
|
end
|
660
633
|
end
|
661
634
|
end
|
@@ -245,18 +245,19 @@ module ActiveRecord
|
|
245
245
|
#
|
246
246
|
# === Validating the presence of a parent model
|
247
247
|
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
248
|
+
# The +belongs_to+ association validates the presence of the parent model
|
249
|
+
# by default. You can disable this behavior by specifying <code>optional: true</code>.
|
250
|
+
# This can be used, for example, when conditionally validating the presence
|
251
|
+
# of the parent model:
|
252
|
+
#
|
253
|
+
# class Veterinarian < ActiveRecord::Base
|
254
|
+
# has_many :patients, inverse_of: :veterinarian
|
255
|
+
# accepts_nested_attributes_for :patients
|
255
256
|
# end
|
256
257
|
#
|
257
|
-
# class
|
258
|
-
# belongs_to :
|
259
|
-
#
|
258
|
+
# class Patient < ActiveRecord::Base
|
259
|
+
# belongs_to :veterinarian, inverse_of: :patients, optional: true
|
260
|
+
# validates :veterinarian, presence: true, unless: -> { awaiting_intake }
|
260
261
|
# end
|
261
262
|
#
|
262
263
|
# Note that if you do not specify the +:inverse_of+ option, then
|
@@ -63,8 +63,8 @@ module ActiveRecord
|
|
63
63
|
# go through Active Record's type casting and serialization.
|
64
64
|
#
|
65
65
|
# See <tt>ActiveRecord::Persistence#insert_all</tt> for documentation.
|
66
|
-
def insert(attributes, returning: nil, unique_by: nil)
|
67
|
-
insert_all([ attributes ], returning: returning, unique_by: unique_by)
|
66
|
+
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
67
|
+
insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
68
68
|
end
|
69
69
|
|
70
70
|
# Inserts multiple records into the database in a single SQL INSERT
|
@@ -110,6 +110,17 @@ module ActiveRecord
|
|
110
110
|
# unique_by: %i[ author_id name ]
|
111
111
|
# unique_by: :index_books_on_isbn
|
112
112
|
#
|
113
|
+
# [:record_timestamps]
|
114
|
+
# By default, automatic setting of timestamp columns is controlled by
|
115
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
116
|
+
# behavior.
|
117
|
+
#
|
118
|
+
# To override this and force automatic setting of timestamp columns one
|
119
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
120
|
+
#
|
121
|
+
# record_timestamps: true # Always set timestamps automatically
|
122
|
+
# record_timestamps: false # Never set timestamps automatically
|
123
|
+
#
|
113
124
|
# Because it relies on the index information from the database
|
114
125
|
# <tt>:unique_by</tt> is recommended to be paired with
|
115
126
|
# Active Record's schema_cache.
|
@@ -131,8 +142,8 @@ module ActiveRecord
|
|
131
142
|
# { id: 1, title: "Rework" },
|
132
143
|
# { id: 2, title: "Eloquent Ruby" }
|
133
144
|
# ])
|
134
|
-
def insert_all(attributes, returning: nil, unique_by: nil)
|
135
|
-
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
|
145
|
+
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
146
|
+
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps).execute
|
136
147
|
end
|
137
148
|
|
138
149
|
# Inserts a single record into the database in a single SQL INSERT
|
@@ -141,8 +152,8 @@ module ActiveRecord
|
|
141
152
|
# go through Active Record's type casting and serialization.
|
142
153
|
#
|
143
154
|
# See <tt>ActiveRecord::Persistence#insert_all!</tt> for more.
|
144
|
-
def insert!(attributes, returning: nil)
|
145
|
-
insert_all!([ attributes ], returning: returning)
|
155
|
+
def insert!(attributes, returning: nil, record_timestamps: nil)
|
156
|
+
insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps)
|
146
157
|
end
|
147
158
|
|
148
159
|
# Inserts multiple records into the database in a single SQL INSERT
|
@@ -174,6 +185,17 @@ module ActiveRecord
|
|
174
185
|
# You can also pass an SQL string if you need more control on the return values
|
175
186
|
# (for example, <tt>returning: "id, name as new_name"</tt>).
|
176
187
|
#
|
188
|
+
# [:record_timestamps]
|
189
|
+
# By default, automatic setting of timestamp columns is controlled by
|
190
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
191
|
+
# behavior.
|
192
|
+
#
|
193
|
+
# To override this and force automatic setting of timestamp columns one
|
194
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
195
|
+
#
|
196
|
+
# record_timestamps: true # Always set timestamps automatically
|
197
|
+
# record_timestamps: false # Never set timestamps automatically
|
198
|
+
#
|
177
199
|
# ==== Examples
|
178
200
|
#
|
179
201
|
# # Insert multiple records
|
@@ -188,8 +210,8 @@ module ActiveRecord
|
|
188
210
|
# { id: 1, title: "Rework", author: "David" },
|
189
211
|
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
190
212
|
# ])
|
191
|
-
def insert_all!(attributes, returning: nil)
|
192
|
-
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
|
213
|
+
def insert_all!(attributes, returning: nil, record_timestamps: nil)
|
214
|
+
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps).execute
|
193
215
|
end
|
194
216
|
|
195
217
|
# Updates or inserts (upserts) a single record into the database in a
|
@@ -198,8 +220,8 @@ module ActiveRecord
|
|
198
220
|
# go through Active Record's type casting and serialization.
|
199
221
|
#
|
200
222
|
# See <tt>ActiveRecord::Persistence#upsert_all</tt> for documentation.
|
201
|
-
def upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil)
|
202
|
-
upsert_all([ attributes ], on_duplicate: on_duplicate, returning: returning, unique_by: unique_by)
|
223
|
+
def upsert(attributes, on_duplicate: :update, returning: nil, unique_by: nil, record_timestamps: nil)
|
224
|
+
upsert_all([ attributes ], on_duplicate: on_duplicate, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
203
225
|
end
|
204
226
|
|
205
227
|
# Updates or inserts (upserts) multiple records into the database in a
|
@@ -213,6 +235,10 @@ module ActiveRecord
|
|
213
235
|
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
|
214
236
|
# <tt>:returning</tt> (see below).
|
215
237
|
#
|
238
|
+
# By default, +upsert_all+ will update all the columns that can be updated when
|
239
|
+
# there is a conflict. These are all the columns except primary keys, read-only
|
240
|
+
# columns, and columns covered by the optional +unique_by+.
|
241
|
+
#
|
216
242
|
# ==== Options
|
217
243
|
#
|
218
244
|
# [:returning]
|
@@ -246,9 +272,52 @@ module ActiveRecord
|
|
246
272
|
# Active Record's schema_cache.
|
247
273
|
#
|
248
274
|
# [:on_duplicate]
|
249
|
-
#
|
275
|
+
# Configure the SQL update sentence that will be used in case of conflict.
|
276
|
+
#
|
277
|
+
# NOTE: If you use this option you must provide all the columns you want to update
|
278
|
+
# by yourself.
|
279
|
+
#
|
280
|
+
# Example:
|
281
|
+
#
|
282
|
+
# Commodity.upsert_all(
|
283
|
+
# [
|
284
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
285
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
286
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
287
|
+
# ],
|
288
|
+
# on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
|
289
|
+
# )
|
290
|
+
#
|
291
|
+
# See the related +:update_only+ option. Both options can't be used at the same time.
|
250
292
|
#
|
251
|
-
#
|
293
|
+
# [:update_only]
|
294
|
+
# Provide a list of column names that will be updated in case of conflict. If not provided,
|
295
|
+
# +upsert_all+ will update all the columns that can be updated. These are all the columns
|
296
|
+
# except primary keys, read-only columns, and columns covered by the optional +unique_by+
|
297
|
+
#
|
298
|
+
# Example:
|
299
|
+
#
|
300
|
+
# Commodity.upsert_all(
|
301
|
+
# [
|
302
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
303
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
304
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
305
|
+
# ],
|
306
|
+
# update_only: [:price] # Only prices will be updated
|
307
|
+
# )
|
308
|
+
#
|
309
|
+
# See the related +:on_duplicate+ option. Both options can't be used at the same time.
|
310
|
+
#
|
311
|
+
# [:record_timestamps]
|
312
|
+
# By default, automatic setting of timestamp columns is controlled by
|
313
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
314
|
+
# behavior.
|
315
|
+
#
|
316
|
+
# To override this and force automatic setting of timestamp columns one
|
317
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
318
|
+
#
|
319
|
+
# record_timestamps: true # Always set timestamps automatically
|
320
|
+
# record_timestamps: false # Never set timestamps automatically
|
252
321
|
#
|
253
322
|
# ==== Examples
|
254
323
|
#
|
@@ -261,8 +330,8 @@ module ActiveRecord
|
|
261
330
|
# ], unique_by: :isbn)
|
262
331
|
#
|
263
332
|
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
|
264
|
-
def upsert_all(attributes, on_duplicate: :update, returning: nil, unique_by: nil)
|
265
|
-
InsertAll.new(self, attributes, on_duplicate: on_duplicate, returning: returning, unique_by: unique_by).execute
|
333
|
+
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
334
|
+
InsertAll.new(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps).execute
|
266
335
|
end
|
267
336
|
|
268
337
|
# Given an attributes hash, +instantiate+ returns a new instance of
|
@@ -431,9 +500,8 @@ module ActiveRecord
|
|
431
500
|
def _update_record(values, constraints) # :nodoc:
|
432
501
|
constraints = constraints.map { |name, value| predicate_builder[name, value] }
|
433
502
|
|
434
|
-
|
435
|
-
|
436
|
-
end
|
503
|
+
default_constraint = build_default_constraint
|
504
|
+
constraints << default_constraint if default_constraint
|
437
505
|
|
438
506
|
if current_scope = self.global_current_scope
|
439
507
|
constraints << current_scope.where_clause.ast
|
@@ -449,9 +517,8 @@ module ActiveRecord
|
|
449
517
|
def _delete_record(constraints) # :nodoc:
|
450
518
|
constraints = constraints.map { |name, value| predicate_builder[name, value] }
|
451
519
|
|
452
|
-
|
453
|
-
|
454
|
-
end
|
520
|
+
default_constraint = build_default_constraint
|
521
|
+
constraints << default_constraint if default_constraint
|
455
522
|
|
456
523
|
if current_scope = self.global_current_scope
|
457
524
|
constraints << current_scope.where_clause.ast
|
@@ -479,6 +546,16 @@ module ActiveRecord
|
|
479
546
|
def discriminate_class_for_record(record)
|
480
547
|
self
|
481
548
|
end
|
549
|
+
|
550
|
+
# Called by +_update_record+ and +_delete_record+
|
551
|
+
# to build `where` clause from default scopes.
|
552
|
+
# Skips empty scopes.
|
553
|
+
def build_default_constraint
|
554
|
+
return unless default_scopes?(all_queries: true)
|
555
|
+
|
556
|
+
default_where_clause = default_scoped(all_queries: true).where_clause
|
557
|
+
default_where_clause.ast unless default_where_clause.empty?
|
558
|
+
end
|
482
559
|
end
|
483
560
|
|
484
561
|
# Returns true if this object hasn't been saved yet -- that is, a record
|
@@ -1035,7 +1112,8 @@ module ActiveRecord
|
|
1035
1112
|
|
1036
1113
|
def _raise_record_not_destroyed
|
1037
1114
|
@_association_destroy_exception ||= nil
|
1038
|
-
|
1115
|
+
key = self.class.primary_key
|
1116
|
+
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{send(key)}", self)
|
1039
1117
|
ensure
|
1040
1118
|
@_association_destroy_exception = nil
|
1041
1119
|
end
|
@@ -38,24 +38,24 @@ module ActiveRecord
|
|
38
38
|
# tags = [
|
39
39
|
# :application,
|
40
40
|
# {
|
41
|
-
# custom_tag: ->(context) { context[:controller]
|
41
|
+
# custom_tag: ->(context) { context[:controller]&.controller_name },
|
42
42
|
# custom_value: -> { Custom.value },
|
43
43
|
# }
|
44
44
|
# ]
|
45
45
|
# ActiveRecord::QueryLogs.tags = tags
|
46
46
|
#
|
47
|
-
# The QueryLogs +context+ can be manipulated via
|
48
|
-
#
|
49
|
-
# Direct updates to a context value:
|
50
|
-
#
|
51
|
-
# ActiveRecord::QueryLogs.update_context(foo: Bar.new)
|
47
|
+
# The QueryLogs +context+ can be manipulated via the +ActiveSupport::ExecutionContext.set+ method.
|
52
48
|
#
|
53
49
|
# Temporary updates limited to the execution of a block:
|
54
50
|
#
|
55
|
-
#
|
51
|
+
# ActiveSupport::ExecutionContext.set(foo: Bar.new) do
|
56
52
|
# posts = Post.all
|
57
53
|
# end
|
58
54
|
#
|
55
|
+
# Direct updates to a context value:
|
56
|
+
#
|
57
|
+
# ActiveSupport::ExecutionContext[:foo] = Bar.new
|
58
|
+
#
|
59
59
|
# Tag comments can be prepended to the query:
|
60
60
|
#
|
61
61
|
# ActiveRecord::QueryLogs.prepend_comment = true
|
@@ -75,66 +75,22 @@ module ActiveRecord
|
|
75
75
|
mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
|
76
76
|
thread_mattr_accessor :cached_comment, instance_accessor: false
|
77
77
|
|
78
|
-
class NullObject # :nodoc:
|
79
|
-
def method_missing(method, *args, &block)
|
80
|
-
NullObject.new
|
81
|
-
end
|
82
|
-
|
83
|
-
def nil?
|
84
|
-
true
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
def respond_to_missing?(method, include_private = false)
|
89
|
-
true
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
78
|
class << self
|
94
|
-
# Updates the context used to construct tags in the SQL comment.
|
95
|
-
# Resets the cached comment if <tt>cache_query_log_tags</tt> is +true+.
|
96
|
-
def update_context(**options)
|
97
|
-
context.merge!(**options.symbolize_keys)
|
98
|
-
self.cached_comment = nil
|
99
|
-
end
|
100
|
-
|
101
|
-
# Updates the context used to construct tags in the SQL comment during
|
102
|
-
# execution of the provided block. Resets the provided keys to their
|
103
|
-
# previous value once the block exits.
|
104
|
-
def set_context(**options)
|
105
|
-
keys = options.keys
|
106
|
-
previous_context = keys.zip(context.values_at(*keys)).to_h
|
107
|
-
update_context(**options)
|
108
|
-
yield if block_given?
|
109
|
-
ensure
|
110
|
-
update_context(**previous_context)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Temporarily tag any query executed within `&block`. Can be nested.
|
114
|
-
def with_tag(tag, &block)
|
115
|
-
inline_tags.push(tag)
|
116
|
-
yield if block_given?
|
117
|
-
ensure
|
118
|
-
inline_tags.pop
|
119
|
-
end
|
120
|
-
|
121
79
|
def call(sql) # :nodoc:
|
122
|
-
parts = self.comments
|
123
80
|
if prepend_comment
|
124
|
-
|
81
|
+
"#{self.comment} #{sql}"
|
125
82
|
else
|
126
|
-
|
127
|
-
end
|
128
|
-
parts.join(" ")
|
83
|
+
"#{sql} #{self.comment}"
|
84
|
+
end.strip
|
129
85
|
end
|
130
86
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
87
|
+
def clear_cache # :nodoc:
|
88
|
+
self.cached_comment = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
137
92
|
|
93
|
+
private
|
138
94
|
# Returns an SQL comment +String+ containing the query log tags.
|
139
95
|
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
|
140
96
|
def comment
|
@@ -152,30 +108,13 @@ module ActiveRecord
|
|
152
108
|
end
|
153
109
|
end
|
154
110
|
|
155
|
-
# Returns a +String+ containing any inline comments from +with_tag+.
|
156
|
-
def inline_comment
|
157
|
-
return nil unless inline_tags.present?
|
158
|
-
"/*#{escape_sql_comment(inline_tag_content)}*/"
|
159
|
-
end
|
160
|
-
|
161
|
-
# Return the set of active inline tags from +with_tag+.
|
162
|
-
def inline_tags
|
163
|
-
if context[:inline_tags].nil?
|
164
|
-
context[:inline_tags] = []
|
165
|
-
else
|
166
|
-
context[:inline_tags]
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def context
|
171
|
-
Thread.current[:active_record_query_log_tags_context] ||= Hash.new { NullObject.new }
|
172
|
-
end
|
173
|
-
|
174
111
|
def escape_sql_comment(content)
|
175
112
|
content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
|
176
113
|
end
|
177
114
|
|
178
115
|
def tag_content
|
116
|
+
context = ActiveSupport::ExecutionContext.to_h
|
117
|
+
|
179
118
|
tags.flat_map { |i| [*i] }.filter_map do |tag|
|
180
119
|
key, handler = tag
|
181
120
|
handler ||= taggings[key]
|
@@ -194,10 +133,6 @@ module ActiveRecord
|
|
194
133
|
"#{key}:#{val}" unless val.nil?
|
195
134
|
end.join(",")
|
196
135
|
end
|
197
|
-
|
198
|
-
def inline_tag_content
|
199
|
-
inline_tags.join
|
200
|
-
end
|
201
136
|
end
|
202
137
|
end
|
203
138
|
end
|
@@ -102,6 +102,14 @@ module ActiveRecord
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
initializer "active_record.shard_selector" do
|
106
|
+
if resolver = config.active_record.shard_resolver
|
107
|
+
options = config.active_record.shard_selector || {}
|
108
|
+
|
109
|
+
config.app_middleware.use ActiveRecord::Middleware::ShardSelector, resolver, options
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
105
113
|
initializer "Check for cache versioning support" do
|
106
114
|
config.after_initialize do |app|
|
107
115
|
ActiveSupport.on_load(:active_record) do
|
@@ -130,7 +138,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
130
138
|
initializer "active_record.check_schema_cache_dump" do
|
131
139
|
check_schema_cache_dump_version = config.active_record.check_schema_cache_dump_version
|
132
140
|
|
133
|
-
if config.active_record.use_schema_cache_dump
|
141
|
+
if config.active_record.use_schema_cache_dump && !config.active_record.lazily_load_schema_cache
|
134
142
|
config.after_initialize do |app|
|
135
143
|
ActiveSupport.on_load(:active_record) do
|
136
144
|
db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
|
@@ -232,6 +240,8 @@ To keep using the current cache store, you can turn off cache versioning entirel
|
|
232
240
|
:database_selector,
|
233
241
|
:database_resolver,
|
234
242
|
:database_resolver_context,
|
243
|
+
:shard_selector,
|
244
|
+
:shard_resolver,
|
235
245
|
:query_log_tags_enabled,
|
236
246
|
:query_log_tags,
|
237
247
|
:cache_query_log_tags,
|