activerecord 7.1.0 → 7.1.3

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +194 -0
  3. data/README.rdoc +1 -0
  4. data/lib/active_record/associations/association.rb +2 -1
  5. data/lib/active_record/associations/preloader/association.rb +4 -1
  6. data/lib/active_record/associations.rb +15 -15
  7. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  8. data/lib/active_record/attribute_methods/dirty.rb +14 -10
  9. data/lib/active_record/attribute_methods.rb +1 -1
  10. data/lib/active_record/callbacks.rb +2 -2
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +11 -8
  12. data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -3
  13. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  14. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1 -3
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +13 -4
  16. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
  17. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -2
  18. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
  19. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  20. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  21. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +14 -6
  22. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -32
  23. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
  24. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
  25. data/lib/active_record/connection_adapters/trilogy_adapter.rb +9 -1
  26. data/lib/active_record/connection_handling.rb +1 -1
  27. data/lib/active_record/core.rb +62 -28
  28. data/lib/active_record/delegated_type.rb +1 -1
  29. data/lib/active_record/encryption/encryptable_record.rb +7 -1
  30. data/lib/active_record/encryption/encrypted_attribute_type.rb +4 -0
  31. data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
  32. data/lib/active_record/enum.rb +6 -9
  33. data/lib/active_record/errors.rb +5 -4
  34. data/lib/active_record/fixtures.rb +16 -0
  35. data/lib/active_record/future_result.rb +1 -0
  36. data/lib/active_record/gem_version.rb +1 -1
  37. data/lib/active_record/insert_all.rb +3 -3
  38. data/lib/active_record/internal_metadata.rb +3 -1
  39. data/lib/active_record/middleware/database_selector.rb +1 -1
  40. data/lib/active_record/migration/command_recorder.rb +4 -1
  41. data/lib/active_record/migration/compatibility.rb +8 -0
  42. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  43. data/lib/active_record/migration.rb +9 -5
  44. data/lib/active_record/model_schema.rb +5 -5
  45. data/lib/active_record/nested_attributes.rb +7 -9
  46. data/lib/active_record/normalization.rb +8 -0
  47. data/lib/active_record/persistence.rb +4 -3
  48. data/lib/active_record/promise.rb +1 -1
  49. data/lib/active_record/railtie.rb +1 -1
  50. data/lib/active_record/railties/controller_runtime.rb +2 -1
  51. data/lib/active_record/railties/databases.rake +5 -5
  52. data/lib/active_record/reflection.rb +13 -1
  53. data/lib/active_record/relation/calculations.rb +44 -9
  54. data/lib/active_record/relation/delegation.rb +1 -1
  55. data/lib/active_record/relation/query_methods.rb +1 -1
  56. data/lib/active_record/relation.rb +18 -3
  57. data/lib/active_record/runtime_registry.rb +15 -1
  58. data/lib/active_record/schema_migration.rb +1 -1
  59. data/lib/active_record/secure_token.rb +1 -1
  60. data/lib/active_record/tasks/database_tasks.rb +5 -5
  61. data/lib/active_record/timestamp.rb +1 -1
  62. data/lib/arel/nodes/homogeneous_in.rb +1 -1
  63. metadata +10 -9
@@ -268,6 +268,8 @@ module ActiveRecord
268
268
  # name: Reginald the Pirate
269
269
  # monkey_id: 1
270
270
  #
271
+ # <code></code>
272
+ #
271
273
  # ### in monkeys.yml
272
274
  #
273
275
  # george:
@@ -285,6 +287,8 @@ module ActiveRecord
285
287
  # name: Reginald the Pirate
286
288
  # monkey: george
287
289
  #
290
+ # <code></code>
291
+ #
288
292
  # ### in monkeys.yml
289
293
  #
290
294
  # george:
@@ -306,6 +310,8 @@ module ActiveRecord
306
310
  #
307
311
  # belongs_to :eater, polymorphic: true
308
312
  #
313
+ # <code></code>
314
+ #
309
315
  # ### in fruits.yml
310
316
  #
311
317
  # apple:
@@ -331,6 +337,8 @@ module ActiveRecord
331
337
  # id: 1
332
338
  # name: George the Monkey
333
339
  #
340
+ # <code></code>
341
+ #
334
342
  # ### in fruits.yml
335
343
  #
336
344
  # apple:
@@ -345,6 +353,8 @@ module ActiveRecord
345
353
  # id: 3
346
354
  # name: grape
347
355
  #
356
+ # <code></code>
357
+ #
348
358
  # ### in fruits_monkeys.yml
349
359
  #
350
360
  # apple_george:
@@ -368,6 +378,8 @@ module ActiveRecord
368
378
  # name: George the Monkey
369
379
  # fruits: apple, orange, grape
370
380
  #
381
+ # <code></code>
382
+ #
371
383
  # ### in fruits.yml
372
384
  #
373
385
  # apple:
@@ -467,6 +479,8 @@ module ActiveRecord
467
479
  # belongs_to :author
468
480
  # end
469
481
  #
482
+ # <code></code>
483
+ #
470
484
  # # books.yml
471
485
  # alices_adventure_in_wonderland:
472
486
  # author_id: <%= ActiveRecord::FixtureSet.identify(:lewis_carroll) %>
@@ -482,6 +496,8 @@ module ActiveRecord
482
496
  # belongs_to :book, query_constraints: [:author_id, :book_id]
483
497
  # end
484
498
  #
499
+ # <code></code>
500
+ #
485
501
  # # book_orders.yml
486
502
  # alices_adventure_in_wonderland_in_books:
487
503
  # author: lewis_carroll
@@ -48,6 +48,7 @@ module ActiveRecord
48
48
  Canceled = Class.new(ActiveRecordError)
49
49
 
50
50
  delegate :empty?, :to_a, to: :result
51
+ delegate_missing_to :result
51
52
 
52
53
  attr_reader :lock_wait
53
54
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 1
12
- TINY = 0
12
+ TINY = 3
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -23,8 +23,6 @@ module ActiveRecord
23
23
  @keys = @inserts.first.keys
24
24
  end
25
25
 
26
- configure_on_duplicate_update_logic
27
-
28
26
  if model.scope_attributes?
29
27
  @scope_attributes = model.scope_attributes
30
28
  @keys |= @scope_attributes.keys
@@ -35,8 +33,8 @@ module ActiveRecord
35
33
  @returning = false if @returning == []
36
34
 
37
35
  @unique_by = find_unique_index_for(@unique_by)
38
- @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
39
36
 
37
+ configure_on_duplicate_update_logic
40
38
  ensure_valid_options_for_connection!
41
39
  end
42
40
 
@@ -135,6 +133,8 @@ module ActiveRecord
135
133
  elsif custom_update_sql_provided?
136
134
  @update_sql = on_duplicate
137
135
  @on_duplicate = :update
136
+ elsif @on_duplicate == :update && updatable_columns.empty?
137
+ @on_duplicate = :skip
138
138
  end
139
139
  end
140
140
 
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  # This is enabled by default. To disable this functionality set
11
11
  # `use_metadata_table` to false in your database configuration.
12
12
  class InternalMetadata # :nodoc:
13
- class NullInternalMetadata
13
+ class NullInternalMetadata # :nodoc:
14
14
  end
15
15
 
16
16
  attr_reader :connection, :arel_table
@@ -64,6 +64,8 @@ module ActiveRecord
64
64
  end
65
65
 
66
66
  def create_table_and_set_flags(environment, schema_sha1 = nil)
67
+ return unless enabled?
68
+
67
69
  create_table
68
70
  update_or_create_entry(:environment, environment)
69
71
  update_or_create_entry(:schema_sha1, schema_sha1) if schema_sha1
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  # To use the DatabaseSelector in your application with default settings,
25
25
  # run the provided generator.
26
26
  #
27
- # bin/rails g active_record:multi_db
27
+ # $ bin/rails g active_record:multi_db
28
28
  #
29
29
  # This will create a file named +config/initializers/multi_db.rb+ with the
30
30
  # following contents:
@@ -206,7 +206,10 @@ module ActiveRecord
206
206
  end
207
207
 
208
208
  def invert_rename_table(args)
209
- [:rename_table, args.reverse]
209
+ old_name, new_name, options = args
210
+ args = [new_name, old_name]
211
+ args << options if options
212
+ [:rename_table, args]
210
213
  end
211
214
 
212
215
  def invert_remove_column(args)
@@ -61,8 +61,10 @@ module ActiveRecord
61
61
  column_name.is_a?(String) && /\W/.match?(column_name)
62
62
  end
63
63
  end
64
+
64
65
  module TableDefinition
65
66
  include LegacyIndexName
67
+
66
68
  def column(name, type, **options)
67
69
  options[:_skip_validate_options] = true
68
70
  super
@@ -95,6 +97,12 @@ module ActiveRecord
95
97
  super
96
98
  end
97
99
 
100
+ def add_reference(table_name, ref_name, **options)
101
+ options[:_skip_validate_options] = true
102
+ super
103
+ end
104
+ alias :add_belongs_to :add_reference
105
+
98
106
  def create_table(table_name, **options)
99
107
  options[:_uses_legacy_table_name] = true
100
108
  options[:_skip_validate_options] = true
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class PendingMigrationConnection # :nodoc:
5
+ def self.establish_temporary_connection(db_config, &block)
6
+ pool = ActiveRecord::Base.connection_handler.establish_connection(db_config, owner_name: self)
7
+
8
+ yield pool.connection
9
+ ensure
10
+ ActiveRecord::Base.connection_handler.remove_connection_pool(self.name)
11
+ end
12
+
13
+ def self.primary_class?
14
+ false
15
+ end
16
+
17
+ def self.current_preventing_writes
18
+ false
19
+ end
20
+ end
21
+ end
@@ -7,6 +7,7 @@ require "active_support/core_ext/array/access"
7
7
  require "active_support/core_ext/enumerable"
8
8
  require "active_support/core_ext/module/attribute_accessors"
9
9
  require "active_support/actionable_error"
10
+ require "active_record/migration/pending_migration_connection"
10
11
 
11
12
  module ActiveRecord
12
13
  class MigrationError < ActiveRecordError # :nodoc:
@@ -370,7 +371,8 @@ module ActiveRecord
370
371
  # The \Rails package has several tools to help create and apply migrations.
371
372
  #
372
373
  # To generate a new migration, you can use
373
- # bin/rails generate migration MyNewMigration
374
+ #
375
+ # $ bin/rails generate migration MyNewMigration
374
376
  #
375
377
  # where MyNewMigration is the name of your migration. The generator will
376
378
  # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
@@ -379,7 +381,7 @@ module ActiveRecord
379
381
  #
380
382
  # There is a special syntactic shortcut to generate migrations that add fields to a table.
381
383
  #
382
- # bin/rails generate migration add_fieldname_to_tablename fieldname:string
384
+ # $ bin/rails generate migration add_fieldname_to_tablename fieldname:string
383
385
  #
384
386
  # This will generate the file <tt>timestamp_add_fieldname_to_tablename.rb</tt>, which will look like this:
385
387
  # class AddFieldnameToTablename < ActiveRecord::Migration[7.1]
@@ -768,9 +770,11 @@ module ActiveRecord
768
770
  def pending_migrations
769
771
  pending_migrations = []
770
772
 
771
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection_for_each(env: env) do |connection|
772
- if pending = connection.migration_context.open.pending_migrations
773
- pending_migrations << pending
773
+ ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
774
+ ActiveRecord::PendingMigrationConnection.establish_temporary_connection(db_config) do |conn|
775
+ if pending = conn.migration_context.open.pending_migrations
776
+ pending_migrations << pending
777
+ end
774
778
  end
775
779
  end
776
780
 
@@ -284,8 +284,10 @@ module ActiveRecord
284
284
 
285
285
  # Computes the table name, (re)sets it internally, and returns it.
