activerecord 7.2.0.beta2 → 7.2.0.rc1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/lib/active_record/associations/association.rb +6 -0
  4. data/lib/active_record/associations/collection_association.rb +7 -3
  5. data/lib/active_record/associations/errors.rb +265 -0
  6. data/lib/active_record/associations/nested_error.rb +1 -1
  7. data/lib/active_record/associations.rb +2 -264
  8. data/lib/active_record/attribute_methods/dirty.rb +1 -1
  9. data/lib/active_record/attribute_methods/read.rb +3 -3
  10. data/lib/active_record/attribute_methods/write.rb +3 -3
  11. data/lib/active_record/attribute_methods.rb +8 -6
  12. data/lib/active_record/autosave_association.rb +3 -1
  13. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +27 -11
  14. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  16. data/lib/active_record/connection_adapters/abstract/transaction.rb +67 -9
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -1
  18. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -1
  19. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +5 -10
  20. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +28 -10
  21. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +3 -9
  22. data/lib/active_record/database_configurations/database_config.rb +4 -0
  23. data/lib/active_record/errors.rb +32 -11
  24. data/lib/active_record/gem_version.rb +1 -1
  25. data/lib/active_record/railtie.rb +2 -3
  26. data/lib/active_record/railties/databases.rake +1 -1
  27. data/lib/active_record/relation/batches.rb +7 -1
  28. data/lib/active_record/relation/calculations.rb +1 -1
  29. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -3
  30. data/lib/active_record/relation/query_methods.rb +25 -13
  31. data/lib/active_record/signed_id.rb +9 -0
  32. data/lib/active_record/tasks/database_tasks.rb +19 -8
  33. data/lib/active_record/test_fixtures.rb +10 -3
  34. data/lib/active_record/timestamp.rb +1 -1
  35. data/lib/active_record/transaction.rb +56 -55
  36. data/lib/active_record/transactions.rb +1 -1
  37. data/lib/active_record.rb +1 -1
  38. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  39. metadata +13 -12
@@ -8,11 +8,11 @@ module ActiveRecord
8
8
 
9
9
  module ClassMethods # :nodoc:
10
10
  private
11
- def define_method_attribute(name, owner:)
11
+ def define_method_attribute(canonical_name, owner:, as: canonical_name)
12
12
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
13
- owner, name
13
+ owner, canonical_name
14
14
  ) do |temp_method_name, attr_name_expr|
15
- owner.define_cached_method(name, as: temp_method_name, namespace: :active_record) do |batch|
15
+ owner.define_cached_method(temp_method_name, as: as, namespace: :active_record) do |batch|
16
16
  batch <<
17
17
  "def #{temp_method_name}" <<
18
18
  " _read_attribute(#{attr_name_expr}) { |n| missing_attribute(n, caller) }" <<
@@ -12,11 +12,11 @@ module ActiveRecord
12
12
 
13
13
  module ClassMethods # :nodoc:
14
14
  private
15
- def define_method_attribute=(name, owner:)
15
+ def define_method_attribute=(canonical_name, owner:, as: canonical_name)
16
16
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
17
- owner, name, writer: true,
17
+ owner, canonical_name, writer: true,
18
18
  ) do |temp_method_name, attr_name_expr|
19
- owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_record) do |batch|
19
+ owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_record) do |batch|
20
20
  batch <<
21
21
  "def #{temp_method_name}(value)" <<
22
22
  " _write_attribute(#{attr_name_expr}, value)" <<
@@ -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
 
@@ -428,7 +428,9 @@ module ActiveRecord
428
428
  # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
429
429
  def save_has_one_association(reflection)
430
430
  association = association_instance_get(reflection.name)
431
- record = association && association.load_target
431
+ return unless association && association.loaded?
432
+
433
+ record = association.load_target
432
434
 
433
435
  if record && !record.destroyed?
434
436
  autosave = reflection.options[:autosave]
@@ -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
@@ -311,9 +319,9 @@ module ActiveRecord
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,7 +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.presence,
280
+ transaction: current_transaction.user_transaction.presence,
281
281
  cached: true
282
282
  }
283
283
  end
@@ -91,7 +91,9 @@ module ActiveRecord
91
91
  raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
92
92
  @started = true
93
93
 
94
- @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.
95
97
  @handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
96
98
  @handle.start
97
99
  end
@@ -106,10 +108,8 @@ module ActiveRecord
106
108
  end
107
109
 
108
110
  class NullTransaction # :nodoc:
109
- def initialize; end
110
111
  def state; end
111
112
  def closed?; true; end
112
- alias_method :blank?, :closed?
113
113
  def open?; false; end
114
114
  def joinable?; false; end
115
115
  def add_record(record, _ = true); end
@@ -121,12 +121,31 @@ module ActiveRecord
121
121
  def materialized?; false; end
122
122
  def before_commit; yield; end
123
123
  def after_commit; yield; end
124
- def after_rollback; end # noop
125
- def uuid; Digest::UUID.nil_uuid; end
124
+ def after_rollback; end
125
+ def user_transaction; ActiveRecord::Transaction::NULL_TRANSACTION; end
126
126
  end
127
127
 
128
- class Transaction < ActiveRecord::Transaction # :nodoc:
129
- 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
130
149
  attr_accessor :written
131
150
 
132
151
  delegate :invalidate!, :invalidated?, to: :@state
@@ -135,6 +154,7 @@ module ActiveRecord
135
154
  super()
136
155
  @connection = connection
137
156
  @state = TransactionState.new
157
+ @callbacks = nil
138
158
  @records = nil
139
159
  @isolation_level = isolation
140
160
  @materialized = false
@@ -142,7 +162,8 @@ module ActiveRecord
142
162
  @run_commit_callbacks = run_commit_callbacks
143
163
  @lazy_enrollment_records = nil
144
164
  @dirty = false
145
- @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: self)
165
+ @user_transaction = joinable ? ActiveRecord::Transaction.new(self) : ActiveRecord::Transaction::NULL_TRANSACTION
166
+ @instrumenter = TransactionInstrumenter.new(connection: connection, transaction: @user_transaction)
146
167
  end
147
168
 
148
169
  def dirty!
@@ -153,6 +174,14 @@ module ActiveRecord
153
174
  @dirty
154
175
  end
155
176
 
177
+ def open?
178
+ true
179
+ end
180
+
181
+ def closed?
182
+ false
183
+ end
184
+
156
185
  def add_record(record, ensure_finalize = true)
157
186
  @records ||= []
158
187
  if ensure_finalize
@@ -163,6 +192,30 @@ module ActiveRecord
163
192
  end
164
193
  end
165
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
+
166
219
  def records
167
220
  if @lazy_enrollment_records
168
221
  @records.concat @lazy_enrollment_records.values
@@ -274,6 +327,11 @@ module ActiveRecord
274
327
  def full_rollback?; true; end
275
328
  def joinable?; @joinable; end
276
329
 
330
+ protected
331
+ def append_callbacks(callbacks) # :nodoc:
332
+ (@callbacks ||= []).concat(callbacks)
333
+ end
334
+
277
335
  private
278
336
  def unique_records
279
337
  records.uniq(&:__id__)
@@ -555,7 +613,7 @@ module ActiveRecord
555
613
  @connection.lock.synchronize do
556
614
  transaction = begin_transaction(isolation: isolation, joinable: joinable)
557
615
  begin
558
- yield transaction
616
+ yield transaction.user_transaction
559
617
  rescue Exception => error
560
618
  rollback_transaction
561
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,7 +1125,7 @@ module ActiveRecord
1118
1125
  statement_name: statement_name,
1119
1126
  async: async,
1120
1127
  connection: self,
1121
- transaction: current_transaction.presence,
1128
+ transaction: current_transaction.user_transaction.presence,
1122
1129
  row_count: 0,
1123
1130
  &block
1124
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?
@@ -6,16 +6,11 @@ module ActiveRecord
6
6
  module DatabaseStatements
7
7
  # Returns an ActiveRecord::Result instance.
8
8
  def select_all(*, **) # :nodoc:
9
- result = nil
10
- with_raw_connection do |conn|
11
- result = if ExplainRegistry.collect? && prepared_statements
12
- unprepared_statement { super }
13
- else
14
- super
15
- end
16
- conn.abandon_results!
9
+ if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
11
+ else
12
+ super
17
13
  end
18
- result
19
14
  end
20
15
 
21
16
  def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
