activerecord 7.2.2.1 → 8.0.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.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +523 -677
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +34 -10
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +7 -6
  9. data/lib/active_record/associations/collection_association.rb +10 -8
  10. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  12. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  13. data/lib/active_record/associations/join_dependency.rb +2 -2
  14. data/lib/active_record/associations/preloader/association.rb +2 -2
  15. data/lib/active_record/associations/preloader/batch.rb +7 -1
  16. data/lib/active_record/associations/singular_association.rb +8 -3
  17. data/lib/active_record/associations.rb +34 -4
  18. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  19. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  20. data/lib/active_record/attribute_methods/query.rb +34 -0
  21. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  22. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  23. data/lib/active_record/attribute_methods.rb +24 -19
  24. data/lib/active_record/attributes.rb +37 -26
  25. data/lib/active_record/autosave_association.rb +91 -39
  26. data/lib/active_record/base.rb +2 -2
  27. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  28. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  29. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  30. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +55 -47
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -8
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +40 -10
  37. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +92 -70
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +35 -46
  40. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -9
  45. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  46. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
  47. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  48. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  50. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  51. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  52. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -24
  53. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  54. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +60 -17
  55. data/lib/active_record/connection_adapters/postgresql_adapter.rb +61 -99
  56. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  57. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  58. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  59. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  60. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  61. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  62. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +10 -1
  63. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +63 -17
  64. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  65. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  66. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  67. data/lib/active_record/connection_adapters.rb +0 -56
  68. data/lib/active_record/connection_handling.rb +35 -9
  69. data/lib/active_record/core.rb +57 -22
  70. data/lib/active_record/counter_cache.rb +1 -1
  71. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  72. data/lib/active_record/database_configurations/database_config.rb +4 -0
  73. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  74. data/lib/active_record/delegated_type.rb +18 -18
  75. data/lib/active_record/encryption/config.rb +3 -1
  76. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  77. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  78. data/lib/active_record/encryption/encryptor.rb +37 -28
  79. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  80. data/lib/active_record/encryption/scheme.rb +8 -1
  81. data/lib/active_record/enum.rb +22 -34
  82. data/lib/active_record/errors.rb +16 -8
  83. data/lib/active_record/fixture_set/table_row.rb +19 -2
  84. data/lib/active_record/fixtures.rb +0 -2
  85. data/lib/active_record/future_result.rb +15 -9
  86. data/lib/active_record/gem_version.rb +4 -4
  87. data/lib/active_record/insert_all.rb +3 -3
  88. data/lib/active_record/locking/optimistic.rb +1 -1
  89. data/lib/active_record/log_subscriber.rb +5 -11
  90. data/lib/active_record/migration/command_recorder.rb +32 -12
  91. data/lib/active_record/migration/compatibility.rb +5 -2
  92. data/lib/active_record/migration.rb +40 -43
  93. data/lib/active_record/model_schema.rb +3 -4
  94. data/lib/active_record/nested_attributes.rb +4 -6
  95. data/lib/active_record/persistence.rb +128 -130
  96. data/lib/active_record/query_logs.rb +106 -50
  97. data/lib/active_record/query_logs_formatter.rb +17 -28
  98. data/lib/active_record/querying.rb +12 -12
  99. data/lib/active_record/railtie.rb +4 -28
  100. data/lib/active_record/railties/databases.rake +11 -34
  101. data/lib/active_record/reflection.rb +18 -21
  102. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  103. data/lib/active_record/relation/batches.rb +132 -72
  104. data/lib/active_record/relation/calculations.rb +71 -65
  105. data/lib/active_record/relation/delegation.rb +25 -14
  106. data/lib/active_record/relation/finder_methods.rb +32 -31
  107. data/lib/active_record/relation/merger.rb +8 -8
  108. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  109. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  110. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  111. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  112. data/lib/active_record/relation/predicate_builder.rb +13 -0
  113. data/lib/active_record/relation/query_attribute.rb +1 -1
  114. data/lib/active_record/relation/query_methods.rb +121 -70
  115. data/lib/active_record/relation/spawn_methods.rb +7 -7
  116. data/lib/active_record/relation/where_clause.rb +9 -3
  117. data/lib/active_record/relation.rb +95 -67
  118. data/lib/active_record/result.rb +66 -4
  119. data/lib/active_record/sanitization.rb +7 -6
  120. data/lib/active_record/schema_dumper.rb +34 -11
  121. data/lib/active_record/schema_migration.rb +2 -1
  122. data/lib/active_record/scoping/named.rb +5 -2
  123. data/lib/active_record/secure_token.rb +3 -3
  124. data/lib/active_record/signed_id.rb +7 -6
  125. data/lib/active_record/statement_cache.rb +14 -14
  126. data/lib/active_record/store.rb +7 -3
  127. data/lib/active_record/table_metadata.rb +1 -3
  128. data/lib/active_record/tasks/database_tasks.rb +69 -60
  129. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  130. data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
  131. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  132. data/lib/active_record/test_databases.rb +1 -1
  133. data/lib/active_record/test_fixtures.rb +12 -0
  134. data/lib/active_record/token_for.rb +1 -1
  135. data/lib/active_record/transactions.rb +8 -7
  136. data/lib/active_record/type/serialized.rb +5 -0
  137. data/lib/active_record/validations/uniqueness.rb +8 -8
  138. data/lib/active_record.rb +22 -49
  139. data/lib/arel/collectors/bind.rb +2 -2
  140. data/lib/arel/collectors/sql_string.rb +1 -1
  141. data/lib/arel/collectors/substitute_binds.rb +2 -2
  142. data/lib/arel/crud.rb +2 -0
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/binary.rb +1 -1
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/node.rb +1 -1
  147. data/lib/arel/nodes/sql_literal.rb +1 -1
  148. data/lib/arel/nodes/update_statement.rb +4 -2
  149. data/lib/arel/predications.rb +1 -3
  150. data/lib/arel/select_manager.rb +6 -2
  151. data/lib/arel/table.rb +3 -7
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -0
  154. data/lib/arel/visitors/to_sql.rb +3 -1
  155. metadata +11 -15
  156. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -68,19 +68,26 @@ module ActiveRecord
