activerecord 7.2.0.beta1 → 7.2.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/lib/active_record/associations/collection_association.rb +3 -3
  4. data/lib/active_record/associations/errors.rb +265 -0
  5. data/lib/active_record/associations.rb +0 -262
  6. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  7. data/lib/active_record/attribute_methods/read.rb +3 -3
  8. data/lib/active_record/attribute_methods/write.rb +3 -3
  9. data/lib/active_record/attribute_methods.rb +8 -6
  10. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +29 -13
  11. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  12. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  13. data/lib/active_record/connection_adapters/abstract/transaction.rb +69 -9
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  15. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -1
  16. data/lib/active_record/database_configurations/database_config.rb +4 -0
  17. data/lib/active_record/enum.rb +1 -1
  18. data/lib/active_record/errors.rb +20 -8
  19. data/lib/active_record/gem_version.rb +1 -1
  20. data/lib/active_record/railtie.rb +3 -4
  21. data/lib/active_record/railties/databases.rake +1 -1
  22. data/lib/active_record/relation/calculations.rb +1 -1
  23. data/lib/active_record/relation/query_methods.rb +22 -10
  24. data/lib/active_record/signed_id.rb +9 -0
  25. data/lib/active_record/tasks/database_tasks.rb +2 -2
  26. data/lib/active_record/test_fixtures.rb +10 -3
  27. data/lib/active_record/timestamp.rb +1 -1
  28. data/lib/active_record/transaction.rb +106 -42
  29. data/lib/active_record/transactions.rb +29 -2
  30. data/lib/active_record.rb +5 -5
  31. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  32. metadata +11 -10
@@ -77,6 +77,13 @@ module ActiveRecord
77
77
  # alias attributes in Active Record are lazily generated
78
78
  end
79
79
 
80
+ def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
81
+ attribute_method_patterns.each do |pattern|
82
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
83
+ end
84
+ attribute_method_patterns_cache.clear
85
+ end
86
+
80
87
  def alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
81
88
  old_name = old_name.to_s
82
89
 
@@ -84,12 +91,7 @@ module ActiveRecord
84
91
  raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
85
92
  "Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
86
93
  else
87
- method_name = pattern.method_name(new_name).to_s
88
- parameters = pattern.parameters
89
-
90
- define_proxy_call(code_generator, method_name, pattern.proxy_target, parameters, old_name,
91
- namespace: :proxy_alias_attribute
92
- )
94
+ define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
93
95
  end
94
96
  end
95
97
 
@@ -74,7 +74,7 @@ module ActiveRecord
74
74
  # Connections can be obtained and used from a connection pool in several
75
75
  # ways:
76
76
  #
77
- # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling.connection].
77
+ # 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
78
78
  # When you're done with the connection(s) and wish it to be returned to the pool, you call
79
79
  # {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
80
80
  # This is the default behavior for Active Record when used in conjunction with
@@ -253,6 +253,7 @@ module ActiveRecord
253
253
 
254
254
  @available = ConnectionLeasingQueue.new self
255
255
  @pinned_connection = nil
256
+ @pinned_connections_depth = 0
256
257
 
257
258
  @async_executor = build_async_executor
258
259
 
@@ -262,6 +263,13 @@ module ActiveRecord
262
263
  @reaper.run
263
264
  end
264
265
 
266
+ def inspect # :nodoc:
267
+ name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
268
+ shard_field = " shard=#{@shard.inspect}" unless @shard == :default
269
+
270
+ "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
271
+ end
272
+
265
273
  def schema_cache
266
274
  @schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
267
275
  end
@@ -305,15 +313,15 @@ module ActiveRecord
305
313
  def connection
306
314
  ActiveRecord.deprecator.warn(<<~MSG)
307
315
  ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
308
- and will be removed in Rails 7.3. Use #lease_connection instead.
316
+ and will be removed in Rails 8.0. Use #lease_connection instead.
309
317
  MSG
310
318
  lease_connection
311
319
  end
312
320
 
313
321
  def pin_connection!(lock_thread) # :nodoc:
314
- raise "There is already a pinned connection" if @pinned_connection
322
+ @pinned_connection ||= (connection_lease&.connection || checkout)
323
+ @pinned_connections_depth += 1
315
324
 
316
- @pinned_connection = (connection_lease&.connection || checkout)
317
325
  # Any leased connection must be in @connections otherwise
318
326
  # some methods like #connected? won't behave correctly
319
327
  unless @connections.include?(@pinned_connection)
@@ -330,7 +338,10 @@ module ActiveRecord
330
338
 
331
339
  clean = true
332
340
  @pinned_connection.lock.synchronize do
333
- connection, @pinned_connection = @pinned_connection, nil
341
+ @pinned_connections_depth -= 1
342
+ connection = @pinned_connection
343
+ @pinned_connection = nil if @pinned_connections_depth.zero?
344
+
334
345
  if connection.transaction_open?
335
346
  connection.rollback_transaction
336
347
  else
@@ -338,8 +349,11 @@ module ActiveRecord
338
349
  clean = false
339
350
  connection.reset!
340
351
  end
341
- connection.lock_thread = nil
342
- checkin(connection)
352
+
353
+ if @pinned_connection.nil?
354
+ connection.lock_thread = nil
355
+ checkin(connection)
356
+ end
343
357
  end
344
358
 
345
359
  clean
@@ -527,12 +541,14 @@ module ActiveRecord
527
541
  # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
528
542
  def checkout(checkout_timeout = @checkout_timeout)
529
543
  if @pinned_connection
530
- synchronize do
531
- @pinned_connection.verify!
532
- # Any leased connection must be in @connections otherwise
533
- # some methods like #connected? won't behave correctly
534
- unless @connections.include?(@pinned_connection)
535
- @connections << @pinned_connection
544
+ @pinned_connection.lock.synchronize do
545
+ synchronize do
546
+ @pinned_connection.verify!
547
+ # Any leased connection must be in @connections otherwise
548
+ # some methods like #connected? won't behave correctly
549
+ unless @connections.include?(@pinned_connection)
550
+ @connections << @pinned_connection
551
+ end
536
552
  end
537
553
  end
538
554
  @pinned_connection
@@ -356,7 +356,7 @@ module ActiveRecord
356
356
  if isolation
357
357
  raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
358
358
  end
359
- yield current_transaction
359
+ yield current_transaction.user_transaction
360
360
  else
361
361
  transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable, &block)
362
362
  end
@@ -277,6 +277,7 @@ module ActiveRecord
277
277
  type_casted_binds: -> { type_casted_binds(binds) },
278
278
  name: name,
279
279
  connection: self,
280
+ transaction: current_transaction.user_transaction.presence,
280
281
  cached: true
281
282
  }
282
283
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/digest"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  # = Active Record Connection Adapters Transaction State
@@ -89,7 +91,9 @@ module ActiveRecord
89
91
  raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
90
92
  @started = true
91
93
 
92
- @payload = @base_payload.dup
94
+ ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload)
95
+
96
+ @payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome.
93
97
  @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
94
98
  @handle.start
95
99
  end
@@ -104,7 +108,6 @@ module ActiveRecord
104
108
  end
105
109
 
106
110
  class NullTransaction # :nodoc:
107
- def initialize; end
108
111
  def state; end
109
112
  def closed?; true; end
110
113
  def open?; false; end
@@ -118,11 +121,31 @@ module ActiveRecord
118
121
  def materialized?; false; end
119
122
  def before_commit; yield; end
120
123
  def after_commit; yield; end
121
- def after_rollback; end # noop
124
+ def after_rollback; end
125
+ def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
122
126
  end
123
127
 
124
- class Transaction < ActiveRecord::Transaction # :nodoc:
125
- attr_reader :connection, :state, :savepoint_name, :isolation_level
128
+ class Transaction # :nodoc:
129
+ class Callback # :nodoc:
130
+ def initialize(event, callback)
131
+ @event = event
132
+ @callback = callback
133
+ end
134
+
135
+ def before_commit
136
+ @callback.call if @event == :before_commit
137
+ end
138
+
139
+ def after_commit
140
+ @callback.call if @event == :after_commit
141
+ end
142
+
143
+ def after_rollback
144
+ @callback.call if @event == :after_rollback
145
+ end
146
+ end
147
+
148
+ attr_reader :connection, :state, :savepoint_name, :isolation_level, :user_transaction
126
149
  attr_accessor :written
127
150
 
128
151
  delegate :invalidate!, :invalidated?, to: :@state
@@ -131,6 +154,7 @@ module ActiveRecord
131
154
  super()
132
155
  @connection = connection
133
156
  @state = TransactionState.new
157
+ @callbacks = nil
134
158
  @records = nil
135
159
  @isolation_level = isolation
136
160
  @materialized = false
@@ -138,7 +162,8 @@ module ActiveRecord
138
162
  @run_commit_callbacks = run_commit_callbacks
139
163
  @lazy_enrollment_records = nil
140
164
  @dirty = false
