activerecord 7.2.0 → 8.0.0.beta1

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +189 -745
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +25 -5
  5. data/lib/active_record/associations/builder/association.rb +7 -6
  6. data/lib/active_record/associations/collection_association.rb +10 -8
  7. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  8. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +5 -5
  11. data/lib/active_record/associations/preloader/association.rb +2 -2
  12. data/lib/active_record/associations/singular_association.rb +8 -3
  13. data/lib/active_record/associations.rb +34 -4
  14. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  15. data/lib/active_record/attribute_assignment.rb +9 -1
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attributes.rb +6 -5
  18. data/lib/active_record/autosave_association.rb +69 -27
  19. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  20. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
  25. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  26. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  27. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
  28. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  29. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
  30. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
  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 +44 -46
  34. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  35. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  36. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  37. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  38. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  40. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
  42. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  43. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  44. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  45. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  46. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  47. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
  48. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  49. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  50. data/lib/active_record/connection_handling.rb +22 -0
  51. data/lib/active_record/core.rb +16 -9
  52. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  53. data/lib/active_record/encryption/config.rb +3 -1
  54. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  55. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  56. data/lib/active_record/encryption/encryptor.rb +16 -9
  57. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  58. data/lib/active_record/encryption/key_provider.rb +1 -1
  59. data/lib/active_record/encryption/scheme.rb +8 -1
  60. data/lib/active_record/encryption.rb +2 -0
  61. data/lib/active_record/enum.rb +8 -0
  62. data/lib/active_record/errors.rb +13 -5
  63. data/lib/active_record/fixtures.rb +0 -1
  64. data/lib/active_record/future_result.rb +14 -10
  65. data/lib/active_record/gem_version.rb +3 -3
  66. data/lib/active_record/insert_all.rb +1 -1
  67. data/lib/active_record/migration/command_recorder.rb +22 -5
  68. data/lib/active_record/migration/compatibility.rb +5 -2
  69. data/lib/active_record/migration.rb +35 -33
  70. data/lib/active_record/model_schema.rb +6 -3
  71. data/lib/active_record/nested_attributes.rb +11 -2
  72. data/lib/active_record/persistence.rb +128 -130
  73. data/lib/active_record/query_logs.rb +97 -39
  74. data/lib/active_record/query_logs_formatter.rb +17 -28
  75. data/lib/active_record/querying.rb +6 -6
  76. data/lib/active_record/railtie.rb +8 -14
  77. data/lib/active_record/reflection.rb +19 -10
  78. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  79. data/lib/active_record/relation/batches.rb +135 -75
  80. data/lib/active_record/relation/calculations.rb +24 -19
  81. data/lib/active_record/relation/delegation.rb +25 -14
  82. data/lib/active_record/relation/finder_methods.rb +18 -18
  83. data/lib/active_record/relation/merger.rb +8 -8
  84. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  85. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  86. data/lib/active_record/relation/predicate_builder.rb +6 -1
  87. data/lib/active_record/relation/query_methods.rb +58 -37
  88. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  89. data/lib/active_record/relation/spawn_methods.rb +1 -1
  90. data/lib/active_record/relation.rb +72 -61
  91. data/lib/active_record/result.rb +68 -7
  92. data/lib/active_record/sanitization.rb +7 -6
  93. data/lib/active_record/schema_dumper.rb +5 -0
  94. data/lib/active_record/schema_migration.rb +2 -1
  95. data/lib/active_record/scoping/named.rb +6 -2
  96. data/lib/active_record/statement_cache.rb +12 -12
  97. data/lib/active_record/store.rb +7 -3
  98. data/lib/active_record/tasks/database_tasks.rb +36 -16
  99. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  100. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  101. data/lib/active_record/test_fixtures.rb +12 -0
  102. data/lib/active_record/token_for.rb +1 -1
  103. data/lib/active_record/validations/uniqueness.rb +9 -8
  104. data/lib/active_record.rb +15 -0
  105. data/lib/arel/collectors/bind.rb +1 -1
  106. metadata +14 -14
@@ -68,19 +68,19 @@ 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: model.arel_table, predicate_builder: model.predicate_builder, values: {})
78
+ @model = model
79
79
  @table = table
80
80
  @values = values
81
81
  @loaded = false
82
82
  @predicate_builder = predicate_builder
83
- @delegate_to_klass = false
83
+ @delegate_to_model = false
84
84
  @future_result = nil
85
85
  @records = nil
86
86
  @async = false
