activerecord 7.1.0 → 7.1.3

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