@@ -60,7 +55,6 @@ module ActiveRecord
60
55
  combine_multi_statements(statements).each do |statement|
61
56
  with_raw_connection do |conn|
62
57
  raw_execute(statement, name)
63
- conn.abandon_results!
64
58
  end
65
59
  end
66
60
  end
@@ -102,6 +96,7 @@ module ActiveRecord
102
96
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
103
97
  sync_timezone_changes(conn)
104
98
  result = conn.query(sql)
99
+ conn.abandon_results!
105
100
  verified!
106
101
  handle_warnings(sql)
107
102
  notification_payload[:row_count] = result&.size || 0
@@ -472,11 +472,7 @@ module ActiveRecord
472
472
  end
473
473
 
474
474
  def table_structure(table_name)
475
- structure = if supports_virtual_columns?
476
- internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
477
- else
478
- internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
479
- end
475
+ structure = table_info(table_name)
480
476
  raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
481
477
  table_structure_with_collation(table_name, structure)
482
478
  end
@@ -679,7 +675,7 @@ module ActiveRecord
679
675
  auto_increments = {}
680
676
  generated_columns = {}
681
677
 
682
- column_strings = table_structure_sql(table_name)
678
+ column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
683
679
 
684
680
  if column_strings.any?
685
681
  column_strings.each do |column_string|
@@ -712,7 +708,15 @@ module ActiveRecord
712
708
  end
713
709
  end
714
710
 
715
- def table_structure_sql(table_name)
711
+ UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
712
+ FINAL_CLOSE_PARENS_REGEX = /\);*\z/
713
+
714
+ def table_structure_sql(table_name, column_names = nil)
715
+ unless column_names
716
+ column_info = table_info(table_name)
717
+ column_names = column_info.map { |column| column["name"] }
718
+ end
719
+
716
720
  sql = <<~SQL
717
721
  SELECT sql FROM
