activerecord 7.2.3 → 8.0.4

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +391 -958
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -0
  5. data/lib/active_record/associations/association.rb +34 -10
  6. data/lib/active_record/associations/builder/association.rb +7 -6
  7. data/lib/active_record/associations/collection_association.rb +1 -1
  8. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  9. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  10. data/lib/active_record/associations/preloader/association.rb +2 -2
  11. data/lib/active_record/associations/singular_association.rb +8 -3
  12. data/lib/active_record/associations.rb +34 -4
  13. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  14. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  15. data/lib/active_record/attribute_methods/query.rb +34 -0
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
  17. data/lib/active_record/autosave_association.rb +69 -27
  18. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
  19. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +6 -15
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
  31. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  32. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  33. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
  38. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  40. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +46 -96
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
  48. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  49. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  50. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
  51. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  52. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  55. data/lib/active_record/connection_adapters.rb +0 -56
  56. data/lib/active_record/connection_handling.rb +23 -1
  57. data/lib/active_record/core.rb +29 -14
  58. data/lib/active_record/database_configurations/database_config.rb +4 -0
  59. data/lib/active_record/database_configurations/hash_config.rb +16 -2
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  63. data/lib/active_record/encryption/encryptor.rb +16 -8
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +9 -22
  67. data/lib/active_record/errors.rb +13 -5
  68. data/lib/active_record/fixtures.rb +0 -2
  69. data/lib/active_record/future_result.rb +13 -9
  70. data/lib/active_record/gem_version.rb +3 -3
  71. data/lib/active_record/insert_all.rb +1 -1
  72. data/lib/active_record/locking/optimistic.rb +1 -1
  73. data/lib/active_record/log_subscriber.rb +5 -11
  74. data/lib/active_record/migration/command_recorder.rb +31 -11
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +38 -42
  77. data/lib/active_record/model_schema.rb +3 -4
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_logs.rb +102 -50
  81. data/lib/active_record/query_logs_formatter.rb +17 -28
  82. data/lib/active_record/querying.rb +8 -8
  83. data/lib/active_record/railtie.rb +2 -26
  84. data/lib/active_record/railties/databases.rake +11 -35
  85. data/lib/active_record/reflection.rb +18 -21
  86. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  87. data/lib/active_record/relation/batches.rb +132 -72
  88. data/lib/active_record/relation/calculations.rb +40 -39
  89. data/lib/active_record/relation/delegation.rb +25 -14
  90. data/lib/active_record/relation/finder_methods.rb +18 -18
  91. data/lib/active_record/relation/merger.rb +8 -8
  92. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  93. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  94. data/lib/active_record/relation/predicate_builder.rb +13 -0
  95. data/lib/active_record/relation/query_methods.rb +105 -61
  96. data/lib/active_record/relation/spawn_methods.rb +7 -7
  97. data/lib/active_record/relation.rb +79 -61
  98. data/lib/active_record/result.rb +66 -4
  99. data/lib/active_record/sanitization.rb +7 -6
  100. data/lib/active_record/schema_dumper.rb +5 -0
  101. data/lib/active_record/schema_migration.rb +2 -1
  102. data/lib/active_record/scoping/named.rb +5 -2
  103. data/lib/active_record/statement_cache.rb +14 -14
  104. data/lib/active_record/store.rb +7 -3
  105. data/lib/active_record/table_metadata.rb +1 -3
  106. data/lib/active_record/tasks/database_tasks.rb +69 -60
  107. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  108. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
  109. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  110. data/lib/active_record/test_databases.rb +1 -1
  111. data/lib/active_record/test_fixtures.rb +12 -0
  112. data/lib/active_record/token_for.rb +1 -1
  113. data/lib/active_record/transactions.rb +5 -6
  114. data/lib/active_record/validations/uniqueness.rb +8 -8
  115. data/lib/active_record.rb +21 -48
  116. data/lib/arel/collectors/bind.rb +2 -2
  117. data/lib/arel/collectors/sql_string.rb +1 -1
  118. data/lib/arel/collectors/substitute_binds.rb +2 -2
  119. data/lib/arel/nodes/binary.rb +1 -1
  120. data/lib/arel/nodes/node.rb +1 -1
  121. data/lib/arel/nodes/sql_literal.rb +1 -1
  122. data/lib/arel/table.rb +3 -7
  123. metadata +9 -10
  124. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -7,7 +7,7 @@ require "active_record/relation/merger"
7
7
  module ActiveRecord
8
8
  module SpawnMethods
9
9
  def spawn # :nodoc:
10
- already_in_scope?(klass.scope_registry) ? klass.all : clone
10
+ already_in_scope?(model.scope_registry) ? model.all : clone
11
11
  end
12
12
 
13
13
  # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
@@ -52,18 +52,18 @@ module ActiveRecord
52
52
  end
53
53
  end
54
54
 
55
- # Removes from the query the condition(s) specified in +skips+.
55
+ # Removes the condition(s) specified in +skips+ from the query.
56
56
  #
57
- # Post.order('id asc').except(:order) # discards the order condition
58
- # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
57
+ # Post.order('id asc').except(:order) # removes the order condition
58
+ # Post.where('id > 10').order('id asc').except(:where) # removes the where condition but keeps the order
59
59
  def except(*skips)
60
60
  relation_with values.except(*skips)
61
61
  end
62
62
 
63
- # Removes any condition from the query other than the one(s) specified in +onlies+.
63
+ # Keeps only the condition(s) specified in +onlies+ in the query, removing all others.
64
64
  #
65
- # Post.order('id asc').only(:where) # discards the order condition
66
- # Post.order('id asc').only(:where, :order) # uses the specified order
65
+ # Post.order('id asc').only(:where) # keeps only the where condition, removes the order
66
+ # Post.order('id asc').only(:where, :order) # keeps only the where and order conditions
67
67
  def only(*onlies)
68
68
  relation_with values.slice(*onlies)
69
69
  end
@@ -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
@@ -440,14 +447,14 @@ module ActiveRecord
440
447
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
441
448
  def cache_key(timestamp_column = "updated_at")
442
449
  @cache_keys ||= {}
443
- @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
450
+ @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
444
451
  end
445
452
 
446
453
  def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
447
454
  query_signature = ActiveSupport::Digest.hexdigest(to_sql)
448
- key = "#{klass.model_name.cache_key}/query-#{query_signature}"
455
+ key = "#{model.model_name.cache_key}/query-#{query_signature}"
449
456
 
450
- if collection_cache_versioning
457
+ if model.collection_cache_versioning
451
458
  key
452
459
  else
453
460
  "#{key}-#{compute_cache_version(timestamp_column)}"
@@ -466,7 +473,7 @@ module ActiveRecord
466
473
  #
467
474
  # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
468
475
  def cache_version(timestamp_column = :updated_at)
469
- if collection_cache_versioning
476
+ if model.collection_cache_versioning
470
477
  @cache_versions ||= {}
471
478
  @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
472
479
  end
@@ -485,7 +492,7 @@ module ActiveRecord
485
492
 
486
493
  with_connection do |c|
487
494
  column = c.visitor.compile(table[timestamp_column])
488
- 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"
489
496
 
490
497
  if collection.has_limit_or_offset?
491
498
  query = collection.select("#{column} AS collection_cache_key_timestamp")
@@ -502,7 +509,7 @@ module ActiveRecord
502
509
  size, timestamp = c.select_rows(arel, nil).first
503
510
 
504
511
  if size
505
- column_type = klass.type_for_attribute(timestamp_column)
512
+ column_type = model.type_for_attribute(timestamp_column)
506
513
  timestamp = column_type.deserialize(timestamp)
507
514
  else
508
515
  size = 0
@@ -511,7 +518,7 @@ module ActiveRecord
511
518
  end
