activerecord 8.0.0 → 8.1.2

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 (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +703 -248
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +16 -5
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +3 -3
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/errors.rb +3 -0
  17. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  18. data/lib/active_record/associations/join_dependency.rb +4 -2
  19. data/lib/active_record/associations/preloader/batch.rb +7 -1
  20. data/lib/active_record/associations/preloader/branch.rb +1 -0
  21. data/lib/active_record/associations.rb +159 -21
  22. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  23. data/lib/active_record/attribute_methods/query.rb +34 -0
  24. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  25. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  26. data/lib/active_record/attribute_methods.rb +23 -18
  27. data/lib/active_record/attributes.rb +40 -26
  28. data/lib/active_record/autosave_association.rb +22 -12
  29. data/lib/active_record/base.rb +3 -4
  30. data/lib/active_record/coders/json.rb +14 -5
  31. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
  32. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  34. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -108
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +31 -35
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -64
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -20
  45. data/lib/active_record/connection_adapters/column.rb +17 -4
  46. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  47. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  48. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  49. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +33 -4
  51. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
  53. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  54. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  55. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -17
  56. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  57. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  58. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  59. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  60. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
  61. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +67 -31
  62. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +82 -49
  63. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -10
  64. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
  66. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  67. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +14 -2
  68. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -12
  69. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +65 -36
  70. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  71. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  72. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -2
  73. data/lib/active_record/connection_adapters.rb +1 -0
  74. data/lib/active_record/connection_handling.rb +15 -10
  75. data/lib/active_record/core.rb +44 -12
  76. data/lib/active_record/counter_cache.rb +34 -9
  77. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  78. data/lib/active_record/database_configurations/database_config.rb +5 -1
  79. data/lib/active_record/database_configurations/hash_config.rb +59 -9
  80. data/lib/active_record/database_configurations/url_config.rb +13 -3
  81. data/lib/active_record/database_configurations.rb +7 -3
  82. data/lib/active_record/delegated_type.rb +19 -19
  83. data/lib/active_record/dynamic_matchers.rb +54 -69
  84. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  85. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  86. data/lib/active_record/encryption/encryptor.rb +39 -25
  87. data/lib/active_record/encryption/scheme.rb +1 -1
  88. data/lib/active_record/enum.rb +37 -20
  89. data/lib/active_record/errors.rb +23 -7
  90. data/lib/active_record/explain.rb +1 -1
  91. data/lib/active_record/explain_registry.rb +51 -2
  92. data/lib/active_record/filter_attribute_handler.rb +73 -0
  93. data/lib/active_record/fixture_set/table_row.rb +19 -2
  94. data/lib/active_record/fixtures.rb +2 -2
  95. data/lib/active_record/future_result.rb +3 -3
  96. data/lib/active_record/gem_version.rb +2 -2
  97. data/lib/active_record/inheritance.rb +1 -1
  98. data/lib/active_record/insert_all.rb +12 -7
  99. data/lib/active_record/locking/optimistic.rb +7 -0
  100. data/lib/active_record/locking/pessimistic.rb +5 -0
  101. data/lib/active_record/log_subscriber.rb +2 -6
  102. data/lib/active_record/middleware/shard_selector.rb +34 -17
  103. data/lib/active_record/migration/command_recorder.rb +19 -3
  104. data/lib/active_record/migration/compatibility.rb +34 -24
  105. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  106. data/lib/active_record/migration.rb +31 -21
  107. data/lib/active_record/model_schema.rb +36 -10
  108. data/lib/active_record/nested_attributes.rb +2 -0
  109. data/lib/active_record/persistence.rb +34 -3
  110. data/lib/active_record/query_cache.rb +22 -15
  111. data/lib/active_record/query_logs.rb +7 -7
  112. data/lib/active_record/querying.rb +4 -4
  113. data/lib/active_record/railtie.rb +35 -6
  114. data/lib/active_record/railties/controller_runtime.rb +11 -6
  115. data/lib/active_record/railties/databases.rake +24 -20
  116. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  117. data/lib/active_record/railties/job_runtime.rb +10 -11
  118. data/lib/active_record/reflection.rb +35 -0
  119. data/lib/active_record/relation/batches.rb +25 -11
  120. data/lib/active_record/relation/calculations.rb +54 -38
  121. data/lib/active_record/relation/delegation.rb +0 -1
  122. data/lib/active_record/relation/finder_methods.rb +42 -25
  123. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  124. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  125. data/lib/active_record/relation/predicate_builder.rb +9 -7
  126. data/lib/active_record/relation/query_attribute.rb +4 -2
  127. data/lib/active_record/relation/query_methods.rb +43 -32
  128. data/lib/active_record/relation/spawn_methods.rb +6 -6
  129. data/lib/active_record/relation/where_clause.rb +10 -11
  130. data/lib/active_record/relation.rb +43 -19
  131. data/lib/active_record/result.rb +44 -21
  132. data/lib/active_record/runtime_registry.rb +42 -58
  133. data/lib/active_record/sanitization.rb +2 -0
  134. data/lib/active_record/schema_dumper.rb +42 -22
  135. data/lib/active_record/scoping.rb +0 -1
  136. data/lib/active_record/secure_token.rb +3 -3
  137. data/lib/active_record/signed_id.rb +47 -18
  138. data/lib/active_record/statement_cache.rb +15 -11
  139. data/lib/active_record/store.rb +44 -19
  140. data/lib/active_record/structured_event_subscriber.rb +85 -0
  141. data/lib/active_record/table_metadata.rb +5 -20
  142. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  143. data/lib/active_record/tasks/database_tasks.rb +44 -45
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  147. data/lib/active_record/test_databases.rb +14 -4
  148. data/lib/active_record/test_fixtures.rb +27 -2
  149. data/lib/active_record/testing/query_assertions.rb +8 -2
  150. data/lib/active_record/timestamp.rb +4 -2
  151. data/lib/active_record/transaction.rb +2 -5
  152. data/lib/active_record/transactions.rb +39 -16
  153. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  154. data/lib/active_record/type/internal/timezone.rb +7 -0
  155. data/lib/active_record/type/json.rb +15 -2
  156. data/lib/active_record/type/serialized.rb +11 -4
  157. data/lib/active_record/type/type_map.rb +1 -1
  158. data/lib/active_record/type_caster/connection.rb +2 -1
  159. data/lib/active_record/validations/associated.rb +1 -1
  160. data/lib/active_record.rb +71 -6
  161. data/lib/arel/alias_predication.rb +2 -0
  162. data/lib/arel/collectors/bind.rb +1 -1
  163. data/lib/arel/collectors/sql_string.rb +1 -1
  164. data/lib/arel/collectors/substitute_binds.rb +2 -2
  165. data/lib/arel/crud.rb +8 -11
  166. data/lib/arel/delete_manager.rb +5 -0
  167. data/lib/arel/nodes/binary.rb +1 -1
  168. data/lib/arel/nodes/count.rb +2 -2
  169. data/lib/arel/nodes/delete_statement.rb +4 -2
  170. data/lib/arel/nodes/function.rb +4 -10
  171. data/lib/arel/nodes/named_function.rb +2 -2
  172. data/lib/arel/nodes/node.rb +2 -2
  173. data/lib/arel/nodes/sql_literal.rb +1 -1
  174. data/lib/arel/nodes/update_statement.rb +4 -2
  175. data/lib/arel/nodes.rb +0 -2
  176. data/lib/arel/select_manager.rb +13 -4
  177. data/lib/arel/update_manager.rb +5 -0
  178. data/lib/arel/visitors/dot.rb +2 -3
  179. data/lib/arel/visitors/postgresql.rb +55 -0
  180. data/lib/arel/visitors/sqlite.rb +55 -8
  181. data/lib/arel/visitors/to_sql.rb +6 -22
  182. data/lib/arel.rb +3 -1
  183. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  184. metadata +16 -15
  185. data/lib/active_record/explain_subscriber.rb +0 -34
  186. data/lib/active_record/normalization.rb +0 -163
@@ -41,11 +41,14 @@ module ActiveRecord
41
41
 
42
42
  def cleanup_view_runtime
43
43
  if logger && logger.info?
44
- db_rt_before_render = ActiveRecord::RuntimeRegistry.reset_runtimes
44
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
45
+ db_rt_before_render = runtime_stats.reset_runtimes
45
46
  self.db_runtime = (db_runtime || 0) + db_rt_before_render
47
+
46
48
  runtime = super
47
- queries_rt = ActiveRecord::RuntimeRegistry.sql_runtime - ActiveRecord::RuntimeRegistry.async_sql_runtime
48
- db_rt_after_render = ActiveRecord::RuntimeRegistry.reset_runtimes
49
+
50
+ queries_rt = runtime_stats.sql_runtime - runtime_stats.async_sql_runtime
51
+ db_rt_after_render = runtime_stats.reset_runtimes
49
52
  self.db_runtime += db_rt_after_render
50
53
  runtime - queries_rt
51
54
  else
@@ -56,9 +59,11 @@ module ActiveRecord
56
59
  def append_info_to_payload(payload)
57
60
  super
58
61
 
59
- payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::RuntimeRegistry.reset_runtimes
60
- payload[:queries_count] = ActiveRecord::RuntimeRegistry.reset_queries_count
61
- payload[:cached_queries_count] = ActiveRecord::RuntimeRegistry.reset_cached_queries_count
62
+ runtime_stats = ActiveRecord::RuntimeRegistry.stats
63
+ payload[:db_runtime] = (db_runtime || 0) + runtime_stats.sql_runtime
64
+ payload[:queries_count] = runtime_stats.queries_count
65
+ payload[:cached_queries_count] = runtime_stats.cached_queries_count
66
+ runtime_stats.reset
62
67
  end
63
68
  end
64
69
  end
@@ -160,9 +160,21 @@ db_namespace = namespace :db do
160
160
  end
161
161
  end
162
162
 
163
- # desc 'Resets your database using your migrations for the current environment'
163
+ desc "Resets your database using your migrations for the current environment"
164
164
  task reset: ["db:drop", "db:create", "db:schema:dump", "db:migrate"]
165
165
 
166
+ namespace :reset do
167
+ ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
168
+ desc "Drop and recreate the #{name} database using migrations"
169
+ task name => :load_config do
170
+ db_namespace["drop:#{name}"].invoke
171
+ db_namespace["create:#{name}"].invoke
172
+ db_namespace["schema:dump:#{name}"].invoke
173
+ db_namespace["migrate:#{name}"].invoke
174
+ end
175
+ end
176
+ end
177
+
166
178
  desc 'Run the "up" for a given migration VERSION.'
167
179
  task up: :load_config do
168
180
  ActiveRecord::Tasks::DatabaseTasks.raise_for_multi_db(command: "db:migrate:up")
@@ -333,7 +345,7 @@ db_namespace = namespace :db do
333
345
  pending_migrations << pool.migration_context.open.pending_migrations
334
346
  end
335
347
 
336
- pending_migrations = pending_migrations.flatten!
348
+ pending_migrations.flatten!
337
349
 
338
350
  if pending_migrations.any?
339
351
  puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -447,28 +459,23 @@ db_namespace = namespace :db do
447
459
  namespace :schema do
448
460
  desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
449
461
  task dump: :load_config do
450
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each do |pool|
451
- db_config = pool.db_config
452
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
453
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
454
- end
462
+ ActiveRecord::Tasks::DatabaseTasks.dump_all
455
463
 
456
464
  db_namespace["schema:dump"].reenable
457
465
  end
458
466
 
459
467
  desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
460
468
  task load: [:load_config, :check_protected_environments] do
461
- ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ActiveRecord.schema_format, ENV["SCHEMA"])
469
+ ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
462
470
  end
463
471
 
464
472
  namespace :dump do
465
473
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
466
- desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) for #{name} database"
474
+ desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) for #{name} database"
467
475
  task name => :load_config do
468
476
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
469
477
  db_config = pool.db_config
470
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
471
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
478
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
472
479
  end
473
480
 
474
481
  db_namespace["schema:dump:#{name}"].reenable
@@ -478,12 +485,11 @@ db_namespace = namespace :db do
478
485
 
479
486
  namespace :load do
480
487
  ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
481
- desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the #{name} database"
482
- task name => "db:test:purge:#{name}" do
488
+ desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
489
+ task name => [:load_config, :check_protected_environments] do
483
490
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
484
491
  db_config = pool.db_config
485
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
486
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
492
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
487
493
  end
488
494
  end
489
495
  end
@@ -527,13 +533,12 @@ db_namespace = namespace :db do
527
533
  end
528
534
 
529
535
  namespace :test do
530
- # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
536
+ # desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on configuration)"
531
537
  task load_schema: %w(db:test:purge) do
532
538
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test") do |pool|
533
539
  db_config = pool.db_config
534
540
  ActiveRecord::Schema.verbose = false
535
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
536
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
541
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
537
542
  end
538
543
  end
539
544
 
@@ -558,8 +563,7 @@ db_namespace = namespace :db do
558
563
  ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool|
559
564
  db_config = pool.db_config
560
565
  ActiveRecord::Schema.verbose = false
561
- schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
562
- ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
566
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
563
567
  end
564
568
  end
565
569
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Railties # :nodoc:
5
+ module JobCheckpoints # :nodoc:
6
+ def checkpoint!
7
+ if ActiveRecord.all_open_transactions.any?
8
+ raise ActiveJob::Continuation::CheckpointError, "Cannot checkpoint job with open transactions"
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,19 +5,18 @@ require "active_record/runtime_registry"
5
5
  module ActiveRecord
6
6
  module Railties # :nodoc:
7
7
  module JobRuntime # :nodoc:
8
- private
9
- def instrument(operation, payload = {}, &block)
10
- if operation == :perform && block
11
- super(operation, payload) do
12
- db_runtime_before_perform = ActiveRecord::RuntimeRegistry.sql_runtime
13
- result = block.call
14
- payload[:db_runtime] = ActiveRecord::RuntimeRegistry.sql_runtime - db_runtime_before_perform
15
- result
16
- end
17
- else
18
- super
8
+ def instrument(operation, payload = {}, &block) # :nodoc:
9
+ if operation == :perform && block
10
+ super(operation, payload) do
11
+ db_runtime_before_perform = ActiveRecord::RuntimeRegistry.stats.sql_runtime
12
+ result = block.call
13
+ payload[:db_runtime] = ActiveRecord::RuntimeRegistry.stats.sql_runtime - db_runtime_before_perform
14
+ result
19
15
  end
16
+ else
17
+ super
20
18
  end
19
+ end
21
20
  end
22
21
  end
23
22
  end
@@ -516,6 +516,8 @@ module ActiveRecord
516
516
 
517
517
  def initialize(name, scope, options, active_record)
518
518
  super
519
+
520
+ @validated = false
519
521
  @type = -(options[:foreign_type]&.to_s || "#{options[:as]}_type") if options[:as]
520
522
  @foreign_type = -(options[:foreign_type]&.to_s || "#{name}_type") if options[:polymorphic]
521
523
  @join_table = nil
@@ -534,6 +536,8 @@ module ActiveRecord
534
536
  options[:query_constraints] = options.delete(:foreign_key)
535
537
  end
536
538
 
539
+ @deprecated = !!options[:deprecated]
540
+
537
541
  ensure_option_not_given_as_class!(:class_name)
538
542
  end
539
543
 
@@ -616,6 +620,8 @@ module ActiveRecord
616
620
  end
617
621
 
618
622
  def check_validity!
623
+ return if @validated
624
+
619
625
  check_validity_of_inverse!
620
626
 
621
627
  if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
@@ -625,6 +631,8 @@ module ActiveRecord
625
631
  raise CompositePrimaryKeyMismatchError.new(self)
626
632
  end
627
633
  end
634
+
635
+ @validated = true
628
636
  end
629
637
 
630
638
  def check_eager_loadable!
@@ -742,6 +750,10 @@ module ActiveRecord
742
750
  Array(options[:extend])
743
751
  end
744
752
 
753
+ def deprecated?
754
+ @deprecated
755
+ end
756
+
745
757
  private
746
758
  # Attempts to find the inverse association name automatically.
747
759
  # If it cannot find a suitable inverse association name, it returns
@@ -975,6 +987,8 @@ module ActiveRecord
975
987
 
976
988
  def initialize(delegate_reflection)
977
989
  super()
990
+
991
+ @validated = false
978
992
  @delegate_reflection = delegate_reflection
979
993
  @klass = delegate_reflection.options[:anonymous_class]
980
994
  @source_reflection_name = delegate_reflection.options[:source]
@@ -1138,6 +1152,8 @@ module ActiveRecord
1138
1152
  end
1139
1153
 
1140
1154
  def check_validity!
1155
+ return if @validated
1156
+
1141
1157
  if through_reflection.nil?
1142
1158
  raise HasManyThroughAssociationNotFoundError.new(active_record, self)
1143
1159
  end
@@ -1175,6 +1191,8 @@ module ActiveRecord
1175
1191
  end
1176
1192
 
1177
1193
  check_validity_of_inverse!
1194
+
1195
+ @validated = true
1178
1196
  end
1179
1197
 
1180
1198
  def constraints
@@ -1195,6 +1213,10 @@ module ActiveRecord
1195
1213
  collect_join_reflections(seed + [self])
1196
1214
  end
1197
1215
 
1216
+ def deprecated_nested_reflections
1217
+ @deprecated_nested_reflections ||= collect_deprecated_nested_reflections
1218
+ end
1219
+
1198
1220
  protected
1199
1221
  def actual_source_reflection # FIXME: this is a horrible name
1200
1222
  source_reflection.actual_source_reflection
@@ -1219,6 +1241,19 @@ module ActiveRecord
1219
1241
  options[:source_type] || source_reflection.class_name
1220
1242
  end
1221
1243
 
1244
+ def collect_deprecated_nested_reflections
1245
+ result = []
1246
+ [through_reflection, source_reflection].each do |reflection|
1247
+ result << reflection if reflection.deprecated?
1248
+ # Both the through and the source reflections could be through
1249
+ # themselves. Nesting can go an arbitrary number of levels down.
1250
+ if reflection.through_reflection?
1251
+ result.concat(reflection.deprecated_nested_reflections)
1252
+ end
1253
+ end
1254
+ result
1255
+ end
1256
+
1222
1257
  delegate_methods = AssociationReflection.public_instance_methods -
1223
1258
  public_instance_methods
1224
1259
 
@@ -435,35 +435,49 @@ module ActiveRecord
435
435
  if load
436
436
  records = batch_relation.records
437
437
  values = records.pluck(*cursor)
438
- yielded_relation = where(cursor => values)
438
+ values_size = values.size
439
+ values_last = values.last
440
+ yielded_relation = rewhere(cursor => values)
439
441
  yielded_relation.load_records(records)
440
442
  elsif (empty_scope && use_ranges != false) || use_ranges
441
- values = batch_relation.pluck(*cursor)
443
+ # Efficiently peak at the last value for the next batch using offset and limit.
444
+ values_size = batch_limit
445
+ values_last = batch_relation.offset(batch_limit - 1).pick(*cursor)
446
+
447
+ # If the last value is not found using offset, there is at most one more batch of size < batch_limit.
448
+ # Retry by getting the whole list of remaining values so that we have the exact size and last value.
449
+ unless values_last
450
+ values = batch_relation.pluck(*cursor)
451
+ values_size = values.size
452
+ values_last = values.last
453
+ end
442
454
 
443
- finish = values.last
444
- if finish
445
- yielded_relation = apply_finish_limit(batch_relation, cursor, finish, batch_orders)
455
+ # Finally, build the yielded relation if at least one value found.
456
+ if values_last
457
+ yielded_relation = apply_finish_limit(batch_relation, cursor, values_last, batch_orders)
446
458
  yielded_relation = yielded_relation.except(:limit, :order)
447
459
  yielded_relation.skip_query_cache!(false)
448
460
  end
449
461
  else
450
462
  values = batch_relation.pluck(*cursor)
451
- yielded_relation = where(cursor => values)
463
+ values_size = values.size
464
+ values_last = values.last
465
+ yielded_relation = rewhere(cursor => values)
452
466
  end
453
467
 
454
- break if values.empty?
468
+ break if values_size == 0
455
469
 
456
- if values.flatten.any?(nil)
470
+ if [values_last].flatten.any?(nil)
457
471
  raise ArgumentError, "Not all of the batch cursor columns were included in the custom select clause "\
458
472
  "or some columns contain nil."
459
473
  end
460
474
 
461
475
  yield yielded_relation
462
476
 
463
- break if values.length < batch_limit
477
+ break if values_size < batch_limit
464
478
 
465
479
  if limit_value
466
- remaining -= values.length
480
+ remaining -= values_size
467
481
 
468
482
  if remaining == 0
469
483
  # Saves a useless iteration when the limit is a multiple of the
@@ -481,7 +495,7 @@ module ActiveRecord
481
495
  end
482
496
  operators << (last_order == :desc ? :lt : :gt)
483
497
 
484
- cursor_value = values.last
498
+ cursor_value = values_last
485
499
  batch_relation = batch_condition(relation, cursor, cursor_value, operators)
486
500
  end
487
501
 
@@ -60,37 +60,37 @@ module ActiveRecord
60
60
  # Person.distinct.count(:age)
61
61
  # # => counts the number of different age values
62
62
  #
63
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group],
63
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
64
64
  # it returns a Hash whose keys represent the aggregated column,
65
65
  # and the values are the respective amounts:
66
66
  #
67
67
  # Person.group(:city).count
68
68
  # # => { 'Rome' => 5, 'Paris' => 3 }
69
69
  #
70
- # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
70
+ # If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
71
71
  # keys are an array containing the individual values of each column and the value
72
- # of each key would be the #count.
72
+ # of each key would be the count.
73
73
  #
74
74
  # Article.group(:status, :category).count
75
75
  # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
76
76
  #
77
- # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
77
+ # If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
78
78
  #
79
79
  # Person.select(:age).count
80
80
  # # => counts the number of different age values
81
81
  #
82
- # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
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
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.
85
+ # When given a block, calls the block with each record in the relation and
86
+ # returns the number of records for which the block returns a truthy value.
88
87
  #
89
88
  # Person.count { |person| person.age > 21 }
90
89
  # # => counts the number of people older that 21
91
90
  #
92
- # Note: If there are a lot of records in the relation, loading all records
93
- # could result in performance issues.
91
+ # If the relation hasn't been loaded yet, calling +count+ with a block will
92
+ # load all records in the relation. If there are a lot of records in the
93
+ # relation, loading all records could result in performance issues.
94
94
  def count(column_name = nil)
95
95
  if block_given?
96
96
  unless column_name.nil?
@@ -159,16 +159,15 @@ module ActiveRecord
159
159
  #
160
160
  # Person.sum(:age) # => 4562
161
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:
162
+ # When given a block, calls the block with each record in the relation and
163
+ # returns the sum of +initial_value_or_column+ plus the block return values:
166
164
  #
167
165
  # Person.sum { |person| person.age } # => 4562
168
166
  # Person.sum(1000) { |person| person.age } # => 5562
169
167
  #
170
- # Note: If there are a lot of records in the relation, loading all records
171
- # could result in performance issues.
168
+ # If the relation hasn't been loaded yet, calling +sum+ with a block will
169
+ # load all records in the relation. If there are a lot of records in the
170
+ # relation, loading all records could result in performance issues.
172
171
  def sum(initial_value_or_column = 0, &block)
173
172
  if block_given?
174
173
  map(&block).sum(initial_value_or_column)
@@ -287,6 +286,11 @@ module ActiveRecord
287
286
  # # SELECT DATEDIFF(updated_at, created_at) FROM people
288
287
  # # => ['0', '27761', '173']
289
288
  #
289
+ # Be aware that #pluck ignores any previous select clauses
290
+ #
291
+ # Person.select(:name).pluck(:id)
292
+ # # SELECT people.id FROM people
293
+ #
290
294
  # See also #ids.
291
295
  def pluck(*column_names)
292
296
  if @none
@@ -315,7 +319,7 @@ module ActiveRecord
315
319
  columns = relation.arel_columns(column_names)
316
320
  relation.select_values = columns
317
321
  result = skip_query_cache_if_necessary do
318
- if where_clause.contradiction?
322
+ if where_clause.contradiction? && !possible_aggregation?(column_names)
319
323
  ActiveRecord::Result.empty(async: @async)
320
324
  else
321
325
  model.with_connection do |c|
@@ -410,6 +414,18 @@ module ActiveRecord
410
414
  async.ids
411
415
  end
412
416
 
417
+ protected
418
+ def aggregate_column(column_name)
419
+ case column_name
420
+ when Arel::Expressions
421
+ column_name
422
+ when :all
423
+ Arel.star
424
+ else
425
+ arel_column(column_name.to_s)
426
+ end
427
+ end
428
+
413
429
  private
414
430
  def all_attributes?(column_names)
415
431
  (column_names.map(&:to_s) - model.attribute_names - model.attribute_aliases.keys).empty?
@@ -450,14 +466,13 @@ module ActiveRecord
450
466
  column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
451
467
  end
452
468
 
453
- def aggregate_column(column_name)
454
- case column_name
455
- when Arel::Expressions
456
- column_name
457
- when :all
458
- Arel.star
459
- else
460
- arel_column(column_name)
469
+ def possible_aggregation?(column_names)
470
+ column_names.all? do |column_name|
471
+ if column_name.is_a?(String)
472
+ column_name.include?("(")
473
+ else
474
+ Arel.arel_node?(column_name)
475
+ end
461
476
  end
462
477
  end
463
478
 
@@ -476,7 +491,7 @@ module ActiveRecord
476
491
  # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
477
492
  relation = unscope(:order).distinct!(false)
478
493
 
479
- column = aggregate_column(column_name)
494
+ column = relation.aggregate_column(column_name)
480
495
  select_value = operation_over_aggregate_column(column, operation, distinct)
481
496
  select_value.distinct = true if operation == "sum" && distinct
482
497
 
@@ -486,7 +501,11 @@ module ActiveRecord
486
501
  end
487
502
 
488
503
  query_result = if relation.where_clause.contradiction?
489
- ActiveRecord::Result.empty
504
+ if @async
505
+ FutureResult.wrap(ActiveRecord::Result.empty)
506
+ else
507
+ ActiveRecord::Result.empty
508
+ end
490
509
  else
491
510
  skip_query_cache_if_necessary do
492
511
  model.with_connection do |c|
@@ -508,14 +527,15 @@ module ActiveRecord
508
527
 
509
528
  def execute_grouped_calculation(operation, column_name, distinct) # :nodoc:
510
529
  group_fields = group_values
511
- group_fields = group_fields.uniq if group_fields.size > 1
512
530
 
513
531
  if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
514
532
  association = model._reflect_on_association(group_fields.first)
515
533
  associated = association && association.belongs_to? # only count belongs_to associations
516
534
  group_fields = Array(association.foreign_key) if associated
517
535
  end
518
- group_fields = arel_columns(group_fields)
536
+
537
+ relation = except(:group).distinct!(false)
538
+ group_fields = relation.arel_columns(group_fields)
519
539
 
520
540
  model.with_connection do |connection|
521
541
  column_alias_tracker = ColumnAliasTracker.new(connection)
@@ -526,10 +546,10 @@ module ActiveRecord
526
546
  }
527
547
  group_columns = group_aliases.zip(group_fields)
528
548
 
529
- column = aggregate_column(column_name)
549
+ column = relation.aggregate_column(column_name)
530
550
  column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
531
551
  select_value = operation_over_aggregate_column(column, operation, distinct)
532
- select_value.as(model.adapter_class.quote_column_name(column_alias))
552
+ select_value = select_value.as(model.adapter_class.quote_column_name(column_alias))
533
553
 
534
554
  select_values = [select_value]
535
555
  select_values += self.select_values unless having_clause.empty?
@@ -543,7 +563,6 @@ module ActiveRecord
543
563
  end
544
564
  }
545
565
 
546
- relation = except(:group).distinct!(false)
547
566
  relation.group_values = group_fields
548
567
  relation.select_values = select_values
549
568
 
@@ -657,19 +676,16 @@ module ActiveRecord
657
676
  if column_name == :all
658
677
  column_alias = Arel.star
659
678
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
679
+ relation.unscope!(:order)
660
680
  else
661
681
  column_alias = Arel.sql("count_column")
662
- relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
682
+ relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
663
683
  end
664
684
 
665
685
  subquery_alias = Arel.sql("subquery_for_count", retryable: true)
666
686
  select_value = operation_over_aggregate_column(column_alias, "count", false)
667
687
 
668
- if column_name == :all
669
- relation.unscope(:order).build_subquery(subquery_alias, select_value)
670
- else
671
- relation.build_subquery(subquery_alias, select_value)
672
- end
688
+ relation.build_subquery(subquery_alias, select_value)
673
689
  end
674
690
  end
675
691
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Delegation # :nodoc: