activerecord 7.2.1.1 → 8.0.0.rc1

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 (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