activerecord 7.2.0 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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: