activerecord 7.2.3 → 8.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -1261
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/associations/alias_tracker.rb +4 -6
  5. data/lib/active_record/associations/association.rb +25 -5
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -18
  7. data/lib/active_record/associations/builder/association.rb +7 -6
  8. data/lib/active_record/associations/collection_association.rb +4 -4
  9. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  10. data/lib/active_record/associations/has_many_through_association.rb +4 -9
  11. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  12. data/lib/active_record/associations/preloader/association.rb +2 -2
  13. data/lib/active_record/associations/singular_association.rb +8 -3
  14. data/lib/active_record/associations.rb +50 -32
  15. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  16. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +19 -24
  18. data/lib/active_record/attributes.rb +26 -37
  19. data/lib/active_record/autosave_association.rb +81 -49
  20. data/lib/active_record/base.rb +2 -2
  21. data/lib/active_record/callbacks.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -75
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  27. data/lib/active_record/connection_adapters/abstract/query_cache.rb +14 -19
  28. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -6
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +27 -9
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -57
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -58
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -15
  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 +2 -16
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  41. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
  42. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +12 -14
  43. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  44. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +51 -9
  45. data/lib/active_record/connection_adapters/postgresql_adapter.rb +44 -101
  46. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  47. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  48. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -13
  49. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  50. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +60 -22
  53. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  54. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
  55. data/lib/active_record/connection_handling.rb +29 -11
  56. data/lib/active_record/core.rb +15 -60
  57. data/lib/active_record/counter_cache.rb +1 -1
  58. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -3
  59. data/lib/active_record/delegated_type.rb +18 -18
  60. data/lib/active_record/encryption/config.rb +3 -1
  61. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  62. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  63. data/lib/active_record/encryption/encryptor.rb +35 -29
  64. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  65. data/lib/active_record/encryption/scheme.rb +8 -1
  66. data/lib/active_record/enum.rb +12 -13
  67. data/lib/active_record/errors.rb +16 -8
  68. data/lib/active_record/fixture_set/table_row.rb +2 -19
  69. data/lib/active_record/fixtures.rb +0 -1
  70. data/lib/active_record/future_result.rb +14 -10
  71. data/lib/active_record/gem_version.rb +4 -4
  72. data/lib/active_record/insert_all.rb +1 -1
  73. data/lib/active_record/marshalling.rb +1 -4
  74. data/lib/active_record/migration/command_recorder.rb +22 -5
  75. data/lib/active_record/migration/compatibility.rb +5 -2
  76. data/lib/active_record/migration.rb +36 -35
  77. data/lib/active_record/model_schema.rb +1 -1
  78. data/lib/active_record/nested_attributes.rb +4 -6
  79. data/lib/active_record/persistence.rb +128 -130
  80. data/lib/active_record/query_cache.rb +5 -4
  81. data/lib/active_record/query_logs.rb +98 -44
  82. data/lib/active_record/query_logs_formatter.rb +17 -28
  83. data/lib/active_record/querying.rb +10 -10
  84. data/lib/active_record/railtie.rb +5 -6
  85. data/lib/active_record/railties/databases.rake +1 -2
  86. data/lib/active_record/reflection.rb +9 -7
  87. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  88. data/lib/active_record/relation/batches.rb +132 -72
  89. data/lib/active_record/relation/calculations.rb +55 -55
  90. data/lib/active_record/relation/delegation.rb +25 -14
  91. data/lib/active_record/relation/finder_methods.rb +31 -32
  92. data/lib/active_record/relation/merger.rb +8 -8
  93. data/lib/active_record/relation/predicate_builder/association_query_value.rb +0 -2
  94. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  95. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  96. data/lib/active_record/relation/predicate_builder.rb +5 -0
  97. data/lib/active_record/relation/query_attribute.rb +1 -1
  98. data/lib/active_record/relation/query_methods.rb +90 -91
  99. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  100. data/lib/active_record/relation/spawn_methods.rb +1 -1
  101. data/lib/active_record/relation/where_clause.rb +2 -8
  102. data/lib/active_record/relation.rb +77 -76
  103. data/lib/active_record/result.rb +68 -7
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +16 -29
  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/secure_token.rb +3 -3
  109. data/lib/active_record/signed_id.rb +6 -7
  110. data/lib/active_record/statement_cache.rb +12 -12
  111. data/lib/active_record/store.rb +7 -3
  112. data/lib/active_record/tasks/database_tasks.rb +24 -15
  113. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  114. data/lib/active_record/tasks/postgresql_database_tasks.rb +0 -7
  115. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  116. data/lib/active_record/test_fixtures.rb +12 -0
  117. data/lib/active_record/testing/query_assertions.rb +2 -2
  118. data/lib/active_record/token_for.rb +1 -1
  119. data/lib/active_record/transactions.rb +1 -3
  120. data/lib/active_record/validations/uniqueness.rb +8 -8
  121. data/lib/active_record.rb +16 -1
  122. data/lib/arel/collectors/bind.rb +1 -1
  123. data/lib/arel/crud.rb +0 -2
  124. data/lib/arel/delete_manager.rb +0 -5
  125. data/lib/arel/nodes/delete_statement.rb +2 -4
  126. data/lib/arel/nodes/update_statement.rb +2 -4
  127. data/lib/arel/select_manager.rb +2 -6
  128. data/lib/arel/update_manager.rb +0 -5
  129. data/lib/arel/visitors/dot.rb +0 -2
  130. data/lib/arel/visitors/sqlite.rb +0 -25
  131. data/lib/arel/visitors/to_sql.rb +1 -3
  132. metadata +14 -11
