activerecord 7.2.0.beta1 → 7.2.0.beta3

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.
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