141
- @instrumenter = TransactionInstrumenter.new(connection: connection)
165
+ @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION
166
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction)
142
167
  end
143
168
 
144
169
  def dirty!
@@ -149,6 +174,14 @@ module ActiveRecord
149
174
  @dirty
150
175
  end
151
176
 
177
+ def open?
178
+ true
179
+ end
180
+
181
+ def closed?
182
+ false
183
+ end
184
+
152
185
  def add_record(record, ensure_finalize = true)
153
186
  @records ||= []
154
187
  if ensure_finalize
@@ -159,6 +192,30 @@ module ActiveRecord
159
192
  end
160
193
  end
161
194
 
195
+ def before_commit(&block)
196
+ if @state.finalized?
197
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
198
+ end
199
+
200
+ (@callbacks ||= []) << Callback.new(:before_commit, block)
201
+ end
202
+
203
+ def after_commit(&block)
204
+ if @state.finalized?
205
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
206
+ end
207
+
208
+ (@callbacks ||= []) << Callback.new(:after_commit, block)
209
+ end
210
+
211
+ def after_rollback(&block)
212
+ if @state.finalized?
213
+ raise ActiveRecordError, "Cannot register callbacks on a finalized transaction"
214
+ end
215
+
216
+ (@callbacks ||= []) << Callback.new(:after_rollback, block)
217
+ end
218
+
162
219
  def records
163
220
  if @lazy_enrollment_records
164
221
  @records.concat @lazy_enrollment_records.values
@@ -269,8 +326,11 @@ module ActiveRecord
269
326
 
270
327
  def full_rollback?; true; end
271
328
  def joinable?; @joinable; end
272
- def closed?; false; end
273
- def open?; !closed?; end
329
+
330
+ protected
331
+ def append_callbacks(callbacks) # :nodoc:
332
+ (@callbacks ||= []).concat(callbacks)
333
+ end
274
334
 
275
335
  private
276
336
  def unique_records
@@ -553,7 +613,7 @@ module ActiveRecord
553
613
  @connection.lock.synchronize do
554
614
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
555
615
  begin
556
- yield transaction
616
+ yield transaction.user_transaction
557
617
  rescue Exception => error
558
618
  rollback_transaction
559
619
  after_failure_actions(transaction, error)
@@ -172,6 +172,13 @@ module ActiveRecord
172
172
  @verified = false
173
173
  end
174
174
 
175
+ def inspect # :nodoc:
176
+ name_field = " name=#{pool.db_config.name.inspect}" unless pool.db_config.name == "primary"
177
+ shard_field = " shard=#{shard.inspect}" unless shard == :default
178
+
179
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)} env_name=#{pool.db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
180
+ end
181
+
175
182
  def lock_thread=(lock_thread) # :nodoc:
176
183
  @lock =
177
184
  case lock_thread
@@ -1118,6 +1125,7 @@ module ActiveRecord
1118
1125
  statement_name: statement_name,
1119
1126
  async: async,
1120
1127
  connection: self,
1128
+ transaction: current_transaction.user_transaction.presence,
1121
1129
  row_count: 0,
1122
1130
  &block
1123
1131
  )
@@ -644,7 +644,7 @@ module ActiveRecord
644
644
  # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
645
645
  # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
646
646
  if supports_insert_raw_alias_syntax?
647
- values_alias = quote_table_name("#{insert.model.table_name}_values")
647
+ values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
648
648
  sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
649
649
 
650
650
  if insert.skip_duplicates?
@@ -18,6 +18,10 @@ module ActiveRecord
18
18
  @adapter_class ||= ActiveRecord::ConnectionAdapters.resolve(adapter)
19
19
  end
20
20
 
21
+ def inspect # :nodoc:
22
+ "#<#{self.class.name} env_name=#{@env_name} name=#{@name} adapter_class=#{adapter_class}>"
23
+ end
24
+
21
25
  def new_connection
22
26
  adapter_class.new(configuration_hash)
23
27
  end
@@ -226,7 +226,7 @@ module ActiveRecord
226
226
 
227
227
  ActiveRecord.deprecator.warn(<<~MSG)
228
228
  Defining enums with keyword arguments is deprecated and will be removed
229
- in Rails 7.3. Positional arguments should be used instead:
229
+ in Rails 8.0. Positional arguments should be used instead:
230
230
 
231
231
  #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
232
232
  MSG
@@ -130,9 +130,17 @@ module ActiveRecord
130
130
 
131
131
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
132
132
  # {ActiveRecord::Base.update_attribute!}[rdoc-ref:Persistence#update_attribute!]