512
519
 
513
520
  if timestamp
514
- "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
521
+ "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
515
522
  else
516
523
  "#{size}"
517
524
  end
@@ -542,7 +549,7 @@ module ActiveRecord
542
549
  # Please check unscoped if you want to remove all previous scopes (including
543
550
  # the default_scope) during the execution of a block.
544
551
  def scoping(all_queries: nil, &block)
545
- registry = klass.scope_registry
552
+ registry = model.scope_registry
546
553
  if global_scope?(registry) && all_queries == false
547
554
  raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
548
555
  elsif already_in_scope?(registry)
@@ -553,11 +560,11 @@ module ActiveRecord
553
560
  end
554
561
 
555
562
  def _exec_scope(...) # :nodoc:
556
- @delegate_to_klass = true
557
- registry = klass.scope_registry
563
+ @delegate_to_model = true
564
+ registry = model.scope_registry
558
565
  _scoping(nil, registry) { instance_exec(...) || self }
559
566
  ensure
560
- @delegate_to_klass = false
567
+ @delegate_to_model = false
561
568
  end
562
569
 
563
570
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
@@ -594,30 +601,30 @@ module ActiveRecord
594
601
  return 0 if @none
595
602
 
596
603
  if updates.is_a?(Hash)
597
- if klass.locking_enabled? &&
598
- !updates.key?(klass.locking_column) &&
599
- !updates.key?(klass.locking_column.to_sym)
600
- 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]
601
608
  updates[attr.name] = _increment_attribute(attr)
602
609
  end
603
610
  values = _substitute_values(updates)
604
611
  else
605
- values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
612
+ values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
606
613
  end
607
614
 
608
- klass.with_connection do |c|
615
+ model.with_connection do |c|
609
616
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
610
617
  arel.source.left = table
611
618
 
612
619
  group_values_arel_columns = arel_columns(group_values.uniq)
613
620
  having_clause_ast = having_clause.ast unless having_clause.empty?
614
- key = if klass.composite_primary_key?
621
+ key = if model.composite_primary_key?
615
622
  primary_key.map { |pk| table[pk] }
616
623
  else
617
624
  table[primary_key]
618
625
  end
619
626
  stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
620
- c.update(stmt, "#{klass} Update All").tap { reset }
627
+ c.update(stmt, "#{model} Update All").tap { reset }
621
628
  end
622
629
  end
623
630
 
@@ -625,7 +632,7 @@ module ActiveRecord
625
632
  if id == :all
626
633
  each { |record| record.update(attributes) }
627
634
  else
628
- klass.update(id, attributes)
635
+ model.update(id, attributes)
629
636
  end
630
637
  end
631
638
 
@@ -633,7 +640,7 @@ module ActiveRecord
633
640
  if id == :all
634
641
  each { |record| record.update!(attributes) }
635
642
  else
636
- klass.update!(id, attributes)
643
+ model.update!(id, attributes)
637
644
  end
638
645
  end
639
646
 
@@ -939,7 +946,7 @@ module ActiveRecord
939
946
  names = touch if touch != true
940
947
  names = Array.wrap(names)
941
948
  options = names.extract_options!
942
- touch_updates = klass.touch_attributes_with_time(*names, **options)
949
+ touch_updates = model.touch_attributes_with_time(*names, **options)
943
950
  updates.merge!(touch_updates) unless touch_updates.empty?
944
951
  end
945
952
 
@@ -970,7 +977,7 @@ module ActiveRecord
970
977
  # Person.where(name: 'David').touch_all
971
978
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
972
979
  def touch_all(*names, time: nil)
973
- update_all klass.touch_attributes_with_time(*names, time: time)
980
+ update_all model.touch_attributes_with_time(*names, time: time)
974
981
  end
975
982
 
976
983
  # Destroys the records by instantiating each
@@ -1022,20 +1029,20 @@ module ActiveRecord
1022
1029
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
1023
1030
  end
1024
1031
 
1025
- klass.with_connection do |c|
1032
+ model.with_connection do |c|
1026
1033
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1027
1034
  arel.source.left = table
1028
1035
 
1029
1036
  group_values_arel_columns = arel_columns(group_values.uniq)
1030
1037
  having_clause_ast = having_clause.ast unless having_clause.empty?
1031
- key = if klass.composite_primary_key?
1038
+ key = if model.composite_primary_key?
1032
1039
  primary_key.map { |pk| table[pk] }
1033
1040
  else
1034
1041
  table[primary_key]
1035
1042
  end
1036
1043
  stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1037
1044
 
1038
- c.delete(stmt, "#{klass} Delete All").tap { reset }
1045
+ c.delete(stmt, "#{model} Delete All").tap { reset }
1039
1046
  end
1040
1047
  end
1041
1048
 
@@ -1134,9 +1141,6 @@ module ActiveRecord
1134
1141
  # for queries to actually be executed concurrently. Otherwise it defaults to
1135
1142
  # executing them in the foreground.
1136
1143
  #
1137
- # +load_async+ will also fall back to executing in the foreground in the test environment when transactional
1138
- # fixtures are enabled.
1139
- #
1140
1144
  # If the query was actually executed in the background, the Active Record logs will show
1141
1145
  # it by prefixing the log line with <tt>ASYNC</tt>:
1142
1146
  #
@@ -1146,7 +1150,7 @@ module ActiveRecord
1146
1150
  return load if !c.async_enabled?
1147
1151
 
1148
1152
  unless loaded?
1149
- result = exec_main_query(async: c.current_transaction.closed?)
1153
+ result = exec_main_query(async: !c.current_transaction.joinable?)
1150
1154
 
1151
1155
  if result.is_a?(Array)
1152
1156
  @records = result
@@ -1160,6 +1164,16 @@ module ActiveRecord
1160
1164
  self
1161
1165
  end
1162
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
+
1163
1177
  # Returns <tt>true</tt> if the relation was scheduled on the background
1164
1178
  # thread pool.
1165
1179
  def scheduled?
@@ -1190,7 +1204,7 @@ module ActiveRecord
1190
1204
  def reset
1191
1205
  @future_result&.cancel
1192
1206
  @future_result = nil
1193
- @delegate_to_klass = false
1207
+ @delegate_to_model = false
1194
1208
  @to_sql = @arel = @loaded = @should_eager_load = nil
1195
1209
  @offsets = @take = nil
1196
1210
  @cache_keys = nil
@@ -1210,7 +1224,7 @@ module ActiveRecord
1210
1224
  relation.to_sql
1211
1225
  end
1212
1226
  else
1213
- klass.with_connection do |conn|
1227
+ model.with_connection do |conn|
1214
1228
  conn.unprepared_statement { conn.to_sql(arel) }
1215
1229
  end
1216
1230
  end
@@ -1220,12 +1234,12 @@ module ActiveRecord
1220
1234
  #
1221
1235
  # User.where(name: 'Oscar').where_values_hash
1222
1236
  # # => {name: "Oscar"}
1223
- def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
1237
+ def where_values_hash(relation_table_name = model.table_name) # :nodoc:
1224
1238
  where_clause.to_h(relation_table_name)
1225
1239
  end
1226
1240
 
1227
1241
  def scope_for_create
1228
- hash = where_clause.to_h(klass.table_name, equality_only: true)
1242
+ hash = where_clause.to_h(model.table_name, equality_only: true)
1229
1243
  create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
1230
1244
  hash
1231
1245
  end
@@ -1271,6 +1285,10 @@ module ActiveRecord
1271
1285
  records.blank?
1272
1286
  end
1273
1287
 
1288
+ def readonly?
1289
+ readonly_value
1290
+ end
1291
+
1274
1292
  def values
1275
1293
  @values.dup
1276
1294
  end
@@ -1289,7 +1307,7 @@ module ActiveRecord
1289
1307
  end