68
68
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
69
69
  include SignedId::RelationMethods, TokenFor::RelationMethods
70
70
 
71
- attr_reader :table, :klass, :loaded, :predicate_builder
71
+ attr_reader :table, :model, :loaded, :predicate_builder
72
72
  attr_accessor :skip_preloading_value
73
- alias :model :klass
73
+ alias :klass :model
74
74
  alias :loaded? :loaded
75
75
  alias :locked? :lock_value
76
76
 
77
- def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
78
- @klass = klass
77
+ def initialize(model, table: nil, predicate_builder: nil, values: {})
78
+ if table
79
+ predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table))
80
+ else
81
+ table = model.arel_table
82
+ predicate_builder ||= model.predicate_builder
83
+ end
84
+
85
+ @model = model
79
86
  @table = table
80
87
  @values = values
81
88
  @loaded = false
82
89
  @predicate_builder = predicate_builder
83
- @delegate_to_klass = false
90
+ @delegate_to_model = false
84
91
  @future_result = nil
85
92
  @records = nil
86
93
  @async = false
@@ -93,7 +100,7 @@ module ActiveRecord
93
100
  end
94
101
 
95
102
  def bind_attribute(name, value) # :nodoc:
96
- if reflection = klass._reflect_on_association(name)
103
+ if reflection = model._reflect_on_association(name)
97
104
  name = reflection.foreign_key
98
105
  value = value.read_attribute(reflection.association_primary_key) unless value.nil?
99
106
  end
@@ -265,7 +272,12 @@ module ActiveRecord
265
272
  # such situation.
266
273
  def create_or_find_by(attributes, &block)
267
274
  with_connection do |connection|
