activerecord 7.2.1.1 → 8.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -756
  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 +10 -3
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +4 -4
  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/primary_key.rb +2 -7
  17. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -12
  18. data/lib/active_record/attributes.rb +1 -2
  19. data/lib/active_record/autosave_association.rb +69 -27
  20. data/lib/active_record/callbacks.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  22. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +26 -9
  25. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  26. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -4
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -26
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -42
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  35. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  36. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  37. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  38. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  42. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  43. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  44. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -11
  45. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -11
  46. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  47. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +54 -14
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -97
  49. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  51. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  52. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  53. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  55. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  56. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  57. data/lib/active_record/connection_adapters.rb +0 -56
  58. data/lib/active_record/connection_handling.rb +22 -0
  59. data/lib/active_record/core.rb +28 -18
  60. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  61. data/lib/active_record/encryption/config.rb +3 -1
  62. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  63. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  64. data/lib/active_record/encryption/encryptor.rb +15 -8
  65. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  66. data/lib/active_record/encryption/key_provider.rb +1 -1
  67. data/lib/active_record/encryption/scheme.rb +8 -1
  68. data/lib/active_record/encryption.rb +2 -0
  69. data/lib/active_record/enum.rb +54 -75
  70. data/lib/active_record/errors.rb +13 -5
  71. data/lib/active_record/fixtures.rb +0 -2
  72. data/lib/active_record/future_result.rb +14 -10
  73. data/lib/active_record/gem_version.rb +4 -4
  74. data/lib/active_record/insert_all.rb +1 -1
  75. data/lib/active_record/locking/optimistic.rb +1 -1
  76. data/lib/active_record/log_subscriber.rb +5 -11
  77. data/lib/active_record/marshalling.rb +4 -1
  78. data/lib/active_record/migration/command_recorder.rb +22 -5
  79. data/lib/active_record/migration/compatibility.rb +5 -2
  80. data/lib/active_record/migration.rb +35 -38
  81. data/lib/active_record/model_schema.rb +4 -6
  82. data/lib/active_record/nested_attributes.rb +11 -2
  83. data/lib/active_record/persistence.rb +128 -130
  84. data/lib/active_record/query_cache.rb +0 -4
  85. data/lib/active_record/query_logs.rb +102 -50
  86. data/lib/active_record/query_logs_formatter.rb +17 -28
  87. data/lib/active_record/querying.rb +8 -8
  88. data/lib/active_record/railtie.rb +9 -38
  89. data/lib/active_record/railties/databases.rake +1 -1
  90. data/lib/active_record/reflection.rb +23 -23
  91. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  92. data/lib/active_record/relation/batches.rb +132 -72
  93. data/lib/active_record/relation/calculations.rb +41 -40
  94. data/lib/active_record/relation/delegation.rb +25 -14
  95. data/lib/active_record/relation/finder_methods.rb +18 -18
  96. data/lib/active_record/relation/merger.rb +8 -8
  97. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  98. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  99. data/lib/active_record/relation/predicate_builder.rb +14 -1
  100. data/lib/active_record/relation/query_methods.rb +122 -71
  101. data/lib/active_record/relation/spawn_methods.rb +1 -1
  102. data/lib/active_record/relation.rb +79 -61
  103. data/lib/active_record/result.rb +66 -4
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +5 -0
  106. data/lib/active_record/schema_migration.rb +2 -1
  107. data/lib/active_record/scoping/named.rb +5 -2
  108. data/lib/active_record/statement_cache.rb +12 -12
  109. data/lib/active_record/store.rb +7 -3
  110. data/lib/active_record/table_metadata.rb +1 -3
  111. data/lib/active_record/tasks/database_tasks.rb +40 -47
  112. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  113. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  114. data/lib/active_record/test_fixtures.rb +12 -0
  115. data/lib/active_record/testing/query_assertions.rb +2 -2
  116. data/lib/active_record/token_for.rb +1 -1
  117. data/lib/active_record/validations/uniqueness.rb +9 -8
  118. data/lib/active_record.rb +15 -45
  119. data/lib/arel/collectors/bind.rb +1 -1
  120. data/lib/arel/table.rb +3 -7
  121. data/lib/arel/visitors/sqlite.rb +25 -0
  122. metadata +10 -11
  123. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -68,19 +68,26 @@ module ActiveRecord
68
68
  include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
69
69
  include SignedId::RelationMethods, TokenFor::RelationMethods
70
70
 
71
- attr_reader :table, :klass, :loaded, :predicate_builder
71
+ attr_reader :table, :model, :loaded, :predicate_builder
72
72
  attr_accessor :skip_preloading_value
73
- alias :model :klass
73
+ alias :klass :model
74
74
  alias :loaded? :loaded
75
75
  alias :locked? :lock_value
76
76
 
77
- def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
78
- @klass = klass
77
+ def initialize(model, table: nil, predicate_builder: nil, values: {})
78
+ if table
79
+ predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table))
80
+ else
81
+ table = model.arel_table
82
+ predicate_builder ||= model.predicate_builder
83
+ end
84
+
85
+ @model = model
79
86
  @table = table
80
87
  @values = values
81
88
  @loaded = false
82
89
  @predicate_builder = predicate_builder
83
- @delegate_to_klass = false
90
+ @delegate_to_model = false
84
91
  @future_result = nil
85
92
  @records = nil
86
93
  @async = false
@@ -93,7 +100,7 @@ module ActiveRecord
93
100
  end
94
101
 
95
102
  def bind_attribute(name, value) # :nodoc:
96
- if reflection = klass._reflect_on_association(name)
103
+ if reflection = model._reflect_on_association(name)
97
104
  name = reflection.foreign_key
98
105
  value = value.read_attribute(reflection.association_primary_key) unless value.nil?
99
106
  end
@@ -430,14 +437,14 @@ module ActiveRecord
430
437
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
431
438
  def cache_key(timestamp_column = "updated_at")
432
439
  @cache_keys ||= {}
433
- @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
440
+ @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
434
441
  end
435
442
 
436
443
  def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
437
444
  query_signature = ActiveSupport::Digest.hexdigest(to_sql)
438
- key = "#{klass.model_name.cache_key}/query-#{query_signature}"
445
+ key = "#{model.model_name.cache_key}/query-#{query_signature}"
439
446
 
440
- if collection_cache_versioning
447
+ if model.collection_cache_versioning
441
448
  key
442
449
  else
443
450
  "#{key}-#{compute_cache_version(timestamp_column)}"
@@ -456,7 +463,7 @@ module ActiveRecord
456
463
  #
457
464
  # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
458
465
  def cache_version(timestamp_column = :updated_at)
459
- if collection_cache_versioning
466
+ if model.collection_cache_versioning
460
467
  @cache_versions ||= {}
461
468
  @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
462
469
  end
@@ -475,7 +482,7 @@ module ActiveRecord
475
482
 
476
483
  with_connection do |c|
477
484
  column = c.visitor.compile(table[timestamp_column])
478
- select_values = "COUNT(*) AS #{adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
485
+ select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
479
486
 
480
487
  if collection.has_limit_or_offset?
481
488
  query = collection.select("#{column} AS collection_cache_key_timestamp")
@@ -492,7 +499,7 @@ module ActiveRecord
492
499
  size, timestamp = c.select_rows(arel, nil).first
493
500
 
494
501
  if size
495
- column_type = klass.type_for_attribute(timestamp_column)
502
+ column_type = model.type_for_attribute(timestamp_column)
496
503
  timestamp = column_type.deserialize(timestamp)
497
504
  else
498
505
  size = 0
@@ -501,7 +508,7 @@ module ActiveRecord
501
508
  end
502
509
 
503
510
  if timestamp
504
- "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
511
+ "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
505
512
  else
506
513
  "#{size}"
507
514
  end
@@ -532,7 +539,7 @@ module ActiveRecord
532
539
  # Please check unscoped if you want to remove all previous scopes (including
533
540
  # the default_scope) during the execution of a block.
534
541
  def scoping(all_queries: nil, &block)
535
- registry = klass.scope_registry
542
+ registry = model.scope_registry
536
543
  if global_scope?(registry) && all_queries == false
537
544
  raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
538
545
  elsif already_in_scope?(registry)
@@ -543,11 +550,11 @@ module ActiveRecord
543
550
  end
544
551
 
545
552
  def _exec_scope(...) # :nodoc:
546
- @delegate_to_klass = true
547
- registry = klass.scope_registry
553
+ @delegate_to_model = true
554
+ registry = model.scope_registry
548
555
  _scoping(nil, registry) { instance_exec(...) || self }
549
556
  ensure
550
- @delegate_to_klass = false
557
+ @delegate_to_model = false
551
558
  end
552
559
 
553
560
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
@@ -584,30 +591,30 @@ module ActiveRecord
584
591
  return 0 if @none
585
592
 
586
593
  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]
594
+ if model.locking_enabled? &&
595
+ !updates.key?(model.locking_column) &&
596
+ !updates.key?(model.locking_column.to_sym)
597
+ attr = table[model.locking_column]
591
598
  updates[attr.name] = _increment_attribute(attr)
592
599
  end
593
600
  values = _substitute_values(updates)
594
601
  else
595
- values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
602
+ values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
596
603
  end
597
604
 
598
- klass.with_connection do |c|
605
+ model.with_connection do |c|
599
606
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
600
607
  arel.source.left = table
601
608
 
602
609
  group_values_arel_columns = arel_columns(group_values.uniq)
603
610
  having_clause_ast = having_clause.ast unless having_clause.empty?
604
- key = if klass.composite_primary_key?
611
+ key = if model.composite_primary_key?
605
612
  primary_key.map { |pk| table[pk] }
606
613
  else
607
614
  table[primary_key]
608
615
  end
609
616
  stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
610
- c.update(stmt, "#{klass} Update All").tap { reset }
617
+ c.update(stmt, "#{model} Update All").tap { reset }
611
618
  end
612
619
  end
613
620
 
@@ -615,7 +622,7 @@ module ActiveRecord
615
622
  if id == :all
616
623
  each { |record| record.update(attributes) }
617
624
  else
618
- klass.update(id, attributes)
625
+ model.update(id, attributes)
619
626
  end
620
627
  end
621
628
 
@@ -623,7 +630,7 @@ module ActiveRecord
623
630
  if id == :all
624
631
  each { |record| record.update!(attributes) }
625
632
  else
626
- klass.update!(id, attributes)
633
+ model.update!(id, attributes)
627
634
  end
628
635
  end
629
636
 
@@ -929,7 +936,7 @@ module ActiveRecord
929
936
  names = touch if touch != true
930
937
  names = Array.wrap(names)
931
938
  options = names.extract_options!
932
- touch_updates = klass.touch_attributes_with_time(*names, **options)
939
+ touch_updates = model.touch_attributes_with_time(*names, **options)
933
940
  updates.merge!(touch_updates) unless touch_updates.empty?
934
941
  end
935
942
 
@@ -960,7 +967,7 @@ module ActiveRecord
960
967
  # Person.where(name: 'David').touch_all
961
968
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
962
969
  def touch_all(*names, time: nil)
963
- update_all klass.touch_attributes_with_time(*names, time: time)
970
+ update_all model.touch_attributes_with_time(*names, time: time)
964
971
  end
965
972
 
966
973
  # Destroys the records by instantiating each
@@ -1012,20 +1019,20 @@ module ActiveRecord
1012
1019
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
1013
1020
  end
1014
1021
 
1015
- klass.with_connection do |c|
1022
+ model.with_connection do |c|
1016
1023
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1017
1024
  arel.source.left = table
1018
1025
 
1019
1026
  group_values_arel_columns = arel_columns(group_values.uniq)
1020
1027
  having_clause_ast = having_clause.ast unless having_clause.empty?
1021
- key = if klass.composite_primary_key?
1028
+ key = if model.composite_primary_key?
1022
1029
  primary_key.map { |pk| table[pk] }
1023
1030
  else
1024
1031
  table[primary_key]
1025
1032
  end
1026
1033
  stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1027
1034
 
1028
- c.delete(stmt, "#{klass} Delete All").tap { reset }
1035
+ c.delete(stmt, "#{model} Delete All").tap { reset }
1029
1036
  end
1030
1037
  end
1031
1038
 
@@ -1124,9 +1131,6 @@ module ActiveRecord
1124
1131
  # for queries to actually be executed concurrently. Otherwise it defaults to
1125
1132
  # executing them in the foreground.
1126
1133
  #
1127
- # +load_async+ will also fall back to executing in the foreground in the test environment when transactional
1128
- # fixtures are enabled.
1129
- #
1130
1134
  # If the query was actually executed in the background, the Active Record logs will show
1131
1135
  # it by prefixing the log line with <tt>ASYNC</tt>:
1132
1136
  #
@@ -1136,7 +1140,7 @@ module ActiveRecord
1136
1140
  return load if !c.async_enabled?
1137
1141
 
1138
1142
  unless loaded?
1139
- result = exec_main_query(async: c.current_transaction.closed?)
1143
+ result = exec_main_query(async: !c.current_transaction.joinable?)
1140
1144
 
1141
1145
  if result.is_a?(Array)
1142
1146
  @records = result
@@ -1150,6 +1154,16 @@ module ActiveRecord
1150
1154
  self
1151
1155
  end
1152
1156
 
1157
+ def then(&block) # :nodoc:
1158
+ if @future_result
1159
+ @future_result.then do
1160
+ yield self
1161
+ end
1162
+ else
1163
+ super
1164
+ end
1165
+ end
1166
+
1153
1167
  # Returns <tt>true</tt> if the relation was scheduled on the background
1154
1168
  # thread pool.
1155
1169
  def scheduled?
@@ -1180,7 +1194,7 @@ module ActiveRecord
1180
1194
  def reset
1181
1195
  @future_result&.cancel
1182
1196
  @future_result = nil
1183
- @delegate_to_klass = false
1197
+ @delegate_to_model = false
1184
1198
  @to_sql = @arel = @loaded = @should_eager_load = nil
1185
1199
  @offsets = @take = nil
1186
1200
  @cache_keys = nil
@@ -1200,7 +1214,7 @@ module ActiveRecord
1200
1214
  relation.to_sql
1201
1215
  end
1202
1216
  else
1203
- klass.with_connection do |conn|
1217
+ model.with_connection do |conn|
1204
1218
  conn.unprepared_statement { conn.to_sql(arel) }
1205
1219
  end
1206
1220
  end
@@ -1210,12 +1224,12 @@ module ActiveRecord
1210
1224
  #
1211
1225
  # User.where(name: 'Oscar').where_values_hash
1212
1226
  # # => {name: "Oscar"}
1213
- def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
1227
+ def where_values_hash(relation_table_name = model.table_name) # :nodoc:
1214
1228
  where_clause.to_h(relation_table_name)
1215
1229
  end
1216
1230
 
1217
1231
  def scope_for_create
1218
- hash = where_clause.to_h(klass.table_name, equality_only: true)
1232
+ hash = where_clause.to_h(model.table_name, equality_only: true)
1219
1233
  create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
1220
1234
  hash
1221
1235
  end
@@ -1261,6 +1275,10 @@ module ActiveRecord
1261
1275
  records.blank?
1262
1276
  end
1263
1277
 
1278
+ def readonly?
1279
+ readonly_value
1280
+ end
1281
+
1264
1282
  def values
1265
1283
  @values.dup
1266
1284
  end
@@ -1279,7 +1297,7 @@ module ActiveRecord
1279
1297
  end
1280
1298
 
1281
1299
  def empty_scope? # :nodoc:
1282
- @values == klass.unscoped.values
1300
+ @values == model.unscoped.values
1283
1301
  end
1284
1302
 
1285
1303
  def has_limit_or_offset? # :nodoc:
@@ -1287,7 +1305,7 @@ module ActiveRecord
1287
1305
  end
1288
1306
 
1289
1307
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
1290
- ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
1308
+ ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
1291
1309
  end
1292
1310
 
1293
1311
  class StrictLoadingScope # :nodoc:
@@ -1317,46 +1335,46 @@ module ActiveRecord
1317
1335
 
1318
1336
  private
1319
1337
  def already_in_scope?(registry)
1320
- @delegate_to_klass && registry.current_scope(klass, true)
1338
+ @delegate_to_model && registry.current_scope(model, true)
1321
1339
  end
1322
1340
 
1323
1341
  def global_scope?(registry)
1324
- registry.global_current_scope(klass, true)
1342
+ registry.global_current_scope(model, true)
1325
1343
  end
1326
1344
 
1327
1345
  def current_scope_restoring_block(&block)
1328
- current_scope = klass.current_scope(true)
1346
+ current_scope = model.current_scope(true)
1329
1347
  -> record do
1330
- klass.current_scope = current_scope
1348
+ model.current_scope = current_scope
1331
1349
  yield record if block_given?
1332
1350
  end
1333
1351
  end
1334
1352
 
1335
1353
  def _new(attributes, &block)
1336
- klass.new(attributes, &block)
1354
+ model.new(attributes, &block)
1337
1355
  end
1338
1356
 
1339
1357
  def _create(attributes, &block)
1340
- klass.create(attributes, &block)
1358
+ model.create(attributes, &block)
1341
1359
  end
1342
1360
 
1343
1361
  def _create!(attributes, &block)
1344
- klass.create!(attributes, &block)
1362
+ model.create!(attributes, &block)
1345
1363
  end
1346
1364
 
1347
1365
  def _scoping(scope, registry, all_queries = false)
1348
- previous = registry.current_scope(klass, true)
1349
- registry.set_current_scope(klass, scope)
1366
+ previous = registry.current_scope(model, true)
1367
+ registry.set_current_scope(model, scope)
1350
1368
 
1351
1369
  if all_queries
1352
- previous_global = registry.global_current_scope(klass, true)
1353
- registry.set_global_current_scope(klass, scope)
1370
+ previous_global = registry.global_current_scope(model, true)
1371
+ registry.set_global_current_scope(model, scope)
1354
1372
  end
1355
1373
  yield
1356
1374
  ensure
1357
- registry.set_current_scope(klass, previous)
1375
+ registry.set_current_scope(model, previous)
1358
1376
  if all_queries
1359
- registry.set_global_current_scope(klass, previous_global)
1377
+ registry.set_global_current_scope(model, previous_global)
1360
1378
  end
1361
1379
  end
1362
1380
 
@@ -1368,7 +1386,7 @@ module ActiveRecord
1368
1386
  value = Arel::Nodes::Grouping.new(value)
1369
1387
  end
1370
1388
  else
1371
- type = klass.type_for_attribute(attr.name)
1389
+ type = model.type_for_attribute(attr.name)
1372
1390
  value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
1373
1391
  end
1374
1392
  [attr, value]
@@ -1415,7 +1433,7 @@ module ActiveRecord
1415
1433
  if where_clause.contradiction?
1416
1434
  [].freeze
1417
1435
  elsif eager_loading?
1418
- klass.with_connection do |c|
1436
+ model.with_connection do |c|
1419
1437
  apply_join_dependency do |relation, join_dependency|
1420
1438
  if relation.null_relation?
1421
1439
  [].freeze
@@ -1427,8 +1445,8 @@ module ActiveRecord
1427
1445
  end
1428
1446
  end
1429
1447
  else
1430
- klass.with_connection do |c|
1431
- klass._query_by_sql(c, arel, async: async)
1448
+ model.with_connection do |c|
1449
+ model._query_by_sql(c, arel, async: async)
1432
1450
  end
1433
1451
  end
1434
1452
  end
@@ -1441,13 +1459,13 @@ module ActiveRecord
1441
1459
  @_join_dependency = nil
1442
1460
  records
1443
1461
  else
1444
- klass._load_from_sql(rows, &block).freeze
1462
+ model._load_from_sql(rows, &block).freeze
1445
1463
  end
1446
1464
  end
1447
1465
 
1448
1466
  def skip_query_cache_if_necessary(&block)
1449
1467
  if skip_query_cache_value
1450
- uncached(&block)
1468
+ model.uncached(&block)
1451
1469
  else
1452
1470
  yield
1453
1471
  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
@@ -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:
@@ -69,9 +69,7 @@ module ActiveRecord
69
69
 
70
70
  def predicate_builder
71
71
  if klass
72
- predicate_builder = klass.predicate_builder.dup
73
- predicate_builder.instance_variable_set(:@table, self)
74
- predicate_builder
72
+ klass.predicate_builder.with(self)
75
73
  else
76
74
  PredicateBuilder.new(self)
77
75
  end