133
- # methods when a record is failed to validate or cannot be saved due to any of the
133
+ # methods when a record failed to validate or cannot be saved due to any of the
134
134
  # <tt>before_*</tt> callbacks throwing +:abort+. See
135
- # ActiveRecord::Callbacks for further details
135
+ # ActiveRecord::Callbacks for further details.
136
+ #
137
+ # class Product < ActiveRecord::Base
138
+ # before_save do
139
+ # throw :abort if price < 0
140
+ # end
141
+ # end
142
+ #
143
+ # Product.create! # => raises an ActiveRecord::RecordNotSaved
136
144
  class RecordNotSaved < ActiveRecordError
137
145
  attr_reader :record
138
146
 
@@ -143,15 +151,17 @@ module ActiveRecord
143
151
  end
144
152
 
145
153
  # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
146
- # when a call to {#destroy}[rdoc-ref:Persistence#destroy]
147
- # would return false.
154
+ # when a record cannot be destroyed due to any of the
155
+ # <tt>before_destroy</tt> callbacks throwing +:abort+. See
156
+ # ActiveRecord::Callbacks for further details.
148
157
  #
149
- # begin
150
- # complex_operation_that_internally_calls_destroy!
151
- # rescue ActiveRecord::RecordNotDestroyed => invalid
152
- # puts invalid.record.errors
158
+ # class User < ActiveRecord::Base
159
+ # before_destroy do
160
+ # throw :abort if still_active?
161
+ # end
153
162
  # end
154
163
  #
164
+ # User.first.destroy! # => raises an ActiveRecord::RecordNotDestroyed
155
165
  class RecordNotDestroyed < ActiveRecordError
156
166
  attr_reader :record
157
167
 
@@ -583,3 +593,5 @@ module ActiveRecord
583
593
  class DatabaseVersionError < ActiveRecordError
584
594
  end
585
595
  end
596
+
597
+ require "active_record/associations/errors"
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  MAJOR = 7
11
11
  MINOR = 2
12
12
  TINY = 0
13
- PRE = "beta1"
13
+ PRE = "beta3"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -187,7 +187,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
187
187
  if config.active_record.warn_on_records_fetched_greater_than
188
188
  ActiveRecord.deprecator.warn <<~MSG.squish
189
189
  `config.active_record.warn_on_records_fetched_greater_than` is deprecated and will be
190
- removed in Rails 7.3.
190
+ removed in Rails 8.0.
191
191
  Please subscribe to `sql.active_record` notifications and access the row count field to
192
192
  detect large result set sizes.
193
193
  MSG
@@ -241,8 +241,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
241
241
  end
242
242
 
243
243
  ActiveSupport.on_load(:active_record) do
244
- # Configs used in other initializers
245
- configs = configs.except(
244
+ configs_used_in_other_initializers = configs.except(
246
245
  :migration_error,
247
246
  :database_selector,
248
247
  :database_resolver,
@@ -259,7 +258,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
259
258
  :postgresql_adapter_decode_dates,
260
259
  )
261
260
 
262
- configs.each do |k, v|
261
+ configs_used_in_other_initializers.each do |k, v|
263
262
  next if k == :encryption
264
263
  setter = "#{k}="
265
264
  # Some existing initializers might rely on Active Record configuration
@@ -89,7 +89,7 @@ db_namespace = namespace :db do
89
89
  task migrate: :load_config do
90
90
  db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
91
91
 
92
- if db_configs.size == 1
92
+ if db_configs.size == 1 && db_configs.first.primary?
93
93
  ActiveRecord::Tasks::DatabaseTasks.migrate
94
94
  else
95
95
  mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
@@ -604,7 +604,7 @@ module ActiveRecord
604
604
  klass.attribute_types.fetch(name = result.columns[i]) do
605
605
  join_dependencies ||= build_join_dependencies
606
606
  lookup_cast_type_from_join_dependencies(name, join_dependencies) ||
607
- result.column_types[name] || Type.default_value
607
+ result.column_types[i] || Type.default_value
608
608
  end
609
609
  end
610
610
  end
@@ -746,7 +746,7 @@ module ActiveRecord
746
746
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
747
747
  :limit, :offset, :joins, :left_outer_joins, :annotate,
748
748
  :includes, :eager_load, :preload, :from, :readonly,
749
- :having, :optimizer_hints])
749
+ :having, :optimizer_hints, :with])
750
750
 
751
751
  # Removes an unwanted relation that is already defined on a chain of relations.
752
752
  # This is useful when passing around chains of relations and would like to