286
286
  def reset_table_name # :nodoc:
287
- self.table_name = if abstract_class?
288
- superclass == Base ? nil : superclass.table_name
287
+ self.table_name = if self == Base
288
+ nil
289
+ elsif abstract_class?
290
+ superclass.table_name
289
291
  elsif superclass.abstract_class?
290
292
  superclass.table_name || compute_table_name
291
293
  else
@@ -467,7 +469,7 @@ module ActiveRecord
467
469
  end
468
470
 
469
471
  # Returns the column object for the named attribute.
470
- # Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
472
+ # Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
471
473
  # named attribute does not exist.
472
474
  #
473
475
  # class Person < ActiveRecord::Base
@@ -627,8 +629,6 @@ module ActiveRecord
627
629
  )
628
630
  alias_attribute :id_value, :id if name == "id"
629
631
  end
630
-
631
- super
632
632
  end
633
633
 
634
634
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -283,13 +283,11 @@ module ActiveRecord
283
283
  #
284
284
  # === Creating forms with nested attributes
285
285
  #
286
- # Use ActionView::Helpers::FormHelper#fields_for to create form elements
287
- # for updating or destroying nested attributes.
286
+ # Use ActionView::Helpers::FormHelper#fields_for to create form elements for
287
+ # nested attributes.
288
288
  #
289
- # === Testing
290
- #
291
- # If you are using ActionView::Helpers::FormHelper#fields_for, your integration
292
- # tests should replicate the HTML structure it provides. For example;
289
+ # Integration test params should reflect the structure of the form. For
290
+ # example:
293
291
  #
294
292
  # post members_path, params: {
295
293
  # member: {
@@ -309,7 +307,7 @@ module ActiveRecord
309
307
  # [:allow_destroy]
310
308
  # If true, destroys any members from the attributes hash with a
311
309
  # <tt>_destroy</tt> key and a value that evaluates to +true+
312
- # (e.g. 1, '1', true, or 'true'). This option is off by default.
310
+ # (e.g. 1, '1', true, or 'true'). This option is false by default.
313
311
  # [:reject_if]
314
312
  # Allows you to specify a Proc or a Symbol pointing to a method
315
313
  # that checks whether a record should be built for a certain attribute
@@ -334,11 +332,11 @@ module ActiveRecord
334
332
  # nested attributes are going to be used when an associated record already
335
333
  # exists. In general, an existing record may either be updated with the
336
334
  # new set of attribute values or be replaced by a wholly new record
337
- # containing those values. By default the +:update_only+ option is +false+
335
+ # containing those values. By default the +:update_only+ option is false
338
336
  # and the nested attributes are used to update the existing record only
339
337
  # if they include the record's <tt>:id</tt> value. Otherwise a new
340
338
  # record will be instantiated and used to replace the existing one.
341
- # However if the +:update_only+ option is +true+, the nested attributes
339
+ # However if the +:update_only+ option is true, the nested attributes
342
340
  # are used to update the record's attributes always, regardless of
343
341
  # whether the <tt>:id</tt> is present. The option is ignored for collection
344
342
  # associations.
@@ -49,6 +49,14 @@ module ActiveRecord # :nodoc:
49
49
  # By default, the normalization will not be applied to +nil+ values. This
50
50
  # behavior can be changed with the +:apply_to_nil+ option.
51
51
  #
52
+ # Be aware that if your app was created before Rails 7.1, and your app
53
+ # marshals instances of the targeted model (for example, when caching),
54
+ # then you should set ActiveRecord.marshalling_format_version to +7.1+ or
55
+ # higher via either <tt>config.load_defaults 7.1</tt> or
56
+ # <tt>config.active_record.marshalling_format_version = 7.1</tt>.
57
+ # Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
58
+ # and raise +TypeError+.
59
+ #
52
60
  # ==== Options
53
61
  #
54
62
  # * +:with+ - Any callable object that accepts the attribute's value as
@@ -115,7 +115,7 @@ module ActiveRecord
115
115
  # ==== Options
116
116
  #
117
117
  # [:returning]
118
- # (PostgreSQL only) An array of attributes to return for all successfully
118
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
119
119
  # inserted records, which by default is the primary key.
120
120
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
121
121
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
@@ -205,7 +205,7 @@ module ActiveRecord
205
205
  # ==== Options
206
206
  #
207
207
  # [:returning]
208
- # (PostgreSQL only) An array of attributes to return for all successfully
208
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
209
209
  # inserted records, which by default is the primary key.
210
210
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
211
211
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
@@ -271,7 +271,7 @@ module ActiveRecord
271
271
  # ==== Options
272
272
  #
273
273
  # [:returning]
274
- # (PostgreSQL only) An array of attributes to return for all successfully
274
+ # (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
275
275
  # inserted records, which by default is the primary key.
276
276
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
277
277
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
@@ -1076,6 +1076,7 @@ module ActiveRecord
1076
1076
  end
1077
1077
 
1078
1078
  @association_cache = fresh_object.instance_variable_get(:@association_cache)
1079
+ @association_cache.each_value { |association| association.owner = self }
1079
1080
  @attributes = fresh_object.instance_variable_get(:@attributes)
1080
1081
  @new_record = false
1081
1082
  @previously_new_record = false
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  # Returns a new +ActiveRecord::Promise+ that will apply the passed block
32
32
  # when the value is accessed:
33
33
  #
34
- # Post.async_pluck(:title).then { |title| title.upcase }.value
34
+ # Post.async_pick(:title).then { |title| title.upcase }.value
35
35
  # # => "POST TITLE"
36
36
  def then(&block)
37
37
  Promise.new(@future_result, @block ? @block >> block : block)
@@ -398,7 +398,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
398
398
  end
399
399
 
400
400
  ActiveSupport.on_load(:active_record_fixture_set) do
401
- # Encrypt active record fixtures
401
+ # Encrypt Active Record fixtures
402
402
  if ActiveRecord::Encryption.config.encrypt_fixtures
403
403
  ActiveRecord::Fixture.prepend ActiveRecord::Encryption::EncryptedFixtures
404
404
  end
@@ -37,9 +37,10 @@ module ActiveRecord
37
37
  db_rt_before_render = ActiveRecord::RuntimeRegistry.reset
38
38
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
39
39
  runtime = super
40
+ queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
40
41
  db_rt_after_render = ActiveRecord::RuntimeRegistry.reset
41
42
  self.db_runtime += db_rt_after_render
42
- runtime - db_rt_after_render
43
+ runtime - queries_rt
43
44
  else
44
45
  super
45
46
  end
@@ -195,7 +195,7 @@ db_namespace = namespace :db do
195
195
 
196
196
  namespace :up do
197
197
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
198
- desc 'Run the "up" on #{name} database for a given migration VERSION.'
198
+ desc "Run the \"up\" on #{name} database for a given migration VERSION."
199
199
  task name => :load_config do
200
200
  raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
201
201
 
@@ -204,7 +204,7 @@ db_namespace = namespace :db do
204
204
  conn.migration_context.run(:up, ActiveRecord::Tasks::DatabaseTasks.target_version)
205
205
  end
206
206
 
207
- db_namespace["_dump"].invoke
207
+ db_namespace["_dump:#{name}"].invoke
208
208
  end
209
209
  end
210
210
  end
@@ -226,7 +226,7 @@ db_namespace = namespace :db do
226
226
 
227
227
  namespace :down do
228
228
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
229
- desc 'Run the "down" on #{name} database for a given migration VERSION.'
229
+ desc "Run the \"down\" on #{name} database for a given migration VERSION."
230
230
  task name => :load_config do
231
231
  raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
232
232
 
@@ -235,7 +235,7 @@ db_namespace = namespace :db do
235
235
  conn.migration_context.run(:down, ActiveRecord::Tasks::DatabaseTasks.target_version)
236
236
  end
237
237
 
238
- db_namespace["_dump"].invoke
238
+ db_namespace["_dump:#{name}"].invoke
239
239
  end
240
240
  end
241
241
  end
@@ -269,7 +269,7 @@ db_namespace = namespace :db do
269
269
  conn.migration_context.rollback(step)
270
270
  end
271
271
 
272
- db_namespace["_dump"].invoke
272
+ db_namespace["_dump:#{name}"].invoke
273
273
  end
274
274
  end
275
275
  end
@@ -382,6 +382,7 @@ module ActiveRecord
382
382
  @klass = options[:anonymous_class]
383
383
  @plural_name = active_record.pluralize_table_names ?
384
384
  name.to_s.pluralize : name.to_s
385
+ validate_reflection!
385
386
  end
386
387
 
387
388
  def autosave=(autosave)
@@ -433,6 +434,17 @@ module ActiveRecord
433
434
  def derive_class_name
434
435
  name.to_s.camelize
435
436
  end
437
+
438
+ def validate_reflection!
439
+ return unless options[:foreign_key].is_a?(Array)
440
+
441
+ message = <<~MSG.squish
442
+ Passing #{options[:foreign_key]} array to :foreign_key option
443
+ on the #{active_record}##{name} association is not supported.
444
+ Use the query_constraints: #{options[:foreign_key]} option instead to represent a composite foreign key.
445
+ MSG
446
+ raise ArgumentError, message
447
+ end
436
448
  end
437
449
 
438
450
  # Holds all the metadata about an aggregation as it was specified in the
@@ -858,7 +870,7 @@ module ActiveRecord
858
870
  def association_primary_key(klass = nil)
859
871
  if primary_key = options[:primary_key]
860
872
  @association_primary_key ||= -primary_key.to_s
861
- elsif !polymorphic? && ((klass || self.klass).has_query_constraints? || options[:query_constraints])
873
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
862
874
  (klass || self.klass).composite_query_constraints_list
863
875
  elsif (klass || self.klass).composite_primary_key?
864
876
  # If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
@@ -81,6 +81,16 @@ module ActiveRecord
81
81
  #
82
82
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
83
83
  # between databases. In invalid cases, an error from the database is thrown.
84
+ #
85
+ # When given a block, loads all records in the relation, if the relation
86
+ # hasn't been loaded yet. Calls the block with each record in the relation.
87
+ # Returns the number of records for which the block returns a truthy value.
88
+ #
89
+ # Person.count { |person| person.age > 21 }
90
+ # # => counts the number of people older that 21
91
+ #
92
+ # Note: If there are a lot of records in the relation, loading all records
93
+ # could result in performance issues.
84
94
  def count(column_name = nil)
85
95
  if block_given?
86
96
  unless column_name.nil?
@@ -93,7 +103,8 @@ module ActiveRecord
93
103
  end
94
104
  end
95
105
 
96
- # Same as <tt>#count</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
106
+ # Same as #count, but performs the query asynchronously and returns an
107
+ # ActiveRecord::Promise.
97
108
  def async_count(column_name = nil)
98
109
  async.count(column_name)
99
110
  end
@@ -106,7 +117,8 @@ module ActiveRecord
106
117
  calculate(:average, column_name)
107
118
  end
108
119
 
109
- # Same as <tt>#average</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
120
+ # Same as #average, but performs the query asynchronously and returns an
121
+ # ActiveRecord::Promise.
110
122
  def async_average(column_name)
111
123
  async.average(column_name)
112
124
  end
@@ -120,7 +132,8 @@ module ActiveRecord
120
132
  calculate(:minimum, column_name)
121
133
  end
122
134
 
123
- # Same as <tt>#minimum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
135
+ # Same as #minimum, but performs the query asynchronously and returns an
136
+ # ActiveRecord::Promise.
124
137
  def async_minimum(column_name)
125
138
  async.minimum(column_name)
126
139
  end
@@ -134,7 +147,8 @@ module ActiveRecord
134
147
  calculate(:maximum, column_name)
135
148
  end
136
149
 