@@ -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
@@ -265,12 +265,7 @@ module ActiveRecord
265
265
  # such situation.
266
266
  def create_or_find_by(attributes, &block)
267
267
  with_connection do |connection|
268
- record = nil
269
- transaction(requires_new: true) do
270
- record = create(attributes, &block)
271
- record._last_transaction_return_status || raise(ActiveRecord::Rollback)
272
- end
273
- record
268
+ transaction(requires_new: true) { create(attributes, &block) }
274
269
  rescue ActiveRecord::RecordNotUnique
275
270
  if connection.transaction_open?
276
271
  where(attributes).lock.find_by!(attributes)
@@ -285,12 +280,7 @@ module ActiveRecord
285
280
  # is raised if the created record is invalid.
286
281
  def create_or_find_by!(attributes, &block)
287
282
  with_connection do |connection|
288
- record = nil
289
- transaction(requires_new: true) do
290
- record = create!(attributes, &block)
291
- record._last_transaction_return_status || raise(ActiveRecord::Rollback)
292
- end
293
- record
283
+ transaction(requires_new: true) { create!(attributes, &block) }
294
284
  rescue ActiveRecord::RecordNotUnique
295
285
  if connection.transaction_open?
296
286
  where(attributes).lock.find_by!(attributes)
@@ -300,7 +290,7 @@ module ActiveRecord
300
290
  end
301
291
  end
302
292
 
303
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
293
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
304
294
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
305
295
  def find_or_initialize_by(attributes, &block)
306
296
  find_by(attributes) || new(attributes, &block)
@@ -440,14 +430,14 @@ module ActiveRecord
440
430
  # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
441
431
  def cache_key(timestamp_column = "updated_at")
442
432
  @cache_keys ||= {}
443
- @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
433
+ @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
444
434
  end
445
435
 
446
436
  def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
447
437
  query_signature = ActiveSupport::Digest.hexdigest(to_sql)
448
- key = "#{klass.model_name.cache_key}/query-#{query_signature}"
438
+ key = "#{model.model_name.cache_key}/query-#{query_signature}"
449
439
 
450
- if collection_cache_versioning
440
+ if model.collection_cache_versioning
451
441
  key
452
442
  else
453
443
  "#{key}-#{compute_cache_version(timestamp_column)}"
@@ -466,7 +456,7 @@ module ActiveRecord
466
456
  #
467
457
  # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
468
458
  def cache_version(timestamp_column = :updated_at)
469
- if collection_cache_versioning
459
+ if model.collection_cache_versioning
470
460
  @cache_versions ||= {}
471
461
  @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