1290
1308
 
1291
1309
  def empty_scope? # :nodoc:
1292
- @values == klass.unscoped.values
1310
+ @values == model.unscoped.values
1293
1311
  end
1294
1312
 
1295
1313
  def has_limit_or_offset? # :nodoc:
@@ -1297,7 +1315,7 @@ module ActiveRecord
1297
1315
  end
1298
1316
 
1299
1317
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
1300
- ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
1318
+ ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
1301
1319
  end
1302
1320
 
1303
1321
  class StrictLoadingScope # :nodoc:
@@ -1327,46 +1345,46 @@ module ActiveRecord
1327
1345
 
1328
1346
  private
1329
1347
  def already_in_scope?(registry)
1330
- @delegate_to_klass && registry.current_scope(klass, true)
1348
+ @delegate_to_model && registry.current_scope(model, true)
1331
1349
  end
1332
1350
 
1333
1351
  def global_scope?(registry)
1334
- registry.global_current_scope(klass, true)
1352
+ registry.global_current_scope(model, true)
1335
1353
  end
1336
1354
 
1337
1355
  def current_scope_restoring_block(&block)
1338
- current_scope = klass.current_scope(true)
1356
+ current_scope = model.current_scope(true)
1339
1357
  -> record do
1340
- klass.current_scope = current_scope
1358
+ model.current_scope = current_scope
1341
1359
  yield record if block_given?
1342
1360
  end
1343
1361
  end
1344
1362
 
1345
1363
  def _new(attributes, &block)
1346
- klass.new(attributes, &block)
1364
+ model.new(attributes, &block)
1347
1365
  end
1348
1366
 
1349
1367
  def _create(attributes, &block)
1350
- klass.create(attributes, &block)
1368
+ model.create(attributes, &block)
1351
1369
  end
1352
1370
 
1353
1371
  def _create!(attributes, &block)
1354
- klass.create!(attributes, &block)
1372
+ model.create!(attributes, &block)
1355
1373
  end
1356
1374
 
1357
1375
  def _scoping(scope, registry, all_queries = false)
1358
- previous = registry.current_scope(klass, true)
1359
- registry.set_current_scope(klass, scope)
1376
+ previous = registry.current_scope(model, true)
1377
+ registry.set_current_scope(model, scope)
1360
1378
 
1361
1379
  if all_queries
1362
- previous_global = registry.global_current_scope(klass, true)
1363
- registry.set_global_current_scope(klass, scope)
1380
+ previous_global = registry.global_current_scope(model, true)
1381
+ registry.set_global_current_scope(model, scope)
1364
1382
  end
1365
1383
  yield
1366
1384
  ensure
1367
- registry.set_current_scope(klass, previous)
1385
+ registry.set_current_scope(model, previous)
1368
1386
  if all_queries
1369
- registry.set_global_current_scope(klass, previous_global)
1387
+ registry.set_global_current_scope(model, previous_global)
1370
1388
  end
1371
1389
  end
1372
1390
 
@@ -1378,7 +1396,7 @@ module ActiveRecord
1378
1396
  value = Arel::Nodes::Grouping.new(value)
1379
1397
  end
1380
1398
  else
1381
- type = klass.type_for_attribute(attr.name)
1399
+ type = model.type_for_attribute(attr.name)
1382
1400
  value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
1383
1401
  end
1384
1402
  [attr, value]
@@ -1425,7 +1443,7 @@ module ActiveRecord
1425
1443
  if where_clause.contradiction?
1426
1444
  [].freeze
1427
1445
  elsif eager_loading?
1428
- klass.with_connection do |c|
1446
+ model.with_connection do |c|
1429
1447
  apply_join_dependency do |relation, join_dependency|
1430
1448
  if relation.null_relation?
1431
1449
  [].freeze
@@ -1437,8 +1455,8 @@ module ActiveRecord
1437
1455
  end
1438
1456
  end
1439
1457
  else
1440
- klass.with_connection do |c|
1441
- klass._query_by_sql(c, arel, async: async)
1458
+ model.with_connection do |c|
1459
+ model._query_by_sql(c, arel, async: async)
1442
1460
  end
1443
1461
  end
1444
1462
  end
@@ -1451,13 +1469,13 @@ module ActiveRecord
1451
1469
  @_join_dependency = nil
1452
1470
  records
1453
1471
  else
1454
- klass._load_from_sql(rows, &block).freeze
1472
+ model._load_from_sql(rows, &block).freeze
1455
1473
  end
1456
1474
  end
1457
1475
 
1458
1476
  def skip_query_cache_if_necessary(&block)
1459
1477
  if skip_query_cache_value
1460
- uncached(&block)
1478
+ model.uncached(&block)
1461
1479
  else
1462
1480
  yield
1463
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
 
@@ -34,7 +34,8 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def delete_all_versions
37
- @pool.with_connection do |connection|
37
+ # Eagerly check in connection to avoid checking in/out many times in the called method.
38
+ @pool.with_connection do
38
39
  versions.each do |version|
39
40
  delete_version(version)
40
41
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  scope = current_scope
24
24
 
25
25
  if scope
26
- if self == scope.klass
26
+ if self == scope.model
27
27
  scope.clone
28
28
  else
29
29
  relation.merge!(scope)
@@ -191,7 +191,10 @@ module ActiveRecord
191
191
  private
192
192
  def singleton_method_added(name)
193
193
  super
194
- generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
194
+ # Most Kernel extends are both singleton and instance methods so
195
+ # respond_to is a fast check, but we don't want to define methods
196
+ # only on the module (ex. Module#name)
197
+ generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
195
198
  end
196
199
  end
197
200
  end
@@ -74,13 +74,13 @@ module ActiveRecord
74
74
  self
75
75
  end
76
76
 
77
- def add_bind(obj)
77
+ def add_bind(obj, &)
78
78
  @binds << obj
79
79
  @parts << Substitute.new
80
80
  self
81
81
  end
82
82
 
83
- def add_binds(binds, proc_for_binds = nil)
83
+ def add_binds(binds, proc_for_binds = nil, &)
84
84
  @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
85
85
  binds.size.times do |i|
86
86
  @parts << ", " unless i == 0
@@ -133,23 +133,26 @@ module ActiveRecord
133
133
  relation = (callable || block).call Params.new
134
134
  query_builder, binds = connection.cacheable_query(self, relation.arel)
135
135
  bind_map = BindMap.new(binds)
136
- new(query_builder, bind_map, relation.klass)
136
+ new(query_builder, bind_map, relation.model)
137
137
  end
138
138
 
139
- def initialize(query_builder, bind_map, klass)
139
+ def initialize(query_builder, bind_map, model)
140
140
  @query_builder = query_builder
141
141
  @bind_map = bind_map
142
- @klass = klass
142
+ @model = model
143
143
  end
144
144
 
145
- def execute(params, connection, allow_retry: false, &block)
146
- bind_values = bind_map.bind params
145
+ def execute(params, connection, allow_retry: false, async: false, &block)
146
+ bind_values = @bind_map.bind params
147
+ sql = @query_builder.sql_for bind_values, connection
147
148
 
148
- sql = query_builder.sql_for bind_values, connection
149
-
150
- klass.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
149
+ if async
150
+ @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
151
+ else
152
+ @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
153
+ end
151
154
  rescue ::RangeError
152
- []
155
+ async ? Promise.wrap([]) : []
153
156
  end
154
157
 
155
158
  def self.unsupported_value?(value)
@@ -157,8 +160,5 @@ module ActiveRecord
157
160
  when NilClass, Array, Range, Hash, Relation, Base then true
158
161
  end
159
162
  end
160
-
161
- private
162
- attr_reader :query_builder, :bind_map, :klass
163
163
  end
164
164
  end