@@ -93,7 +93,7 @@ module ActiveRecord
93
93
  end
94
94
 
95
95
  def bind_attribute(name, value) # :nodoc:
96
- if reflection = klass._reflect_on_association(name)
96
+ if reflection = model._reflect_on_association(name)
97
97
  name = reflection.foreign_key
98
98
  value = value.read_attribute(reflection.association_primary_key) unless value.nil?
99
99
  end
@@ -430,14 +430,14 @@ module ActiveRecord
430
430
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
431
431
  def cache_key(timestamp_column = "updated_at")
432
432
  @cache_keys ||= {}
433
- @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
433
+ @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
434
434
  end
435
435
 
436
436
  def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
437
437
  query_signature = ActiveSupport::Digest.hexdigest(to_sql)
438
- key = "#{klass.model_name.cache_key}/query-#{query_signature}"
438
+ key = "#{model.model_name.cache_key}/query-#{query_signature}"
439
439
 
440
- if collection_cache_versioning
440
+ if model.collection_cache_versioning
441
441
  key
442
442
  else
443
443
  "#{key}-#{compute_cache_version(timestamp_column)}"
@@ -456,7 +456,7 @@ module ActiveRecord
456
456
  #
457
457
  # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
458
458
  def cache_version(timestamp_column = :updated_at)
459
- if collection_cache_versioning
459
+ if model.collection_cache_versioning
460
460
  @cache_versions ||= {}
461
461
  @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
462
462
  end
@@ -475,7 +475,7 @@ module ActiveRecord
475
475
 
476
476
  with_connection do |c|
477
477
  column = c.visitor.compile(table[timestamp_column])
478
- select_values = "COUNT(*) AS #{adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
478
+ select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
479
479
 
480
480
  if collection.has_limit_or_offset?
481
481
  query = collection.select("#{column} AS collection_cache_key_timestamp")
@@ -492,7 +492,7 @@ module ActiveRecord
492
492
  size, timestamp = c.select_rows(arel, nil).first
493
493
 
494
494
  if size
495
- column_type = klass.type_for_attribute(timestamp_column)
495
+ column_type = model.type_for_attribute(timestamp_column)
496
496
  timestamp = column_type.deserialize(timestamp)
497
497
  else
498
498
  size = 0
@@ -501,7 +501,7 @@ module ActiveRecord
501
501
  end
502
502
 
503
503
  if timestamp
504
- "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
504
+ "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
505
505
  else
506
506
  "#{size}"
507
507
  end
@@ -532,7 +532,7 @@ module ActiveRecord
532
532
  # Please check unscoped if you want to remove all previous scopes (including
533
533
  # the default_scope) during the execution of a block.
534
534
  def scoping(all_queries: nil, &block)
535
- registry = klass.scope_registry
535
+ registry = model.scope_registry
536
536
  if global_scope?(registry) && all_queries == false
537
537
  raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
538
538
  elsif already_in_scope?(registry)
@@ -543,11 +543,11 @@ module ActiveRecord
543
543
  end
544
544
 
545
545
  def _exec_scope(...) # :nodoc:
546
- @delegate_to_klass = true
547
- registry = klass.scope_registry
546
+ @delegate_to_model = true
547
+ registry = model.scope_registry
548
548
  _scoping(nil, registry) { instance_exec(...) || self }
549
549
  ensure
550
- @delegate_to_klass = false
550
+ @delegate_to_model = false
551
551
  end
552
552
 
553
553
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
@@ -584,30 +584,30 @@ module ActiveRecord
584
584
  return 0 if @none
585
585
 
586
586
  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]
587
+ if model.locking_enabled? &&
588
+ !updates.key?(model.locking_column) &&
589
+ !updates.key?(model.locking_column.to_sym)
590
+ attr = table[model.locking_column]
591
591
  updates[attr.name] = _increment_attribute(attr)
592
592
  end
593
593
  values = _substitute_values(updates)
594
594
  else
595
- values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
595
+ values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
596
596
  end
597
597
 
598
- klass.with_connection do |c|
598
+ model.with_connection do |c|
599
599
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
600
600
  arel.source.left = table
601
601
 
602
602
  group_values_arel_columns = arel_columns(group_values.uniq)
603
603
  having_clause_ast = having_clause.ast unless having_clause.empty?
604
- key = if klass.composite_primary_key?
604
+ key = if model.composite_primary_key?
605
605
  primary_key.map { |pk| table[pk] }
606
606
  else
607
607
  table[primary_key]
608
608
  end
609
609
  stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
610
- c.update(stmt, "#{klass} Update All").tap { reset }
610
+ c.update(stmt, "#{model} Update All").tap { reset }
611
611
  end
612
612
  end
613
613
 
@@ -615,7 +615,7 @@ module ActiveRecord
615
615
  if id == :all
616
616
  each { |record| record.update(attributes) }
617
617
  else
618
- klass.update(id, attributes)
618
+ model.update(id, attributes)
619
619
  end
620
620
  end
621
621
 
@@ -623,7 +623,7 @@ module ActiveRecord
623
623
  if id == :all
624
624
  each { |record| record.update!(attributes) }
625
625
  else
626
- klass.update!(id, attributes)
626
+ model.update!(id, attributes)
627
627
  end
628
628
  end
629
629
 
@@ -929,7 +929,7 @@ module ActiveRecord
929
929
  names = touch if touch != true
930
930
  names = Array.wrap(names)
931
931
  options = names.extract_options!
932
- touch_updates = klass.touch_attributes_with_time(*names, **options)
932
+ touch_updates = model.touch_attributes_with_time(*names, **options)
933
933
  updates.merge!(touch_updates) unless touch_updates.empty?
934
934
  end
935
935
 
@@ -960,7 +960,7 @@ module ActiveRecord
960
960
  # Person.where(name: 'David').touch_all
961
961
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
962
962
  def touch_all(*names, time: nil)
963
- update_all klass.touch_attributes_with_time(*names, time: time)
963
+ update_all model.touch_attributes_with_time(*names, time: time)
964
964
  end
965
965
 
966
966
  # Destroys the records by instantiating each
@@ -1012,20 +1012,20 @@ module ActiveRecord
1012
1012
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
1013
1013
  end
1014
1014
 
1015
- klass.with_connection do |c|
1015
+ model.with_connection do |c|
1016
1016
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1017
1017
  arel.source.left = table
1018
1018
 
1019
1019
  group_values_arel_columns = arel_columns(group_values.uniq)
1020
1020
  having_clause_ast = having_clause.ast unless having_clause.empty?
1021
- key = if klass.composite_primary_key?
1021
+ key = if model.composite_primary_key?
1022
1022
  primary_key.map { |pk| table[pk] }
1023
1023
  else
1024
1024
  table[primary_key]
1025
1025
  end
1026
1026
  stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1027
1027
 
1028
- c.delete(stmt, "#{klass} Delete All").tap { reset }
1028
+ c.delete(stmt, "#{model} Delete All").tap { reset }
1029
1029
  end
1030
1030
  end
1031
1031
 
@@ -1124,9 +1124,6 @@ module ActiveRecord
1124
1124
  # for queries to actually be executed concurrently. Otherwise it defaults to
1125
1125
  # executing them in the foreground.
1126
1126
  #
1127
- # +load_async+ will also fall back to executing in the foreground in the test environment when transactional
1128
- # fixtures are enabled.
1129
- #
1130
1127
  # If the query was actually executed in the background, the Active Record logs will show
1131
1128
  # it by prefixing the log line with <tt>ASYNC</tt>:
1132
1129
  #
@@ -1136,7 +1133,7 @@ module ActiveRecord
1136
1133
  return load if !c.async_enabled?
1137
1134
 
1138
1135
  unless loaded?
1139
- result = exec_main_query(async: c.current_transaction.closed?)
1136
+ result = exec_main_query(async: !c.current_transaction.joinable?)
1140
1137
 
1141
1138
  if result.is_a?(Array)
1142
1139
  @records = result
@@ -1150,6 +1147,16 @@ module ActiveRecord
1150
1147
  self
1151
1148
  end
1152
1149
 
1150
+ def then(&block) # :nodoc:
1151
+ if @future_result
1152
+ @future_result.then do
1153
+ yield self
1154
+ end
1155
+ else
1156
+ super
1157
+ end
1158
+ end
1159
+
1153
1160
  # Returns <tt>true</tt> if the relation was scheduled on the background
1154
1161
  # thread pool.
1155
1162
  def scheduled?
@@ -1180,7 +1187,7 @@ module ActiveRecord
1180
1187
  def reset
1181
1188
  @future_result&.cancel
1182
1189
  @future_result = nil
1183
- @delegate_to_klass = false
1190
+ @delegate_to_model = false
1184
1191
  @to_sql = @arel = @loaded = @should_eager_load = nil
1185
1192
  @offsets = @take = nil
1186
1193
  @cache_keys = nil
@@ -1200,7 +1207,7 @@ module ActiveRecord
1200
1207
  relation.to_sql
1201
1208
  end
1202
1209
  else
1203
- klass.with_connection do |conn|
1210
+ model.with_connection do |conn|
1204
1211
  conn.unprepared_statement { conn.to_sql(arel) }
1205
1212
  end
1206
1213
  end
@@ -1210,12 +1217,12 @@ module ActiveRecord
1210
1217
  #
1211
1218
  # User.where(name: 'Oscar').where_values_hash
1212
1219
  # # => {name: "Oscar"}
1213
- def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
1220
+ def where_values_hash(relation_table_name = model.table_name) # :nodoc:
1214
1221
  where_clause.to_h(relation_table_name)
1215
1222
  end
1216
1223
 
1217
1224
  def scope_for_create
1218
- hash = where_clause.to_h(klass.table_name, equality_only: true)
1225
+ hash = where_clause.to_h(model.table_name, equality_only: true)
1219
1226
  create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
1220
1227
  hash
1221
1228
  end
@@ -1261,6 +1268,10 @@ module ActiveRecord
1261
1268
  records.blank?
1262
1269
  end
1263
1270
 
1271
+ def readonly?
1272
+ readonly_value
1273
+ end
1274
+
1264
1275
  def values
1265
1276
  @values.dup
1266
1277
  end
@@ -1279,7 +1290,7 @@ module ActiveRecord
1279
1290
  end
1280
1291
 
1281
1292
  def empty_scope? # :nodoc:
1282
- @values == klass.unscoped.values
1293
+ @values == model.unscoped.values
1283
1294
  end
1284
1295
 
1285
1296
  def has_limit_or_offset? # :nodoc:
@@ -1287,7 +1298,7 @@ module ActiveRecord
1287
1298
  end
1288
1299
 
1289
1300
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
1290
- ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
1301
+ ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
1291
1302
  end
1292
1303
 
1293
1304
  class StrictLoadingScope # :nodoc:
@@ -1317,46 +1328,46 @@ module ActiveRecord
1317
1328
 
1318
1329
  private
1319
1330
  def already_in_scope?(registry)
1320
- @delegate_to_klass && registry.current_scope(klass, true)
1331
+ @delegate_to_model && registry.current_scope(model, true)
1321
1332
  end
1322
1333
 
1323
1334
  def global_scope?(registry)
1324
- registry.global_current_scope(klass, true)
1335
+ registry.global_current_scope(model, true)
1325
1336
  end
1326
1337
 
1327
1338
  def current_scope_restoring_block(&block)
1328
- current_scope = klass.current_scope(true)
1339
+ current_scope = model.current_scope(true)
1329
1340
  -> record do
1330
- klass.current_scope = current_scope
1341
+ model.current_scope = current_scope
1331
1342
  yield record if block_given?
1332
1343
  end
1333
1344
  end
1334
1345
 
1335
1346
  def _new(attributes, &block)
1336
- klass.new(attributes, &block)
1347
+ model.new(attributes, &block)
1337
1348
  end
1338
1349
 
1339
1350
  def _create(attributes, &block)
1340
- klass.create(attributes, &block)
1351
+ model.create(attributes, &block)
1341
1352
  end
1342
1353
 
1343
1354
  def _create!(attributes, &block)
1344
- klass.create!(attributes, &block)
1355
+ model.create!(attributes, &block)
1345
1356
  end
1346
1357
 
1347
1358
  def _scoping(scope, registry, all_queries = false)
1348
- previous = registry.current_scope(klass, true)
1349
- registry.set_current_scope(klass, scope)
1359
+ previous = registry.current_scope(model, true)
1360
+ registry.set_current_scope(model, scope)
1350
1361
 
1351
1362
  if all_queries
1352
- previous_global = registry.global_current_scope(klass, true)
1353
- registry.set_global_current_scope(klass, scope)
1363
+ previous_global = registry.global_current_scope(model, true)
1364
+ registry.set_global_current_scope(model, scope)
1354
1365
  end
1355
1366
  yield
1356
1367
  ensure
1357
- registry.set_current_scope(klass, previous)
1368
+ registry.set_current_scope(model, previous)
1358
1369
  if all_queries
1359
- registry.set_global_current_scope(klass, previous_global)
1370
+ registry.set_global_current_scope(model, previous_global)
1360
1371
  end
1361
1372
  end
1362
1373
 
@@ -1368,7 +1379,7 @@ module ActiveRecord
1368
1379
  value = Arel::Nodes::Grouping.new(value)
1369
1380
  end
1370
1381
  else
1371
- type = klass.type_for_attribute(attr.name)
1382
+ type = model.type_for_attribute(attr.name)
1372
1383
  value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
1373
1384
  end
1374
1385
  [attr, value]
@@ -1415,7 +1426,7 @@ module ActiveRecord
1415
1426
  if where_clause.contradiction?
1416
1427
  [].freeze
1417
1428
  elsif eager_loading?
1418
- klass.with_connection do |c|
1429
+ model.with_connection do |c|
1419
1430
  apply_join_dependency do |relation, join_dependency|
1420
1431
  if relation.null_relation?
1421
1432
  [].freeze
@@ -1427,8 +1438,8 @@ module ActiveRecord
1427
1438
  end
1428
1439
  end
1429
1440
  else
1430
- klass.with_connection do |c|
1431
- klass._query_by_sql(c, arel, async: async)
1441
+ model.with_connection do |c|
1442
+ model._query_by_sql(c, arel, async: async)
1432
1443
  end
1433
1444
  end
1434
1445
  end
@@ -1441,13 +1452,13 @@ module ActiveRecord
1441
1452
  @_join_dependency = nil
1442
1453
  records
1443
1454
  else
1444
- klass._load_from_sql(rows, &block).freeze
1455
+ model._load_from_sql(rows, &block).freeze
1445
1456
  end
1446
1457
  end
1447
1458
 
1448
1459
  def skip_query_cache_if_necessary(&block)
1449
1460
  if skip_query_cache_value
1450
- uncached(&block)
1461
+ model.uncached(&block)
1451
1462
  else
1452
1463
  yield
1453
1464
  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,14 +120,16 @@ 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)
74
129
  if block_given?
75
- hash_rows.each(&block)
130
+ indexed_rows.each(&block)
76
131
  else
77
- hash_rows.to_enum { @rows.size }
132
+ indexed_rows.to_enum { @rows.size }
78
133
  end
79
134
  end
80
135
 
@@ -134,14 +189,13 @@ 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
- @hash_rows = nil
141
194
  end
142
195
 
143
196
  def freeze # :nodoc:
144
197
  hash_rows.freeze
198
+ indexed_rows.freeze
145
199
  super
146
200
  end
147
201
 
@@ -154,7 +208,7 @@ module ActiveRecord
154
208
  hash[columns[index]] = index
155
209
  index += 1
156
210
  end
157
- hash
211
+ hash.freeze
158
212
  end
159
213
  end
160
214
 
@@ -167,6 +221,13 @@ module ActiveRecord
167
221
  end
168
222
  end
169
223
 
224
+ def indexed_rows
225
+ @indexed_rows ||= begin
226
+ columns = column_indexes
227
+ @rows.map { |row| IndexedRow.new(columns, row) }.freeze
228
+ end
229
+ end
230
+
170
231
  def hash_rows
171
232
  # We use transform_values to rows.
172
233
  # This is faster because we avoid any reallocs and avoid hashing entirely.
@@ -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)
@@ -190,7 +190,11 @@ module ActiveRecord
190
190
 
191
191
  private
192
192
  def singleton_method_added(name)
193
- generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
193
+ super
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)
194
198
  end
195
199
  end
196
200
  end
@@ -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
@@ -25,8 +25,8 @@ module ActiveRecord
25
25
  # You can set custom coder to encode/decode your serialized attributes to/from different formats.
26
26
  # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
27
27
  #
28
- # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
29
- # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
28
+ # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, MySQL 5.7+
29
+ # +json+, or SQLite 3.38+ +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
30
30
  # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
31
31
  # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
32
32
  # using a symbol.
@@ -217,7 +217,11 @@ module ActiveRecord
217
217
  end
218
218
 
219
219
  def store_accessor_for(store_attribute)
220
- type_for_attribute(store_attribute).accessor
220
+ type_for_attribute(store_attribute).tap do |type|
221
+ unless type.respond_to?(:accessor)
222
+ raise ConfigurationError, "the column '#{store_attribute}' has not been configured as a store. Please make sure the column is declared serializable via 'ActiveRecord.store' or, if your database supports it, use a structured column type like hstore or json."
223
+ end
224
+ end.accessor
221
225
  end
222
226
 
223
227
  class HashAccessor # :nodoc: