activerecord 7.2.0.beta2 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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