268
- transaction(requires_new: true) { create(attributes, &block) }
275
+ record = nil
276
+ transaction(requires_new: true) do
277
+ record = create(attributes, &block)
278
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
279
+ end
280
+ record
269
281
  rescue ActiveRecord::RecordNotUnique
270
282
  if connection.transaction_open?
271
283
  where(attributes).lock.find_by!(attributes)
@@ -280,7 +292,12 @@ module ActiveRecord
280
292
  # is raised if the created record is invalid.
281
293
  def create_or_find_by!(attributes, &block)
282
294
  with_connection do |connection|
283
- transaction(requires_new: true) { create!(attributes, &block) }
295
+ record = nil
296
+ transaction(requires_new: true) do
297
+ record = create!(attributes, &block)
298
+ record._last_transaction_return_status || raise(ActiveRecord::Rollback)
299
+ end
300
+ record
284
301
  rescue ActiveRecord::RecordNotUnique
285
302
  if connection.transaction_open?
286
303
  where(attributes).lock.find_by!(attributes)
@@ -290,7 +307,7 @@ module ActiveRecord
290
307
  end
291
308
  end
292
309
 
293
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
310
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
294
311
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
295
312
  def find_or_initialize_by(attributes, &block)
296
313
  find_by(attributes) || new(attributes, &block)
@@ -430,14 +447,14 @@ module ActiveRecord
430
447
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
431
448
  def cache_key(timestamp_column = "updated_at")
432
449
  @cache_keys ||= {}
433
- @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
450
+ @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
434
451
  end
435
452
 
436
453
  def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
437
454
  query_signature = ActiveSupport::Digest.hexdigest(to_sql)
438
- key = "#{klass.model_name.cache_key}/query-#{query_signature}"
455
+ key = "#{model.model_name.cache_key}/query-#{query_signature}"
439
456
 
440
- if collection_cache_versioning
457
+ if model.collection_cache_versioning
441
458
  key
442
459
  else
443
460
  "#{key}-#{compute_cache_version(timestamp_column)}"
@@ -456,7 +473,7 @@ module ActiveRecord
456
473
  #
457
474
  # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
458
475
  def cache_version(timestamp_column = :updated_at)
459
- if collection_cache_versioning
476
+ if model.collection_cache_versioning
460
477
  @cache_versions ||= {}
461
478
  @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
462
479
  end
@@ -475,7 +492,7 @@ module ActiveRecord
475
492
 
476
493
  with_connection do |c|
477
494
  column = c.visitor.compile(table[timestamp_column])
478
- select_values = "COUNT(*) AS #{adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
495
+ select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
479
496
 
480
497
  if collection.has_limit_or_offset?
481
498
  query = collection.select("#{column} AS collection_cache_key_timestamp")
@@ -492,7 +509,7 @@ module ActiveRecord
492
509
  size, timestamp = c.select_rows(arel, nil).first
493
510
 
494
511
  if size
495
- column_type = klass.type_for_attribute(timestamp_column)
512
+ column_type = model.type_for_attribute(timestamp_column)
496
513
  timestamp = column_type.deserialize(timestamp)
497
514
  else
498
515
  size = 0
@@ -501,7 +518,7 @@ module ActiveRecord
501
518
  end
502
519
 
503
520
  if timestamp
504
- "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
521
+ "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
505
522
  else
506
523
  "#{size}"
507
524
  end
@@ -532,7 +549,7 @@ module ActiveRecord
532
549
  # Please check unscoped if you want to remove all previous scopes (including
533
550
  # the default_scope) during the execution of a block.
534
551
  def scoping(all_queries: nil, &block)
535
- registry = klass.scope_registry
552
+ registry = model.scope_registry
536
553
  if global_scope?(registry) && all_queries == false
537
554
  raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
538
555
  elsif already_in_scope?(registry)
@@ -543,11 +560,11 @@ module ActiveRecord
543
560
  end
544
561
 
545
562
  def _exec_scope(...) # :nodoc:
546
- @delegate_to_klass = true
547
- registry = klass.scope_registry
563
+ @delegate_to_model = true
564
+ registry = model.scope_registry
548
565
  _scoping(nil, registry) { instance_exec(...) || self }
549
566
  ensure
550
- @delegate_to_klass = false
567
+ @delegate_to_model = false
551
568
  end
552
569
 
553
570
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
@@ -584,30 +601,30 @@ module ActiveRecord
584
601
  return 0 if @none
585
602
 
586
603
  if updates.is_a?(Hash)
587
- if klass.locking_enabled? &&
588
- !updates.key?(klass.locking_column) &&
589
- !updates.key?(klass.locking_column.to_sym)
590
- attr = table[klass.locking_column]
604
+ if model.locking_enabled? &&
605
+ !updates.key?(model.locking_column) &&
606
+ !updates.key?(model.locking_column.to_sym)
607
+ attr = table[model.locking_column]
591
608
  updates[attr.name] = _increment_attribute(attr)
592
609
  end
593
610
  values = _substitute_values(updates)
594
611
  else
595
- values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
612
+ values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
596
613
  end
597
614
 
598
- klass.with_connection do |c|
615
+ model.with_connection do |c|
599
616
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
600
617
  arel.source.left = table
601
618
 
602
619
  group_values_arel_columns = arel_columns(group_values.uniq)
603
620
  having_clause_ast = having_clause.ast unless having_clause.empty?
604
- key = if klass.composite_primary_key?
621
+ key = if model.composite_primary_key?
605
622
  primary_key.map { |pk| table[pk] }
606
623
  else
607
624
  table[primary_key]
608
625
  end
609
626
  stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
610
- c.update(stmt, "#{klass} Update All").tap { reset }
627
+ c.update(stmt, "#{model} Update All").tap { reset }
611
628
  end
612
629
  end
613
630
 
@@ -615,7 +632,7 @@ module ActiveRecord
615
632
  if id == :all
616
633
  each { |record| record.update(attributes) }
617
634
  else
618
- klass.update(id, attributes)
635
+ model.update(id, attributes)
619
636
  end
620
637
  end
621
638
 
@@ -623,7 +640,7 @@ module ActiveRecord
623
640
  if id == :all
624
641
  each { |record| record.update!(attributes) }
625
642
  else
626
- klass.update!(id, attributes)
643
+ model.update!(id, attributes)
627
644
  end
628
645
  end
629
646
 
@@ -813,7 +830,7 @@ module ActiveRecord
813
830
  #
814
831
  # [:returning]
815
832
  # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
816
- # inserted records, which by default is the primary key.
833
+ # upserted records, which by default is the primary key.
817
834
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
818
835
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
819
836
  # clause entirely.
@@ -929,7 +946,7 @@ module ActiveRecord
929
946
  names = touch if touch != true
930
947
  names = Array.wrap(names)
931
948
  options = names.extract_options!
932
- touch_updates = klass.touch_attributes_with_time(*names, **options)
949
+ touch_updates = model.touch_attributes_with_time(*names, **options)
933
950
  updates.merge!(touch_updates) unless touch_updates.empty?
934
951
  end
935
952
 
@@ -942,7 +959,7 @@ module ActiveRecord
942
959
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
943
960
  # If no time argument is passed, the current time is used as default.
944
961
  #
945
- # === Examples
962
+ # ==== Examples
946
963
  #
947
964
  # # Touch all records
948
965
  # Person.all.touch_all
@@ -960,7 +977,7 @@ module ActiveRecord
960
977
  # Person.where(name: 'David').touch_all
961
978
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
962
979
  def touch_all(*names, time: nil)
963
- update_all klass.touch_attributes_with_time(*names, time: time)
980
+ update_all model.touch_attributes_with_time(*names, time: time)
964
981
  end
965
982
 
966
983
  # Destroys the records by instantiating each
@@ -993,7 +1010,7 @@ module ActiveRecord
993
1010
  #
994
1011
  # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
995
1012
  #
996
- # Both calls delete the affected posts all at once with a single DELETE statement.
1013
+ # This call deletes the affected posts all at once with a single DELETE statement.
997
1014
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
998
1015
  # +after_destroy+ callbacks, use the #destroy_all method instead.
999
1016
  #
@@ -1012,20 +1029,20 @@ module ActiveRecord
1012
1029
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
1013
1030
  end
1014
1031
 
1015
- klass.with_connection do |c|
1032
+ model.with_connection do |c|
1016
1033
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1017
1034
  arel.source.left = table
1018
1035
 
1019
1036
  group_values_arel_columns = arel_columns(group_values.uniq)
1020
1037
  having_clause_ast = having_clause.ast unless having_clause.empty?
1021
- key = if klass.composite_primary_key?
1038
+ key = if model.composite_primary_key?
1022
1039
  primary_key.map { |pk| table[pk] }
1023
1040
  else
1024
1041
  table[primary_key]
1025
1042
  end
1026
1043
  stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1027
1044
 
1028
- c.delete(stmt, "#{klass} Delete All").tap { reset }
1045
+ c.delete(stmt, "#{model} Delete All").tap { reset }
1029
1046
  end
1030
1047
  end
1031
1048
 
@@ -1124,9 +1141,6 @@ module ActiveRecord
1124
1141
  # for queries to actually be executed concurrently. Otherwise it defaults to
1125
1142
  # executing them in the foreground.
1126
1143
  #
1127
- # +load_async+ will also fall back to executing in the foreground in the test environment when transactional
1128
- # fixtures are enabled.
1129
- #
1130
1144
  # If the query was actually executed in the background, the Active Record logs will show
1131
1145
  # it by prefixing the log line with <tt>ASYNC</tt>:
1132
1146
  #
@@ -1136,7 +1150,7 @@ module ActiveRecord
1136
1150
  return load if !c.async_enabled?
1137
1151
 
1138
1152
  unless loaded?
1139
- result = exec_main_query(async: c.current_transaction.closed?)
1153
+ result = exec_main_query(async: !c.current_transaction.joinable?)
1140
1154
 
1141
1155
  if result.is_a?(Array)
1142
1156
  @records = result
@@ -1150,6 +1164,16 @@ module ActiveRecord
1150
1164
  self
1151
1165
  end
1152
1166
 
1167
+ def then(&block) # :nodoc:
1168
+ if @future_result
1169
+ @future_result.then do
1170
+ yield self
1171
+ end
1172
+ else
1173
+ super
1174
+ end
1175
+ end
1176
+
1153
1177
  # Returns <tt>true</tt> if the relation was scheduled on the background
1154
1178
  # thread pool.
1155
1179
  def scheduled?
@@ -1180,7 +1204,7 @@ module ActiveRecord
1180
1204
  def reset
1181
1205
  @future_result&.cancel
1182
1206
  @future_result = nil
1183
- @delegate_to_klass = false
1207
+ @delegate_to_model = false
1184
1208
  @to_sql = @arel = @loaded = @should_eager_load = nil
1185
1209
  @offsets = @take = nil
1186
1210
  @cache_keys = nil
@@ -1200,7 +1224,7 @@ module ActiveRecord
1200
1224
  relation.to_sql
1201
1225
  end
1202
1226
  else
1203
- klass.with_connection do |conn|
1227
+ model.with_connection do |conn|
1204
1228
  conn.unprepared_statement { conn.to_sql(arel) }
1205
1229
  end
1206
1230
  end
@@ -1210,12 +1234,12 @@ module ActiveRecord
1210
1234
  #
1211
1235
  # User.where(name: 'Oscar').where_values_hash
1212
1236
  # # => {name: "Oscar"}
1213
- def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
1237
+ def where_values_hash(relation_table_name = model.table_name) # :nodoc:
1214
1238
  where_clause.to_h(relation_table_name)
1215
1239
  end
1216
1240
 
1217
1241
  def scope_for_create
1218
- hash = where_clause.to_h(klass.table_name, equality_only: true)
1242
+ hash = where_clause.to_h(model.table_name, equality_only: true)
1219
1243
  create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
1220
1244
  hash
1221
1245
  end
@@ -1261,6 +1285,10 @@ module ActiveRecord
1261
1285
  records.blank?
1262
1286
  end
1263
1287
 
1288
+ def readonly?
1289
+ readonly_value
1290
+ end
1291
+
1264
1292
  def values
1265
1293
  @values.dup
1266
1294
  end
@@ -1279,7 +1307,7 @@ module ActiveRecord
1279
1307
  end
1280
1308
 
1281
1309
  def empty_scope? # :nodoc:
1282
- @values == klass.unscoped.values
1310
+ @values == model.unscoped.values
1283
1311
  end
1284
1312
 
1285
1313
  def has_limit_or_offset? # :nodoc:
@@ -1287,7 +1315,7 @@ module ActiveRecord
1287
1315
  end
1288
1316
 
1289
1317
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
1290
- ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
1318
+ ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
1291
1319
  end
1292
1320
 
1293
1321
  class StrictLoadingScope # :nodoc:
@@ -1317,46 +1345,46 @@ module ActiveRecord
1317
1345
 
1318
1346
  private
1319
1347
  def already_in_scope?(registry)
1320
- @delegate_to_klass && registry.current_scope(klass, true)
1348
+ @delegate_to_model && registry.current_scope(model, true)
1321
1349
  end
1322
1350
 
1323
1351
  def global_scope?(registry)
1324
- registry.global_current_scope(klass, true)
1352
+ registry.global_current_scope(model, true)
1325
1353
  end
1326
1354
 
1327
1355
  def current_scope_restoring_block(&block)
1328
- current_scope = klass.current_scope(true)
1356
+ current_scope = model.current_scope(true)
1329
1357
  -> record do
1330
- klass.current_scope = current_scope
1358
+ model.current_scope = current_scope
1331
1359
  yield record if block_given?
1332
1360
  end
1333
1361
  end
1334
1362
 
1335
1363
  def _new(attributes, &block)
1336
- klass.new(attributes, &block)
1364
+ model.new(attributes, &block)
1337
1365
  end
1338
1366
 
1339
1367
  def _create(attributes, &block)
1340
- klass.create(attributes, &block)
1368
+ model.create(attributes, &block)
1341
1369
  end
1342
1370
 
1343
1371
  def _create!(attributes, &block)
1344
- klass.create!(attributes, &block)
1372
+ model.create!(attributes, &block)
1345
1373
  end
1346
1374
 
1347
1375
  def _scoping(scope, registry, all_queries = false)
1348
- previous = registry.current_scope(klass, true)
1349
- registry.set_current_scope(klass, scope)
1376
+ previous = registry.current_scope(model, true)
1377
+ registry.set_current_scope(model, scope)
1350
1378
 
1351
1379
  if all_queries
1352
- previous_global = registry.global_current_scope(klass, true)
1353
- registry.set_global_current_scope(klass, scope)
1380
+ previous_global = registry.global_current_scope(model, true)
1381
+ registry.set_global_current_scope(model, scope)
1354
1382
  end
1355
1383
  yield
1356
1384
  ensure
1357
- registry.set_current_scope(klass, previous)
1385
+ registry.set_current_scope(model, previous)
1358
1386
  if all_queries
1359
- registry.set_global_current_scope(klass, previous_global)
1387
+ registry.set_global_current_scope(model, previous_global)
1360
1388
  end
1361
1389
  end
1362
1390
 
@@ -1368,7 +1396,7 @@ module ActiveRecord
1368
1396
  value = Arel::Nodes::Grouping.new(value)
1369
1397
  end
1370
1398
  else
1371
- type = klass.type_for_attribute(attr.name)
1399
+ type = model.type_for_attribute(attr.name)
1372
1400
  value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
1373
1401
  end
1374
1402
  [attr, value]
@@ -1415,7 +1443,7 @@ module ActiveRecord
1415
1443
  if where_clause.contradiction?
1416
1444
  [].freeze
1417
1445
  elsif eager_loading?
1418
- klass.with_connection do |c|
1446
+ model.with_connection do |c|
1419
1447
  apply_join_dependency do |relation, join_dependency|
1420
1448
  if relation.null_relation?
1421
1449
  [].freeze
@@ -1427,8 +1455,8 @@ module ActiveRecord
1427
1455
  end
1428
1456
  end
1429
1457
  else
1430
- klass.with_connection do |c|
1431
- klass._query_by_sql(c, arel, async: async)
1458
+ model.with_connection do |c|
1459
+ model._query_by_sql(c, arel, async: async)
1432
1460
  end
1433
1461
  end
1434
1462
  end
@@ -1441,13 +1469,13 @@ module ActiveRecord
1441
1469
  @_join_dependency = nil
1442
1470
  records
1443
1471
  else
1444
- klass._load_from_sql(rows, &block).freeze
1472
+ model._load_from_sql(rows, &block).freeze
1445
1473
  end
1446
1474
  end
1447
1475
 
1448
1476
  def skip_query_cache_if_necessary(&block)
1449
1477
  if skip_query_cache_value
1450
- uncached(&block)
1478
+ model.uncached(&block)
1451
1479
  else
1452
1480
  yield
1453
1481
  end
@@ -36,6 +36,59 @@ module ActiveRecord
36
36
  class Result
37
37
  include Enumerable
38
38
 
39
+ class IndexedRow
40
+ def initialize(column_indexes, row)
41
+ @column_indexes = column_indexes
42
+ @row = row
43
+ end
44
+
45
+ def size
46
+ @column_indexes.size
47
+ end
48
+ alias_method :length, :size
49
+
50
+ def each_key(&block)
51
+ @column_indexes.each_key(&block)
52
+ end
53
+
54
+ def keys
55
+ @column_indexes.keys
56
+ end
57
+
58
+ def ==(other)
59
+ if other.is_a?(Hash)
60
+ to_hash == other
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def key?(column)
67
+ @column_indexes.key?(column)
68
+ end
69
+
70
+ def fetch(column)
71
+ if index = @column_indexes[column]
72
+ @row[index]
73
+ elsif block_given?
74
+ yield
75
+ else
76
+ raise KeyError, "key not found: #{column.inspect}"
77
+ end
78
+ end
79
+
80
+ def [](column)
81
+ if index = @column_indexes[column]
82
+ @row[index]
83
+ end
84
+ end
85
+
86
+ def to_h
87
+ @column_indexes.transform_values { |index| @row[index] }
88
+ end
89
+ alias_method :to_hash, :to_h
90
+ end
91
+
39
92
  attr_reader :columns, :rows, :column_types
40
93
 
41
94
  def self.empty(async: false) # :nodoc:
@@ -67,7 +120,9 @@ module ActiveRecord
67
120
  end
68
121
 
69
122
  # Calls the given block once for each element in row collection, passing
70
- # row as parameter.
123
+ # row as parameter. Each row is a Hash-like, read only object.
124
+ #
125
+ # To get real hashes, use +.to_a.each+.
71
126
  #
72
127
  # Returns an +Enumerator+ if no block is given.
73
128
  def each(&block)
@@ -134,14 +189,14 @@ module ActiveRecord
134
189
  end
135
190
 
136
191
  def initialize_copy(other)
137
- @columns = columns
138
- @rows = rows.dup
192
+ @rows = rows.dup
139
193
  @column_types = column_types.dup
140
194
  @hash_rows = nil
141
195
  end
142
196
 
143
197
  def freeze # :nodoc:
144
198
  hash_rows.freeze
199
+ indexed_rows.freeze
145
200
  super
146
201
  end
147
202
 
@@ -154,7 +209,14 @@ module ActiveRecord
154
209
  hash[columns[index]] = index
155
210
  index += 1
156
211
  end
157
- hash
212
+ hash.freeze
213
+ end
214
+ end
215
+
216
+ def indexed_rows # :nodoc:
217
+ @indexed_rows ||= begin
218
+ columns = column_indexes
219
+ @rows.map { |row| IndexedRow.new(columns, row) }.freeze
158
220
  end
159
221
  end
160
222
 
@@ -105,12 +105,13 @@ module ActiveRecord
105
105
  # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
106
106
  # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
107
107
  def sanitize_sql_hash_for_assignment(attrs, table)
108
- c = connection
109
- attrs.map do |attr, value|
110
- type = type_for_attribute(attr)
111
- value = type.serialize(type.cast(value))
112
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
- end.join(", ")
108
+ with_connection do |c|
109
+ attrs.map do |attr, value|
110
+ type = type_for_attribute(attr)
111
+ value = type.serialize(type.cast(value))
112
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
113
+ end.join(", ")
114
+ end
114
115
  end
115
116
 
116
117
  # Sanitizes a +string+ so that it is safe to use within an SQL
@@ -63,6 +63,7 @@ module ActiveRecord
63
63
  extensions(stream)
64
64
  types(stream)
65
65
  tables(stream)
66
+ virtual_tables(stream)
66
67
  trailer(stream)
67
68
  stream
68
69
  end
@@ -126,6 +127,10 @@ module ActiveRecord
126
127
  def schemas(stream)
127
128
  end
128
129
 
130
+ # virtual tables are only supported by SQLite
131
+ def virtual_tables(stream)
132
+ end
133
+
129
134
  def tables(stream)
130
135
  sorted_tables = @connection.tables.sort
131
136
 
@@ -202,12 +207,17 @@ module ActiveRecord
202
207
  end
203
208
 
204
209
  indexes_in_create(table, tbl)
205
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
206
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
207
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
208
213
 
209
214
  tbl.puts " end"
210
215
 
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
220
+
211
221
  stream.print tbl.string
212
222
  rescue => e
213
223
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -272,24 +282,37 @@ module ActiveRecord
272
282
 
273
283
  def check_constraints_in_create(table, stream)
274
284
  if (check_constraints = @connection.check_constraints(table)).any?
275
- add_check_constraint_statements = check_constraints.map do |check_constraint|
276
- parts = [
277
- "t.check_constraint #{check_constraint.expression.inspect}"
278
- ]
285
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
279
286
 
280
- if check_constraint.export_name_on_schema_dump?
281
- parts << "name: #{check_constraint.name.inspect}"
287
+ unless check_valid.empty?
288
+ check_constraint_statements = check_valid.map do |check|
289
+ " t.check_constraint #{check_parts(check).join(', ')}"
282
290
  end
283
291
 
284
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
285
-
286
- " #{parts.join(', ')}"
292
+ stream.puts check_constraint_statements.sort.join("\n")
287
293
  end
288
294
 
289
- stream.puts add_check_constraint_statements.sort.join("\n")
295
+ unless check_invalid.empty?
296
+ remaining = StringIO.new
297
+ table_name = remove_prefix_and_suffix(table).inspect
298
+
299
+ add_check_constraint_statements = check_invalid.map do |check|
300
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
301
+ end
302
+
303
+ remaining.puts add_check_constraint_statements.sort.join("\n")
304
+ remaining
305
+ end
290
306
  end
291
307
  end
292
308
 
309
+ def check_parts(check)
310
+ check_parts = [ check.expression.inspect ]
311
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
312
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
313
+ check_parts
314
+ end
315
+
293
316
  def foreign_keys(table, stream)
294
317
  if (foreign_keys = @connection.foreign_keys(table)).any?
295
318
  add_foreign_key_statements = foreign_keys.map do |foreign_key|