472
462
  end
@@ -485,7 +475,7 @@ module ActiveRecord
485
475
 
486
476
  with_connection do |c|
487
477
  column = c.visitor.compile(table[timestamp_column])
488
- 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"
489
479
 
490
480
  if collection.has_limit_or_offset?
491
481
  query = collection.select("#{column} AS collection_cache_key_timestamp")
@@ -502,7 +492,7 @@ module ActiveRecord
502
492
  size, timestamp = c.select_rows(arel, nil).first
503
493
 
504
494
  if size
505
- column_type = klass.type_for_attribute(timestamp_column)
495
+ column_type = model.type_for_attribute(timestamp_column)
506
496
  timestamp = column_type.deserialize(timestamp)
507
497
  else
508
498
  size = 0
@@ -511,7 +501,7 @@ module ActiveRecord
511
501
  end
512
502
 
513
503
  if timestamp
514
- "#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
504
+ "#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
515
505
  else
516
506
  "#{size}"
517
507
  end
@@ -542,7 +532,7 @@ module ActiveRecord
542
532
  # Please check unscoped if you want to remove all previous scopes (including
543
533
  # the default_scope) during the execution of a block.
544
534
  def scoping(all_queries: nil, &block)
545
- registry = klass.scope_registry
535
+ registry = model.scope_registry
546
536
  if global_scope?(registry) && all_queries == false
547
537
  raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
548
538
  elsif already_in_scope?(registry)
@@ -553,11 +543,11 @@ module ActiveRecord
553
543
  end
554
544
 
555
545
  def _exec_scope(...) # :nodoc:
556
- @delegate_to_klass = true
557
- registry = klass.scope_registry
546
+ @delegate_to_model = true
547
+ registry = model.scope_registry
558
548
  _scoping(nil, registry) { instance_exec(...) || self }
559
549
  ensure
560
- @delegate_to_klass = false
550
+ @delegate_to_model = false
561
551
  end
562
552
 
563
553
  # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
@@ -594,30 +584,30 @@ module ActiveRecord
594
584
  return 0 if @none
595
585
 
596
586
  if updates.is_a?(Hash)
597
- if klass.locking_enabled? &&
598
- !updates.key?(klass.locking_column) &&
599
- !updates.key?(klass.locking_column.to_sym)
600
- attr = table[klass.locking_column]
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]
601
591
  updates[attr.name] = _increment_attribute(attr)
602
592
  end
603
593
  values = _substitute_values(updates)
604
594
  else
605
- values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
595
+ values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
606
596
  end
607
597
 
608
- klass.with_connection do |c|
598
+ model.with_connection do |c|
609
599
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
610
600
  arel.source.left = table
611
601
 
612
602
  group_values_arel_columns = arel_columns(group_values.uniq)
613
603
  having_clause_ast = having_clause.ast unless having_clause.empty?
614
- key = if klass.composite_primary_key?
604
+ key = if model.composite_primary_key?
615
605
  primary_key.map { |pk| table[pk] }
616
606
  else
617
607
  table[primary_key]
618
608
  end
619
609
  stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
620
- c.update(stmt, "#{klass} Update All").tap { reset }
610
+ c.update(stmt, "#{model} Update All").tap { reset }
621
611
  end
622
612
  end
623
613
 
@@ -625,7 +615,7 @@ module ActiveRecord
625
615
  if id == :all
626
616
  each { |record| record.update(attributes) }
627
617
  else
628
- klass.update(id, attributes)
618
+ model.update(id, attributes)
629
619
  end
630
620
  end
631
621
 
@@ -633,7 +623,7 @@ module ActiveRecord
633
623
  if id == :all
634
624
  each { |record| record.update!(attributes) }
635
625
  else
636
- klass.update!(id, attributes)
626
+ model.update!(id, attributes)
637
627
  end
638
628
  end
639
629
 
@@ -823,7 +813,7 @@ module ActiveRecord
823
813
  #
824
814
  # [:returning]
825
815
  # (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
826
- # upserted records, which by default is the primary key.
816
+ # inserted records, which by default is the primary key.
827
817
  # Pass <tt>returning: %w[ id name ]</tt> for both id and name
828
818
  # or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
829
819
  # clause entirely.
@@ -939,7 +929,7 @@ module ActiveRecord
939
929
  names = touch if touch != true
940
930
  names = Array.wrap(names)
941
931
  options = names.extract_options!
942
- touch_updates = klass.touch_attributes_with_time(*names, **options)
932
+ touch_updates = model.touch_attributes_with_time(*names, **options)
943
933
  updates.merge!(touch_updates) unless touch_updates.empty?
944
934
  end
945
935
 
@@ -952,7 +942,7 @@ module ActiveRecord
952
942
  # If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
953
943
  # If no time argument is passed, the current time is used as default.
954
944
  #
955
- # ==== Examples
945
+ # === Examples
956
946
  #
957
947
  # # Touch all records
958
948
  # Person.all.touch_all
@@ -970,7 +960,7 @@ module ActiveRecord
970
960
  # Person.where(name: 'David').touch_all
971
961
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
972
962
  def touch_all(*names, time: nil)
973
- update_all klass.touch_attributes_with_time(*names, time: time)
963
+ update_all model.touch_attributes_with_time(*names, time: time)
974
964
  end
975
965
 
976
966
  # Destroys the records by instantiating each
@@ -1022,20 +1012,20 @@ module ActiveRecord
1022
1012
  raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
1023
1013
  end
1024
1014
 
1025
- klass.with_connection do |c|
1015
+ model.with_connection do |c|
1026
1016
  arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
1027
1017
  arel.source.left = table
1028
1018
 
1029
1019
  group_values_arel_columns = arel_columns(group_values.uniq)
1030
1020
  having_clause_ast = having_clause.ast unless having_clause.empty?
1031
- key = if klass.composite_primary_key?
1021
+ key = if model.composite_primary_key?
1032
1022
  primary_key.map { |pk| table[pk] }
1033
1023
  else
1034
1024
  table[primary_key]
1035
1025
  end
1036
1026
  stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
1037
1027
 
1038
- c.delete(stmt, "#{klass} Delete All").tap { reset }
1028
+ c.delete(stmt, "#{model} Delete All").tap { reset }
1039
1029
  end
1040
1030
  end
1041
1031
 
@@ -1134,9 +1124,6 @@ module ActiveRecord
1134
1124
  # for queries to actually be executed concurrently. Otherwise it defaults to
1135
1125
  # executing them in the foreground.
1136
1126
  #
1137
- # +load_async+ will also fall back to executing in the foreground in the test environment when transactional
1138
- # fixtures are enabled.
1139
- #
1140
1127
  # If the query was actually executed in the background, the Active Record logs will show
1141
1128
  # it by prefixing the log line with <tt>ASYNC</tt>:
1142
1129
  #
@@ -1146,7 +1133,7 @@ module ActiveRecord
1146
1133
  return load if !c.async_enabled?
1147
1134
 
1148
1135
  unless loaded?
1149
- result = exec_main_query(async: c.current_transaction.closed?)
1136
+ result = exec_main_query(async: !c.current_transaction.joinable?)
1150
1137
 
1151
1138
  if result.is_a?(Array)
1152
1139
  @records = result
@@ -1160,6 +1147,16 @@ module ActiveRecord
1160
1147
  self
1161
1148
  end
1162
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
+
1163
1160
  # Returns <tt>true</tt> if the relation was scheduled on the background
1164
1161
  # thread pool.
1165
1162
  def scheduled?
@@ -1190,7 +1187,7 @@ module ActiveRecord
1190
1187
  def reset
1191
1188
  @future_result&.cancel
1192
1189
  @future_result = nil
1193
- @delegate_to_klass = false
1190
+ @delegate_to_model = false
1194
1191
  @to_sql = @arel = @loaded = @should_eager_load = nil
1195
1192
  @offsets = @take = nil
1196
1193
  @cache_keys = nil
@@ -1210,7 +1207,7 @@ module ActiveRecord
1210
1207
  relation.to_sql
1211
1208
  end
1212
1209
  else