137
- # Same as <tt>#maximum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
150
+ # Same as #maximum, but performs the query asynchronously and returns an
151
+ # ActiveRecord::Promise.
138
152
  def async_maximum(column_name)
139
153
  async.maximum(column_name)
140
154
  end
@@ -144,6 +158,17 @@ module ActiveRecord
144
158
  # #calculate for examples with options.
145
159
  #
146
160
  # Person.sum(:age) # => 4562
161
+ #
162
+ # When given a block, loads all records in the relation, if the relation
163
+ # hasn't been loaded yet. Calls the block with each record in the relation.
164
+ # Returns the sum of +initial_value_or_column+ and the block return
165
+ # values:
166
+ #
167
+ # Person.sum { |person| person.age } # => 4562
168
+ # Person.sum(1000) { |person| person.age } # => 5562
169
+ #
170
+ # Note: If there are a lot of records in the relation, loading all records
171
+ # could result in performance issues.
147
172
  def sum(initial_value_or_column = 0, &block)
148
173
  if block_given?
149
174
  map(&block).sum(initial_value_or_column)
@@ -152,7 +177,8 @@ module ActiveRecord
152
177
  end
153
178
  end
154
179
 
155
- # Same as <tt>#sum</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
180
+ # Same as #sum, but performs the query asynchronously and returns an
181
+ # ActiveRecord::Promise.
156
182
  def async_sum(identity_or_column = nil)
157
183
  async.sum(identity_or_column)
158
184
  end
@@ -255,7 +281,13 @@ module ActiveRecord
255
281
  #
256
282
  # See also #ids.
257
283
  def pluck(*column_names)
258
- return [] if @none
284
+ if @none
285
+ if @async
286
+ return Promise::Complete.new([])
287
+ else
288
+ return []
289
+ end
290
+ end
259
291
 
260
292
  if loaded? && all_attributes?(column_names)
261
293
  result = records.pluck(*column_names)
@@ -287,7 +319,8 @@ module ActiveRecord
287
319
  end
288
320
  end
289
321
 
290
- # Same as <tt>#pluck</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
322
+ # Same as #pluck, but performs the query asynchronously and returns an
323
+ # ActiveRecord::Promise.
291
324
  def async_pluck(*column_names)
292
325
  async.pluck(*column_names)
293
326
  end
@@ -315,7 +348,8 @@ module ActiveRecord
315
348
  limit(1).pluck(*column_names).then(&:first)
316
349
  end
317
350
 
318
- # Same as <tt>#pick</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
351
+ # Same as #pick, but performs the query asynchronously and returns an
352
+ # ActiveRecord::Promise.
319
353
  def async_pick(*column_names)
320
354
  async.pick(*column_names)
321
355
  end
@@ -358,7 +392,8 @@ module ActiveRecord
358
392
  result.then { |result| type_cast_pluck_values(result, columns) }
359
393
  end
360
394
 
361
- # Same as <tt>#ids</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
395
+ # Same as #ids, but performs the query asynchronously and returns an
396
+ # ActiveRecord::Promise.
362
397
  def async_ids
363
398
  async.ids
364
399
  end
@@ -102,7 +102,7 @@ module ActiveRecord
102
102
  :to_sentence, :to_fs, :to_formatted_s, :as_json,
103
103
  :shuffle, :split, :slice, :index, :rindex, to: :records
104
104
 
105
- delegate :primary_key, :connection, to: :klass
105
+ delegate :primary_key, :connection, :transaction, to: :klass
106
106
 
107
107
  module ClassSpecificRelation # :nodoc:
108
108
  extend ActiveSupport::Concern
@@ -588,7 +588,7 @@ module ActiveRecord
588
588
  # User.order(Arel.sql('end_date - start_date'))
589
589
  # # SELECT "users".* FROM "users" ORDER BY end_date - start_date
590
590
  #
591
- # Custom query syntax, like JSON columns for Postgres, is supported in this way.
591
+ # Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
592
592
  #
593
593
  # User.order(Arel.sql("payload->>'kind'"))
594
594
  # # SELECT "users".* FROM "users" ORDER BY payload->>'kind'