@@ -2063,17 +2063,29 @@ module ActiveRecord
2063
2063
  order_args.flat_map do |arg|
2064
2064
  case arg
2065
2065
  when String, Symbol
2066
- arg
2066
+ extract_table_name_from(arg)
2067
2067
  when Hash
2068
- arg.keys.select { |e| e.is_a?(String) || e.is_a?(Symbol) }
2068
+ arg
2069
+ .map do |key, value|
2070
+ case value
2071
+ when Hash
2072
+ key.to_s
2073
+ else
2074
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
2075
+ end
2076
+ end
2077
+ when Arel::Attribute
2078
+ arg.relation.name
2079
+ when Arel::Nodes::Ordering
2080
+ if arg.expr.is_a?(Arel::Attribute)
2081
+ arg.expr.relation.name
2082
+ end
2069
2083
  end
2070
- end.filter_map do |arg|
2071
- arg =~ /^\W?(\w+)\W?\./ && $1
2072
- end +
2073
- order_args
2074
- .select { |e| e.is_a?(Hash) }
2075
- .flat_map { |e| e.map { |k, v| k if v.is_a?(Hash) } }
2076
- .compact
2084
+ end.compact
2085
+ end
2086
+
2087
+ def extract_table_name_from(string)
2088
+ string.match(/^\W?(\w+)\W?\./) && $1
2077
2089
  end
2078
2090
 
2079
2091
  def order_column(field)
@@ -106,7 +106,16 @@ module ActiveRecord
106
106
 
107
107
 
108
108
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
109
+ #
109
110
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
111
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
112
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
113
+ # It's just encoded and protected against tampering.
114
+ #
115
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
116
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
117
+ # when passed to +find_signed+ (or raise with +find_signed!+).
118
+ #
110
119
  # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
111
120
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
112
121
  # record. If a purpose is set, this too must match.
@@ -446,7 +446,7 @@ module ActiveRecord
446
446
  db_config_or_name.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
447
447
  else
448
448
  ActiveRecord.deprecator.warn(<<~MSG.squish)
449
- Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 7.3. Pass a
449
+ Passing a database name to `cache_dump_filename` is deprecated and will be removed in Rails 8.0. Pass a
450
450
  `ActiveRecord::DatabaseConfigurations::DatabaseConfig` object instead.
451
451
  MSG
452
452
 
@@ -531,7 +531,7 @@ module ActiveRecord
531
531
  def schema_cache_env
532
532
  if ENV["SCHEMA_CACHE"]
533
533
  ActiveRecord.deprecator.warn(<<~MSG.squish)
534
- Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 7.3.
534
+ Setting `ENV["SCHEMA_CACHE"]` is deprecated and will be removed in Rails 8.0.
535
535
  Configure the `:schema_cache_path` in the database configuration instead.
536
536
  MSG
537
537
 
@@ -96,6 +96,14 @@ module ActiveRecord
96
96
  end
97
97
  end
98
98
 
99
+ # Generic fixture accessor for fixture names that may conflict with other methods.
100
+ #
101
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
102
+ # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
103
+ def fixture(fixture_set_name, *fixture_names)
104
+ active_record_fixture(fixture_set_name, *fixture_names)
105
+ end
106
+
99
107
  private
100
108
  def run_in_transaction?
101
109
  use_transactional_tests &&
@@ -255,7 +263,7 @@ module ActiveRecord
255
263
 
256
264
  def method_missing(method, ...)
257
265
  if fixture_sets.key?(method.name)
258
- _active_record_fixture(method, ...)
266
+ active_record_fixture(method, ...)
259
267
  else
260
268
  super
261
269
  end
@@ -269,14 +277,13 @@ module ActiveRecord
269
277
  end
270
278
  end
271
279
 
272
- def _active_record_fixture(fixture_set_name, *fixture_names)
280
+ def active_record_fixture(fixture_set_name, *fixture_names)
273
281
  if fs_name = fixture_sets[fixture_set_name.name]
274
282
  access_fixture(fs_name, *fixture_names)
275
283
  else
276
284
  raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
277
285
  end
278
286
  end
279
- alias_method :fixture, :_active_record_fixture
280
287
 
281
288
  def access_fixture(fs_name, *fixture_names)
282
289
  force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
@@ -162,7 +162,7 @@ module ActiveRecord
162
162
 
163
163
  def max_updated_column_timestamp
164
164
  timestamp_attributes_for_update_in_model
165
- .filter_map { |attr| self[attr]&.to_time }
165
+ .filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
166
166
  .max
167
167
  end
168
168