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.

Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +539 -11
  3. data/lib/active_record/associations/association.rb +2 -8
  4. data/lib/active_record/associations/builder/collection_association.rb +9 -2
  5. data/lib/active_record/associations/collection_association.rb +10 -2
  6. data/lib/active_record/associations/join_dependency.rb +6 -2
  7. data/lib/active_record/associations/preloader/association.rb +68 -48
  8. data/lib/active_record/associations/preloader/batch.rb +3 -6
  9. data/lib/active_record/associations/preloader/through_association.rb +19 -9
  10. data/lib/active_record/associations/preloader.rb +14 -24
  11. data/lib/active_record/associations/through_association.rb +2 -2
  12. data/lib/active_record/associations.rb +16 -3
  13. data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
  14. data/lib/active_record/attribute_methods/dirty.rb +9 -1
  15. data/lib/active_record/attribute_methods.rb +7 -5
  16. data/lib/active_record/autosave_association.rb +3 -3
  17. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
  18. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
  25. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
  27. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/column.rb +4 -0
  29. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
  30. data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
  31. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/pool_config.rb +7 -5
  33. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  34. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  35. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
  36. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  37. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
  41. data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
  43. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  44. data/lib/active_record/connection_handling.rb +31 -19
  45. data/lib/active_record/core.rb +13 -24
  46. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  47. data/lib/active_record/database_configurations/database_config.rb +0 -9
  48. data/lib/active_record/database_configurations/hash_config.rb +40 -8
  49. data/lib/active_record/database_configurations.rb +2 -27
  50. data/lib/active_record/delegated_type.rb +19 -0
  51. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  52. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  53. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
  54. data/lib/active_record/encryption/message_serializer.rb +11 -1
  55. data/lib/active_record/encryption/scheme.rb +1 -1
  56. data/lib/active_record/enum.rb +8 -1
  57. data/lib/active_record/errors.rb +1 -1
  58. data/lib/active_record/explain_registry.rb +11 -6
  59. data/lib/active_record/fixture_set/table_row.rb +1 -1
  60. data/lib/active_record/fixtures.rb +1 -9
  61. data/lib/active_record/future_result.rb +2 -2
  62. data/lib/active_record/gem_version.rb +1 -1
  63. data/lib/active_record/insert_all.rb +52 -15
  64. data/lib/active_record/integration.rb +3 -2
  65. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  66. data/lib/active_record/locking/pessimistic.rb +9 -3
  67. data/lib/active_record/log_subscriber.rb +8 -1
  68. data/lib/active_record/middleware/shard_selector.rb +60 -0
  69. data/lib/active_record/migration.rb +2 -2
  70. data/lib/active_record/model_schema.rb +1 -28
  71. data/lib/active_record/nested_attributes.rb +11 -10
  72. data/lib/active_record/no_touching.rb +1 -1
  73. data/lib/active_record/persistence.rb +99 -21
  74. data/lib/active_record/query_logs.rb +18 -83
  75. data/lib/active_record/railtie.rb +11 -1
  76. data/lib/active_record/railties/databases.rake +4 -91
  77. data/lib/active_record/reflection.rb +22 -6
  78. data/lib/active_record/relation/calculations.rb +1 -10
  79. data/lib/active_record/relation/finder_methods.rb +0 -13
  80. data/lib/active_record/relation/query_methods.rb +5 -14
  81. data/lib/active_record/relation/record_fetch_warning.rb +5 -7
  82. data/lib/active_record/relation/where_clause.rb +2 -15
  83. data/lib/active_record/relation.rb +11 -15
  84. data/lib/active_record/result.rb +0 -5
  85. data/lib/active_record/runtime_registry.rb +10 -12
  86. data/lib/active_record/schema_dumper.rb +7 -0
  87. data/lib/active_record/schema_migration.rb +4 -0
  88. data/lib/active_record/scoping.rb +34 -22
  89. data/lib/active_record/suppressor.rb +11 -15
  90. data/lib/active_record/tasks/database_tasks.rb +18 -44
  91. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
  92. data/lib/active_record/validations/uniqueness.rb +1 -1
  93. data/lib/active_record.rb +41 -33
  94. data/lib/arel/crud.rb +12 -2
  95. data/lib/arel/delete_manager.rb +16 -0
  96. data/lib/arel/filter_predications.rb +9 -0
  97. data/lib/arel/nodes/delete_statement.rb +5 -1
  98. data/lib/arel/nodes/filter.rb +10 -0
  99. data/lib/arel/nodes/function.rb +1 -0
  100. data/lib/arel/nodes/update_statement.rb +5 -1
  101. data/lib/arel/nodes.rb +1 -0
  102. data/lib/arel/predications.rb +10 -2
  103. data/lib/arel/update_manager.rb +16 -0
  104. data/lib/arel/visitors/mysql.rb +2 -1
  105. data/lib/arel/visitors/to_sql.rb +15 -0
  106. data/lib/arel.rb +1 -0
  107. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  108. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  109. metadata +18 -12
@@ -2,50 +2,13 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module LegacyYamlAdapter # :nodoc:
5
- def self.convert(klass, coder)
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
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
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>lock!</tt>).
85
- def with_lock(lock = true)
86
- transaction do
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
- binds << render_bind(attr, casted_params[i])
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 less than
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
- direct_descendants.each do |descendant|
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
- # If you want to validate that a child record is associated with a parent
249
- # record, you can use the +validates_presence_of+ method and the +:inverse_of+
250
- # key as this example illustrates:
251
- #
252
- # class Member < ActiveRecord::Base
253
- # has_many :posts, inverse_of: :member
254
- # accepts_nested_attributes_for :posts
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 Post < ActiveRecord::Base
258
- # belongs_to :member, inverse_of: :posts
259
- # validates_presence_of :member
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
@@ -39,7 +39,7 @@ module ActiveRecord
39
39
 
40
40
  private
41
41
  def klasses
42
- Thread.current[:no_touching_classes] ||= []
42
+ ActiveSupport::IsolatedExecutionState[:active_record_no_touching_classes] ||= []
43
43
  end
44
44
  end
45
45
 
@@ -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
- # Specify a custom SQL for updating rows on conflict.
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
- # NOTE: in this case you must provide all the columns you want to update by yourself.
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
- if default_scopes?(all_queries: true)
435
- constraints << default_scoped(all_queries: true).where_clause.ast
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
- if default_scopes?(all_queries: true)
453
- constraints << default_scoped(all_queries: true).where_clause.ast
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
- raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self)
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].controller_name },
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 +update_context+ & +set_context+ methods.
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
- # ActiveRecord::QueryLogs.set_context(foo: Bar.new) do
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
- parts << sql
81
+ "#{self.comment} #{sql}"
125
82
  else
126
- parts.unshift(sql)
127
- end
128
- parts.join(" ")
83
+ "#{sql} #{self.comment}"
84
+ end.strip
129
85
  end
130
86
 
131
- private
132
- # Returns an array of comments which need to be added to the query, comprised
133
- # of configured and inline tags.
134
- def comments
135
- [ comment, inline_comment ].compact
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,