1213
- klass.with_connection do |conn|
1210
+ model.with_connection do |conn|
1214
1211
  conn.unprepared_statement { conn.to_sql(arel) }
1215
1212
  end
1216
1213
  end
@@ -1220,12 +1217,12 @@ module ActiveRecord
1220
1217
  #
1221
1218
  # User.where(name: 'Oscar').where_values_hash
1222
1219
  # # => {name: "Oscar"}
1223
- def where_values_hash(relation_table_name = klass.table_name) # :nodoc:
1220
+ def where_values_hash(relation_table_name = model.table_name) # :nodoc:
1224
1221
  where_clause.to_h(relation_table_name)
1225
1222
  end
1226
1223
 
1227
1224
  def scope_for_create
1228
- hash = where_clause.to_h(klass.table_name, equality_only: true)
1225
+ hash = where_clause.to_h(model.table_name, equality_only: true)
1229
1226
  create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
1230
1227
  hash
1231
1228
  end
@@ -1271,6 +1268,10 @@ module ActiveRecord
1271
1268
  records.blank?
1272
1269
  end
1273
1270
 
1271
+ def readonly?
1272
+ readonly_value
1273
+ end
1274
+
1274
1275
  def values
1275
1276
  @values.dup
1276
1277
  end
@@ -1289,7 +1290,7 @@ module ActiveRecord
1289
1290
  end
1290
1291
 
1291
1292
  def empty_scope? # :nodoc:
1292
- @values == klass.unscoped.values
1293
+ @values == model.unscoped.values
1293
1294
  end
1294
1295
 
1295
1296
  def has_limit_or_offset? # :nodoc:
@@ -1297,7 +1298,7 @@ module ActiveRecord
1297
1298
  end
1298
1299
 
1299
1300
  def alias_tracker(joins = [], aliases = nil) # :nodoc:
1300
- ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
1301
+ ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
1301
1302
  end
1302
1303
 
1303
1304
  class StrictLoadingScope # :nodoc:
@@ -1327,46 +1328,46 @@ module ActiveRecord
1327
1328
 
1328
1329
  private
1329
1330
  def already_in_scope?(registry)
1330
- @delegate_to_klass && registry.current_scope(klass, true)
1331
+ @delegate_to_model && registry.current_scope(model, true)
1331
1332
  end
1332
1333
 
1333
1334
  def global_scope?(registry)
1334
- registry.global_current_scope(klass, true)
1335
+ registry.global_current_scope(model, true)
1335
1336
  end
1336
1337
 
1337
1338
  def current_scope_restoring_block(&block)
1338
- current_scope = klass.current_scope(true)
1339
+ current_scope = model.current_scope(true)
1339
1340
  -> record do
1340
- klass.current_scope = current_scope
1341
+ model.current_scope = current_scope
1341
1342
  yield record if block_given?
1342
1343
  end
1343
1344
  end
1344
1345
 
1345
1346
  def _new(attributes, &block)
1346
- klass.new(attributes, &block)
1347
+ model.new(attributes, &block)
1347
1348
  end
1348
1349
 
1349
1350
  def _create(attributes, &block)
1350
- klass.create(attributes, &block)
1351
+ model.create(attributes, &block)
1351
1352
  end
1352
1353
 
1353
1354
  def _create!(attributes, &block)
1354
- klass.create!(attributes, &block)
1355
+ model.create!(attributes, &block)
1355
1356
  end
1356
1357
 
1357
1358
  def _scoping(scope, registry, all_queries = false)
1358
- previous = registry.current_scope(klass, true)
1359
- registry.set_current_scope(klass, scope)
1359
+ previous = registry.current_scope(model, true)
1360
+ registry.set_current_scope(model, scope)
1360
1361
 
1361
1362
  if all_queries
1362
- previous_global = registry.global_current_scope(klass, true)
1363
- registry.set_global_current_scope(klass, scope)
1363
+ previous_global = registry.global_current_scope(model, true)
1364
+ registry.set_global_current_scope(model, scope)
1364
1365
  end
1365
1366
  yield
1366
1367
  ensure
1367
- registry.set_current_scope(klass, previous)
1368
+ registry.set_current_scope(model, previous)
1368
1369
  if all_queries
1369
- registry.set_global_current_scope(klass, previous_global)
1370
+ registry.set_global_current_scope(model, previous_global)
1370
1371
  end
1371
1372
  end
1372
1373
 
@@ -1378,7 +1379,7 @@ module ActiveRecord
1378
1379
  value = Arel::Nodes::Grouping.new(value)
1379
1380
  end
1380
1381
  else
1381
- type = klass.type_for_attribute(attr.name)
1382
+ type = model.type_for_attribute(attr.name)
1382
1383
  value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
1383
1384
  end
1384
1385
  [attr, value]
@@ -1425,7 +1426,7 @@ module ActiveRecord
1425
1426
  if where_clause.contradiction?
1426
1427
  [].freeze
1427
1428
  elsif eager_loading?
1428
- klass.with_connection do |c|
1429
+ model.with_connection do |c|
1429
1430
  apply_join_dependency do |relation, join_dependency|
1430
1431
  if relation.null_relation?
1431
1432
  [].freeze
@@ -1437,8 +1438,8 @@ module ActiveRecord
1437
1438
  end
1438
1439
  end
1439
1440
  else
1440
- klass.with_connection do |c|
1441
- klass._query_by_sql(c, arel, async: async)
1441
+ model.with_connection do |c|
1442
+ model._query_by_sql(c, arel, async: async)
1442
1443
  end
1443
1444
  end
1444
1445
  end
@@ -1451,13 +1452,13 @@ module ActiveRecord
1451
1452
  @_join_dependency = nil
1452
1453
  records
1453
1454
  else
1454
- klass._load_from_sql(rows, &block).freeze
1455
+ model._load_from_sql(rows, &block).freeze
1455
1456
  end
1456
1457
  end
1457
1458
 
1458
1459
  def skip_query_cache_if_necessary(&block)
1459
1460
  if skip_query_cache_value
1460
- uncached(&block)
1461
+ model.uncached(&block)
1461
1462
  else
1462
1463
  yield
1463
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
 
@@ -202,17 +207,12 @@ module ActiveRecord
202
207
  end
203
208
 
204
209
  indexes_in_create(table, tbl)
205
- remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
206
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
207
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
208
213
 
209
214
  tbl.puts " end"
210
215
 
211
- if remaining
212
- tbl.puts
213
- tbl.print remaining.string
214
- end
215
-
216
216
  stream.print tbl.string
217
217
  rescue => e
218
218
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -277,35 +277,22 @@ module ActiveRecord
277
277
 
278
278
  def check_constraints_in_create(table, stream)
279
279
  if (check_constraints = @connection.check_constraints(table)).any?
280
- check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
280
+ add_check_constraint_statements = check_constraints.map do |check_constraint|
281
+ parts = [
282
+ "t.check_constraint #{check_constraint.expression.inspect}"
283
+ ]
281
284
 
282
- unless check_valid.empty?
283
- check_constraint_statements = check_valid.map do |check|
284
- " t.check_constraint #{check_parts(check).join(', ')}"
285
+ if check_constraint.export_name_on_schema_dump?
286
+ parts << "name: #{check_constraint.name.inspect}"
285
287
  end
286
288
 
287
- stream.puts check_constraint_statements.sort.join("\n")
288
- end
289
-
290
- unless check_invalid.empty?
291
- remaining = StringIO.new
292
- table_name = remove_prefix_and_suffix(table).inspect
293
-
294
- add_check_constraint_statements = check_invalid.map do |check|
295
- " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
296
- end
289
+ parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
297
290
 
298
- remaining.puts add_check_constraint_statements.sort.join("\n")
299
- remaining
291
+ " #{parts.join(', ')}"
300
292
  end
301
- end
302
- end
303
293
 
304
- def check_parts(check)
305
- check_parts = [ check.expression.inspect ]
306
- check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
307
- check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
308
- check_parts
294
+ stream.puts add_check_constraint_statements.sort.join("\n")
295
+ end
309
296
  end
310
297
 
311
298
  def foreign_keys(table, stream)
@@ -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