718
722
  (SELECT * FROM sqlite_master UNION ALL
@@ -722,16 +726,30 @@ module ActiveRecord
722
726
 
723
727
  # Result will have following sample string
724
728
  # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
725
- # "password_digest" varchar COLLATE "NOCASE");
729
+ # "password_digest" varchar COLLATE "NOCASE",
730
+ # "o_id" integer,
731
+ # CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
726
732
  result = query_value(sql, "SCHEMA")
727
733
 
728
734
  return [] unless result
729
735
 
730
736
  # Splitting with left parentheses and discarding the first part will return all
731
737
  # columns separated with comma(,).
732
- columns_string = result.split("(", 2).last
738
+ result.partition(UNQUOTED_OPEN_PARENS_REGEX)
739
+ .last
740
+ .sub(FINAL_CLOSE_PARENS_REGEX, "")
741
+ # column definitions can have a comma in them, so split on commas followed
742
+ # by a space and a column name in quotes or followed by the keyword CONSTRAINT
743
+ .split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
744
+ .map(&:strip)
745
+ end
733
746
 
734
- columns_string.split(",").map(&:strip)
747
+ def table_info(table_name)
748
+ if supports_virtual_columns?
749
+ internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
750
+ else
751
+ internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
752
+ end
735
753
  end
736
754
 
737
755
  def arel_visitor
@@ -4,14 +4,6 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module Trilogy
6
6
  module DatabaseStatements
7
- def select_all(*, **) # :nodoc:
8
- result = super
9
- with_raw_connection do |conn|
10
- conn.next_result while conn.more_results_exist?
11
- end
12
- result
13
- end
14
-
15
7
  def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
16
8
  sql = transform_query(sql)
17
9
  check_if_write_query(sql)
@@ -47,6 +39,9 @@ module ActiveRecord
47
39
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
48
40
  sync_timezone_changes(conn)
49
41
  result = conn.query(sql)
42
+ while conn.more_results_exist?
43
+ conn.next_result
44
+ end
50
45
  verified!
51
46
  handle_warnings(sql)
52
47
  notification_payload[:row_count] = result.count
@@ -77,7 +72,6 @@ module ActiveRecord
77
72
  combine_multi_statements(statements).each do |statement|
78
73
  with_raw_connection do |conn|
79
74
  raw_execute(statement, name)
80
- conn.next_result while conn.more_results_exist?
81
75
  end
82
76
  end
83
77
  end
@@ -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
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/deprecation"
4
+
3
5
  module ActiveRecord
6
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
7
+
4
8
  # = Active Record Errors
5
9
  #
6
10
  # Generic Active Record exception class.
@@ -130,9 +134,17 @@ module ActiveRecord
130
134
 
131
135
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
132
136
  # {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
137
+ # methods when a record failed to validate or cannot be saved due to any of the
134
138
  # <tt>before_*</tt> callbacks throwing +:abort+. See
135
- # ActiveRecord::Callbacks for further details
139
+ # ActiveRecord::Callbacks for further details.
140
+ #
141
+ # class Product < ActiveRecord::Base
142
+ # before_save do
143
+ # throw :abort if price < 0
144
+ # end
145
+ # end
146
+ #
147
+ # Product.create! # => raises an ActiveRecord::RecordNotSaved
136
148
  class RecordNotSaved < ActiveRecordError
137
149
  attr_reader :record
138
150
 
@@ -143,15 +155,17 @@ module ActiveRecord
143
155
  end
144
156
 
145
157
  # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
146
- # when a call to {#destroy}[rdoc-ref:Persistence#destroy]
147
- # would return false.
158
+ # when a record cannot be destroyed due to any of the
159
+ # <tt>before_destroy</tt> callbacks throwing +:abort+. See
160
+ # ActiveRecord::Callbacks for further details.
148
161
  #
149
- # begin
150
- # complex_operation_that_internally_calls_destroy!
151
- # rescue ActiveRecord::RecordNotDestroyed => invalid
152
- # puts invalid.record.errors
162
+ # class User < ActiveRecord::Base
163
+ # before_destroy do
164
+ # throw :abort if still_active?
165
+ # end
153
166
  # end
154
167
  #
168
+ # User.first.destroy! # => raises an ActiveRecord::RecordNotDestroyed
155
169
  class RecordNotDestroyed < ActiveRecordError
156
170
  attr_reader :record
157
171
 
@@ -466,10 +480,15 @@ module ActiveRecord
466
480
  # relation.loaded? # => true
467
481
  #
468
482
  # # Methods which try to mutate a loaded relation fail.
469
- # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation
470
- # relation.limit!(5) # => ActiveRecord::ImmutableRelation
471
- class ImmutableRelation < ActiveRecordError
483
+ # relation.where!(title: 'TODO') # => ActiveRecord::UnmodifiableRelation
484
+ # relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
485
+ class UnmodifiableRelation < ActiveRecordError
472
486
  end
487
+ deprecate_constant(
488
+ :ImmutableRelation,
489
+ "ActiveRecord::UnmodifiableRelation",
490
+ deprecator: ActiveRecord.deprecator
491
+ )
473
492
 
474
493
  # TransactionIsolationError will be raised under the following conditions:
475
494
  #
@@ -583,3 +602,5 @@ module ActiveRecord
583
602
  class DatabaseVersionError < ActiveRecordError
584
603
  end
585
604
  end
605
+
606
+ 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 = "beta2"
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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
@@ -341,7 +341,13 @@ module ActiveRecord
341
341
 
342
342
  if start || finish
343
343
  records = records.filter do |record|
344
- (start.nil? || record.id >= start) && (finish.nil? || record.id <= finish)
344
+ id = record.id
345
+
346
+ if order == :asc
347
+ (start.nil? || id >= start) && (finish.nil? || id <= finish)
348
+ else
349
+ (start.nil? || id <= start) && (finish.nil? || id >= finish)
350
+ end
345
351
  end
346
352
  end
347
353
 
@@ -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
@@ -57,9 +57,15 @@ module ActiveRecord
57
57
  end
58
58
 
59
59
  def convert_to_id(value)
60
- return primary_key.map { |pk| value.public_send(pk) } if primary_key.is_a?(Array)
61
-
62
- if value.respond_to?(primary_key)
60
+ if primary_key.is_a?(Array)
61
+ primary_key.map do |attribute|
62
+ if attribute == "id"
63
+ value.id_value
64
+ else
65
+ value.public_send(attribute)
66
+ end
67
+ end
68
+ elsif value.respond_to?(primary_key)
63
69
  value.public_send(primary_key)
64
70
  else
65
71
  value