activerecord 7.1.1 → 7.1.5

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.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +496 -0
  3. data/README.rdoc +1 -0
  4. data/lib/active_record/associations/association.rb +2 -1
  5. data/lib/active_record/associations/belongs_to_association.rb +4 -4
  6. data/lib/active_record/associations/collection_association.rb +4 -4
  7. data/lib/active_record/associations/has_many_association.rb +2 -2
  8. data/lib/active_record/associations/has_one_association.rb +2 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +10 -12
  11. data/lib/active_record/associations/preloader/association.rb +4 -1
  12. data/lib/active_record/associations.rb +21 -15
  13. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  14. data/lib/active_record/attribute_methods/dirty.rb +15 -11
  15. data/lib/active_record/attribute_methods/read.rb +3 -3
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attribute_methods/write.rb +3 -3
  18. data/lib/active_record/attribute_methods.rb +47 -7
  19. data/lib/active_record/autosave_association.rb +5 -2
  20. data/lib/active_record/callbacks.rb +2 -2
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +19 -9
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -3
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -4
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -19
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +37 -13
  27. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -1
  28. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
  29. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -10
  30. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
  31. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  33. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -6
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -33
  35. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -2
  37. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
  38. data/lib/active_record/connection_adapters/trilogy_adapter.rb +25 -21
  39. data/lib/active_record/connection_handling.rb +1 -1
  40. data/lib/active_record/core.rb +49 -10
  41. data/lib/active_record/counter_cache.rb +7 -3
  42. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  43. data/lib/active_record/database_configurations/hash_config.rb +6 -2
  44. data/lib/active_record/delegated_type.rb +7 -7
  45. data/lib/active_record/destroy_association_async_job.rb +1 -1
  46. data/lib/active_record/encryption/encryptable_record.rb +7 -1
  47. data/lib/active_record/encryption/encrypted_attribute_type.rb +6 -2
  48. data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
  49. data/lib/active_record/encryption/scheme.rb +8 -4
  50. data/lib/active_record/encryption.rb +2 -0
  51. data/lib/active_record/enum.rb +6 -9
  52. data/lib/active_record/errors.rb +5 -4
  53. data/lib/active_record/fixtures.rb +16 -0
  54. data/lib/active_record/future_result.rb +10 -0
  55. data/lib/active_record/gem_version.rb +1 -1
  56. data/lib/active_record/insert_all.rb +3 -3
  57. data/lib/active_record/internal_metadata.rb +1 -1
  58. data/lib/active_record/locking/optimistic.rb +1 -1
  59. data/lib/active_record/marshalling.rb +4 -1
  60. data/lib/active_record/message_pack.rb +1 -1
  61. data/lib/active_record/middleware/database_selector.rb +1 -1
  62. data/lib/active_record/migration/compatibility.rb +14 -0
  63. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  64. data/lib/active_record/migration.rb +9 -5
  65. data/lib/active_record/model_schema.rb +12 -7
  66. data/lib/active_record/nested_attributes.rb +16 -5
  67. data/lib/active_record/normalization.rb +8 -0
  68. data/lib/active_record/persistence.rb +6 -5
  69. data/lib/active_record/promise.rb +1 -1
  70. data/lib/active_record/query_cache.rb +1 -1
  71. data/lib/active_record/query_logs_formatter.rb +1 -1
  72. data/lib/active_record/railtie.rb +14 -14
  73. data/lib/active_record/railties/controller_runtime.rb +2 -1
  74. data/lib/active_record/railties/databases.rake +7 -7
  75. data/lib/active_record/reflection.rb +21 -3
  76. data/lib/active_record/relation/calculations.rb +28 -1
  77. data/lib/active_record/relation/delegation.rb +1 -1
  78. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  79. data/lib/active_record/relation/query_methods.rb +21 -7
  80. data/lib/active_record/relation.rb +30 -5
  81. data/lib/active_record/result.rb +1 -1
  82. data/lib/active_record/runtime_registry.rb +15 -1
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/secure_token.rb +1 -1
  85. data/lib/active_record/tasks/database_tasks.rb +35 -13
  86. data/lib/active_record/test_fixtures.rb +1 -0
  87. data/lib/active_record/timestamp.rb +3 -1
  88. data/lib/active_record.rb +2 -2
  89. data/lib/arel/nodes/homogeneous_in.rb +1 -1
  90. data/lib/arel/tree_manager.rb +5 -1
  91. data/lib/arel/visitors/to_sql.rb +2 -1
  92. metadata +14 -13
@@ -307,7 +307,7 @@ module ActiveRecord
307
307
  # [:allow_destroy]
308
308
  # If true, destroys any members from the attributes hash with a
309
309
  # <tt>_destroy</tt> key and a value that evaluates to +true+
310
- # (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.
311
311
  # [:reject_if]
312
312
  # Allows you to specify a Proc or a Symbol pointing to a method
313
313
  # that checks whether a record should be built for a certain attribute
@@ -332,11 +332,11 @@ module ActiveRecord
332
332
  # nested attributes are going to be used when an associated record already
333
333
  # exists. In general, an existing record may either be updated with the
334
334
  # new set of attribute values or be replaced by a wholly new record
335
- # containing those values. By default the +:update_only+ option is +false+
335
+ # containing those values. By default the +:update_only+ option is false
336
336
  # and the nested attributes are used to update the existing record only
337
337
  # if they include the record's <tt>:id</tt> value. Otherwise a new
338
338
  # record will be instantiated and used to replace the existing one.
339
- # However if the +:update_only+ option is +true+, the nested attributes
339
+ # However if the +:update_only+ option is true, the nested attributes
340
340
  # are used to update the record's attributes always, regardless of
341
341
  # whether the <tt>:id</tt> is present. The option is ignored for collection
342
342
  # associations.
@@ -519,12 +519,12 @@ module ActiveRecord
519
519
  unless reject_new_record?(association_name, attributes)
520
520
  association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
521
521
  end
522
- elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
522
+ elsif existing_record = find_record_by_id(existing_records, attributes["id"])
523
523
  unless call_reject_if(association_name, attributes)
524
524
  # Make sure we are operating on the actual object which is in the association's
525
525
  # proxy_target array (either by finding it, or adding it if not found)
526
526
  # Take into account that the proxy_target may have changed due to callbacks
527
- target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
527
+ target_record = find_record_by_id(association.target, attributes["id"])
528
528
  if target_record
529
529
  existing_record = target_record
530
530
  else
@@ -612,5 +612,16 @@ module ActiveRecord
612
612
  raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
613
613
  model, "id", record_id)
614
614
  end
615
+
616
+ def find_record_by_id(records, id)
617
+ return if records.empty?
618
+
619
+ if records.first.class.composite_primary_key?
620
+ id = Array(id).map(&:to_s)
621
+ records.find { |record| Array(record.id).map(&:to_s) == id }
622
+ else
623
+ records.find { |record| record.id.to_s == id.to_s }
624
+ end
625
+ end
615
626
  end
616
627
  end
@@ -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
@@ -456,7 +456,7 @@ module ActiveRecord
456
456
  end
457
457
 
458
458
  # Accepts a list of attribute names to be used in the WHERE clause
459
- # of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for `#first` and `#last` finder methods.
459
+ # of SELECT / UPDATE / DELETE queries and in the ORDER BY clause for +#first+ and +#last+ finder methods.
460
460
  #
461
461
  # class Developer < ActiveRecord::Base
462
462
  # query_constraints :company_id, :id
@@ -469,7 +469,7 @@ module ActiveRecord
469
469
  # developer.update!(name: "Nikita")
470
470
  # # UPDATE "developers" SET "name" = 'Nikita' WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
471
471
  #
472
- # It is possible to update attribute used in the query_by clause:
472
+ # # It is possible to update an attribute used in the query_constraints clause:
473
473
  # developer.update!(company_id: 2)
474
474
  # # UPDATE "developers" SET "company_id" = 2 WHERE "developers"."company_id" = 1 AND "developers"."id" = 1
475
475
  #
@@ -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)
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  end
31
31
 
32
32
  def self.complete(pools)
33
- pools.each { |pool| pool.disable_query_cache! }
33
+ pools.each { |pool| pool.disable_query_cache! unless pool.discarded? }
34
34
 
35
35
  ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
36
36
  pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  end
29
29
 
30
30
  def format(pairs)
31
- pairs.sort_by!(&:first)
31
+ pairs.sort_by! { |pair| pair.first.to_s }
32
32
  super
33
33
  end
34
34
 
@@ -146,6 +146,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
146
146
  config.after_initialize do |app|
147
147
  ActiveSupport.on_load(:active_record) do
148
148
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).first
149
+ next if db_config.nil?
149
150
 
150
151
  filename = ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename(
151
152
  db_config.name,
@@ -261,8 +262,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
261
262
  end
262
263
 
263
264
  ActiveSupport.on_load(:active_record) do
264
- # Configs used in other initializers
265
- configs = configs.except(
265
+ configs_used_in_other_initializers = configs.except(
266
266
  :migration_error,
267
267
  :database_selector,
268
268
  :database_resolver,
@@ -279,7 +279,7 @@ To keep using the current cache store, you can turn off cache versioning entirel
279
279
  :use_schema_cache_dump
280
280
  )
281
281
 
282
- configs.each do |k, v|
282
+ configs_used_in_other_initializers.each do |k, v|
283
283
  next if k == :encryption
284
284
  setter = "#{k}="
285
285
  # Some existing initializers might rely on Active Record configuration
@@ -377,23 +377,23 @@ To keep using the current cache store, you can turn off cache versioning entirel
377
377
  end
378
378
 
379
379
  initializer "active_record_encryption.configuration" do |app|
380
- auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app)
381
-
382
- config.after_initialize do |app|
383
- ActiveRecord::Encryption.configure \
380
+ ActiveSupport.on_load(:active_record_encryption) do
381
+ ActiveRecord::Encryption.configure(
384
382
  primary_key: app.credentials.dig(:active_record_encryption, :primary_key),
385
383
  deterministic_key: app.credentials.dig(:active_record_encryption, :deterministic_key),
386
384
  key_derivation_salt: app.credentials.dig(:active_record_encryption, :key_derivation_salt),
387
- **config.active_record.encryption
385
+ **app.config.active_record.encryption
386
+ )
388
387
 
388
+ auto_filtered_parameters = ActiveRecord::Encryption::AutoFilteredParameters.new(app)
389
389
  auto_filtered_parameters.enable if ActiveRecord::Encryption.config.add_to_filter_parameters
390
+ end
390
391
 
391
- ActiveSupport.on_load(:active_record) do
392
- # Support extended queries for deterministic attributes and validations
393
- if ActiveRecord::Encryption.config.extend_queries
394
- ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
395
- ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
396
- end
392
+ ActiveSupport.on_load(:active_record) do
393
+ # Support extended queries for deterministic attributes and validations
394
+ if ActiveRecord::Encryption.config.extend_queries
395
+ ActiveRecord::Encryption::ExtendedDeterministicQueries.install_support
396
+ ActiveRecord::Encryption::ExtendedDeterministicUniquenessValidator.install_support
397
397
  end
398
398
  end
399
399
 
@@ -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
@@ -89,10 +89,10 @@ 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
- mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions(db_configs)
95
+ mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
96
96
 
97
97
  mapped_versions.sort.each do |version, db_configs|
98
98
  db_configs.each do |db_config|
@@ -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
@@ -774,7 +786,7 @@ module ActiveRecord
774
786
  primary_query_constraints = active_record.query_constraints_list
775
787
  owner_pk = active_record.primary_key
776
788
 
777
- if primary_query_constraints.size != 2
789
+ if primary_query_constraints.size > 2
778
790
  raise ArgumentError, <<~MSG.squish
779
791
  The query constraints list on the `#{active_record}` model has more than 2
780
792
  attributes. Active Record is unable to derive the query constraints
@@ -792,6 +804,8 @@ module ActiveRecord
792
804
  MSG
793
805
  end
794
806
 
807
+ return foreign_key if primary_query_constraints.include?(foreign_key)
808
+
795
809
  first_key, last_key = primary_query_constraints
796
810
 
797
811
  if first_key == owner_pk
@@ -857,8 +871,12 @@ module ActiveRecord
857
871
  # klass option is necessary to support loading polymorphic associations
858
872
  def association_primary_key(klass = nil)
859
873
  if primary_key = options[:primary_key]
860
- @association_primary_key ||= -primary_key.to_s
861
- elsif !polymorphic? && ((klass || self.klass).has_query_constraints? || options[:query_constraints])
874
+ @association_primary_key ||= if primary_key.is_a?(Array)
875
+ primary_key.map { |pk| pk.to_s.freeze }.freeze
876
+ else
877
+ -primary_key.to_s
878
+ end
879
+ elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
862
880
  (klass || self.klass).composite_query_constraints_list
863
881
  elsif (klass || self.klass).composite_primary_key?
864
882
  # 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?
@@ -148,6 +158,17 @@ module ActiveRecord
148
158
  # #calculate for examples with options.
149
159
  #
150
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.
151
172
  def sum(initial_value_or_column = 0, &block)
152
173
  if block_given?
153
174
  map(&block).sum(initial_value_or_column)
@@ -260,7 +281,13 @@ module ActiveRecord
260
281
  #
261
282
  # See also #ids.
262
283
  def pluck(*column_names)
263
- return [] if @none
284
+ if @none
285
+ if @async
286
+ return Promise::Complete.new([])
287
+ else
288
+ return []
289
+ end
290
+ end
264
291
 
265
292
  if loaded? && all_attributes?(column_names)
266
293
  result = records.pluck(*column_names)
@@ -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
@@ -43,7 +43,12 @@ module ActiveRecord
43
43
 
44
44
  def convert_to_id(value)
45
45
  if value.is_a?(Base)
46
- value._read_attribute(primary_key(value))
46
+ primary_key = primary_key(value)
47
+ if primary_key.is_a?(Array)
48
+ primary_key.map { |column| value._read_attribute(column) }
49
+ else
50
+ value._read_attribute(primary_key)
51
+ end
47
52
  elsif value.is_a?(Relation)
48
53
  value.select(primary_key(value))
49
54
  else
@@ -1909,18 +1909,32 @@ module ActiveRecord
1909
1909
  end
1910
1910
 
1911
1911
  def column_references(order_args)
1912
- references = order_args.flat_map do |arg|
1912
+ order_args.flat_map do |arg|
1913
1913
  case arg
1914
1914
  when String, Symbol
1915
- arg
1915
+ extract_table_name_from(arg)
1916
1916
  when Hash
1917
- arg.keys.map do |key|
1918
- key if key.is_a?(String) || key.is_a?(Symbol)
1917
+ arg
1918
+ .map do |key, value|
1919
+ case value
1920
+ when Hash
1921
+ key.to_s
1922
+ else
1923
+ extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
1924
+ end
1925
+ end
1926
+ when Arel::Attribute
1927
+ arg.relation.name
1928
+ when Arel::Nodes::Ordering
1929
+ if arg.expr.is_a?(Arel::Attribute)
1930
+ arg.expr.relation.name
1919
1931
  end
1920
1932
  end
1921
- end
1922
- references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
1923
- references
1933
+ end.compact
1934
+ end
1935
+
1936
+ def extract_table_name_from(string)
1937
+ string.match(/^\W?(\w+)\W?\./) && $1
1924
1938
  end
1925
1939
 
1926
1940
  def order_column(field)
@@ -164,8 +164,8 @@ module ActiveRecord
164
164
  #
165
165
  # If creation failed because of a unique constraint, this method will
166
166
  # assume it encountered a race condition and will try finding the record
167
- # once more If somehow the second find still find no record because a
168
- # concurrent DELETE happened, it will then raise an
167
+ # once more. If somehow the second find still does not find a record
168
+ # because a concurrent DELETE happened, it will then raise an
169
169
  # ActiveRecord::RecordNotFound exception.
170
170
  #
171
171
  # Please note <b>this method is not atomic</b>, it runs first a SELECT,
@@ -291,6 +291,11 @@ module ActiveRecord
291
291
  end
292
292
 
293
293
  # Returns true if there are no records.
294
+ #
295
+ # When a pattern argument is given, this method checks whether elements in
296
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
297
+ #
298
+ # posts.none?(Comment) # => true or false
294
299
  def none?(*args)
295
300
  return true if @none
296
301
 
@@ -299,6 +304,11 @@ module ActiveRecord
299
304
  end
300
305
 
301
306
  # Returns true if there are any records.
307
+ #
308
+ # When a pattern argument is given, this method checks whether elements in
309
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
310
+ #
311
+ # posts.any?(Post) # => true or false
302
312
  def any?(*args)
303
313
  return false if @none
304
314
 
@@ -307,6 +317,11 @@ module ActiveRecord
307
317
  end
308
318
 
309
319
  # Returns true if there is exactly one record.
320
+ #
321
+ # When a pattern argument is given, this method checks whether elements in
322
+ # the Enumerable match the pattern via the case-equality operator (<tt>===</tt>).
323
+ #
324
+ # posts.one?(Post) # => true or false
310
325
  def one?(*args)
311
326
  return false if @none
312
327
 
@@ -511,7 +526,12 @@ module ActiveRecord
511
526
 
512
527
  group_values_arel_columns = arel_columns(group_values.uniq)
513
528
  having_clause_ast = having_clause.ast unless having_clause.empty?
514
- stmt = arel.compile_update(values, table[primary_key], having_clause_ast, group_values_arel_columns)
529
+ key = if klass.composite_primary_key?
530
+ primary_key.map { |pk| table[pk] }
531
+ else
532
+ table[primary_key]
533
+ end
534
+ stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
515
535
  klass.connection.update(stmt, "#{klass} Update All").tap { reset }
516
536
  end
517
537
 
@@ -644,7 +664,12 @@ module ActiveRecord
644
664
 
645
665
  group_values_arel_columns = arel_columns(group_values.uniq)
646
666
  having_clause_ast = having_clause.ast unless having_clause.empty?
647
- stmt = arel.compile_delete(table[primary_key], having_clause_ast, group_values_arel_columns)
667
+ key = if klass.composite_primary_key?
668
+ primary_key.map { |pk| table[pk] }
669
+ else
670
+ table[primary_key]
671
+ end
672
+ stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
648
673
 
649
674
  klass.connection.delete(stmt, "#{klass} Delete All").tap { reset }
650
675
  end
@@ -960,7 +985,7 @@ module ActiveRecord
960
985
  def exec_main_query(async: false)
961
986
  if @none
962
987
  if async
963
- return Promise::Complete.new([])
988
+ return FutureResult.wrap([])
964
989
  else
965
990
  return []
966
991
  end
@@ -195,7 +195,7 @@ module ActiveRecord
195
195
  EMPTY = new([].freeze, [].freeze, {}.freeze).freeze
196
196
  private_constant :EMPTY
197
197
 
198
- EMPTY_ASYNC = FutureResult::Complete.new(EMPTY).freeze
198
+ EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
199
199
  private_constant :EMPTY_ASYNC
200
200
  end
201
201
  end
@@ -17,13 +17,27 @@ module ActiveRecord
17
17
  ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
18
  end
19
19
 
20
+ def async_sql_runtime
21
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
+ end
23
+
24
+ def async_sql_runtime=(runtime)
25
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
+ end
27
+
20
28
  def reset
21
29
  rt, self.sql_runtime = sql_runtime, 0.0
30
+ self.async_sql_runtime = 0.0
22
31
  rt
23
32
  end
24
33
  end
25
34
  end
26
35
 
27
36
  ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
28
- ActiveRecord::RuntimeRegistry.sql_runtime += (finish - start) * 1_000.0
37
+ runtime = (finish - start) * 1_000.0
38
+
39
+ if payload[:async]
40
+ ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
41
+ end
42
+ ActiveRecord::RuntimeRegistry.sql_runtime += runtime
29
43
  end
@@ -6,7 +6,7 @@ module ActiveRecord
6
6
  # number is inserted in to the schema migrations table so it doesn't need
7
7
  # to be executed the next time.
8
8
  class SchemaMigration # :nodoc:
9
- class NullSchemaMigration
9
+ class NullSchemaMigration # :nodoc:
10
10
  end
11
11
 
12
12
  attr_reader :connection, :arel_table
@@ -53,7 +53,7 @@ module ActiveRecord
53
53
  define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
54
54
  set_callback on, on == :initialize ? :after : :before do
55
55
  if new_record? && !query_attribute(attribute)
56
- write_attribute(attribute, self.class.generate_unique_secure_token(length: length))
56
+ send("#{attribute}=", self.class.generate_unique_secure_token(length: length))
57
57
  end
58
58
  end
59
59
  end