activerecord 7.2.0 → 8.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +189 -745
- data/README.rdoc +1 -1
- data/lib/active_record/associations/association.rb +25 -5
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +10 -8
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +9 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attributes.rb +6 -5
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +23 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +53 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +26 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -25
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +20 -38
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +44 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +50 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +38 -90
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -12
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_handling.rb +22 -0
- data/lib/active_record/core.rb +16 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +16 -9
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +8 -0
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -1
- data/lib/active_record/future_result.rb +14 -10
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +22 -5
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +35 -33
- data/lib/active_record/model_schema.rb +6 -3
- data/lib/active_record/nested_attributes.rb +11 -2
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +97 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +6 -6
- data/lib/active_record/railtie.rb +8 -14
- data/lib/active_record/reflection.rb +19 -10
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +135 -75
- data/lib/active_record/relation/calculations.rb +24 -19
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +6 -1
- data/lib/active_record/relation/query_methods.rb +58 -37
- data/lib/active_record/relation/record_fetch_warning.rb +2 -2
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation.rb +72 -61
- data/lib/active_record/result.rb +68 -7
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/statement_cache.rb +12 -12
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -16
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +9 -8
- data/lib/active_record.rb +15 -0
- data/lib/arel/collectors/bind.rb +1 -1
- metadata +14 -14
@@ -68,19 +68,19 @@ module ActiveRecord
|
|
68
68
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
69
69
|
include SignedId::RelationMethods, TokenFor::RelationMethods
|
70
70
|
|
71
|
-
attr_reader :table, :
|
71
|
+
attr_reader :table, :model, :loaded, :predicate_builder
|
72
72
|
attr_accessor :skip_preloading_value
|
73
|
-
alias :
|
73
|
+
alias :klass :model
|
74
74
|
alias :loaded? :loaded
|
75
75
|
alias :locked? :lock_value
|
76
76
|
|
77
|
-
def initialize(
|
78
|
-
@
|
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
|
-
@
|
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 =
|
96
|
+
if reflection = model._reflect_on_association(name)
|
97
97
|
name = reflection.foreign_key
|
98
98
|
value = value.read_attribute(reflection.association_primary_key) unless value.nil?
|
99
99
|
end
|
@@ -430,14 +430,14 @@ module ActiveRecord
|
|
430
430
|
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
|
431
431
|
def cache_key(timestamp_column = "updated_at")
|
432
432
|
@cache_keys ||= {}
|
433
|
-
@cache_keys[timestamp_column] ||=
|
433
|
+
@cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
|
434
434
|
end
|
435
435
|
|
436
436
|
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
|
437
437
|
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
|
438
|
-
key = "#{
|
438
|
+
key = "#{model.model_name.cache_key}/query-#{query_signature}"
|
439
439
|
|
440
|
-
if collection_cache_versioning
|
440
|
+
if model.collection_cache_versioning
|
441
441
|
key
|
442
442
|
else
|
443
443
|
"#{key}-#{compute_cache_version(timestamp_column)}"
|
@@ -456,7 +456,7 @@ module ActiveRecord
|
|
456
456
|
#
|
457
457
|
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
|
458
458
|
def cache_version(timestamp_column = :updated_at)
|
459
|
-
if collection_cache_versioning
|
459
|
+
if model.collection_cache_versioning
|
460
460
|
@cache_versions ||= {}
|
461
461
|
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
|
462
462
|
end
|
@@ -475,7 +475,7 @@ module ActiveRecord
|
|
475
475
|
|
476
476
|
with_connection do |c|
|
477
477
|
column = c.visitor.compile(table[timestamp_column])
|
478
|
-
select_values = "COUNT(*) AS #{adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
|
478
|
+
select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
|
479
479
|
|
480
480
|
if collection.has_limit_or_offset?
|
481
481
|
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
@@ -492,7 +492,7 @@ module ActiveRecord
|
|
492
492
|
size, timestamp = c.select_rows(arel, nil).first
|
493
493
|
|
494
494
|
if size
|
495
|
-
column_type =
|
495
|
+
column_type = model.type_for_attribute(timestamp_column)
|
496
496
|
timestamp = column_type.deserialize(timestamp)
|
497
497
|
else
|
498
498
|
size = 0
|
@@ -501,7 +501,7 @@ module ActiveRecord
|
|
501
501
|
end
|
502
502
|
|
503
503
|
if timestamp
|
504
|
-
"#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
|
504
|
+
"#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
|
505
505
|
else
|
506
506
|
"#{size}"
|
507
507
|
end
|
@@ -532,7 +532,7 @@ module ActiveRecord
|
|
532
532
|
# Please check unscoped if you want to remove all previous scopes (including
|
533
533
|
# the default_scope) during the execution of a block.
|
534
534
|
def scoping(all_queries: nil, &block)
|
535
|
-
registry =
|
535
|
+
registry = model.scope_registry
|
536
536
|
if global_scope?(registry) && all_queries == false
|
537
537
|
raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
|
538
538
|
elsif already_in_scope?(registry)
|
@@ -543,11 +543,11 @@ module ActiveRecord
|
|
543
543
|
end
|
544
544
|
|
545
545
|
def _exec_scope(...) # :nodoc:
|
546
|
-
@
|
547
|
-
registry =
|
546
|
+
@delegate_to_model = true
|
547
|
+
registry = model.scope_registry
|
548
548
|
_scoping(nil, registry) { instance_exec(...) || self }
|
549
549
|
ensure
|
550
|
-
@
|
550
|
+
@delegate_to_model = false
|
551
551
|
end
|
552
552
|
|
553
553
|
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
|
@@ -584,30 +584,30 @@ module ActiveRecord
|
|
584
584
|
return 0 if @none
|
585
585
|
|
586
586
|
if updates.is_a?(Hash)
|
587
|
-
if
|
588
|
-
!updates.key?(
|
589
|
-
!updates.key?(
|
590
|
-
attr = table[
|
587
|
+
if model.locking_enabled? &&
|
588
|
+
!updates.key?(model.locking_column) &&
|
589
|
+
!updates.key?(model.locking_column.to_sym)
|
590
|
+
attr = table[model.locking_column]
|
591
591
|
updates[attr.name] = _increment_attribute(attr)
|
592
592
|
end
|
593
593
|
values = _substitute_values(updates)
|
594
594
|
else
|
595
|
-
values = Arel.sql(
|
595
|
+
values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
|
596
596
|
end
|
597
597
|
|
598
|
-
|
598
|
+
model.with_connection do |c|
|
599
599
|
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
|
600
600
|
arel.source.left = table
|
601
601
|
|
602
602
|
group_values_arel_columns = arel_columns(group_values.uniq)
|
603
603
|
having_clause_ast = having_clause.ast unless having_clause.empty?
|
604
|
-
key = if
|
604
|
+
key = if model.composite_primary_key?
|
605
605
|
primary_key.map { |pk| table[pk] }
|
606
606
|
else
|
607
607
|
table[primary_key]
|
608
608
|
end
|
609
609
|
stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
|
610
|
-
c.update(stmt, "#{
|
610
|
+
c.update(stmt, "#{model} Update All").tap { reset }
|
611
611
|
end
|
612
612
|
end
|
613
613
|
|
@@ -615,7 +615,7 @@ module ActiveRecord
|
|
615
615
|
if id == :all
|
616
616
|
each { |record| record.update(attributes) }
|
617
617
|
else
|
618
|
-
|
618
|
+
model.update(id, attributes)
|
619
619
|
end
|
620
620
|
end
|
621
621
|
|
@@ -623,7 +623,7 @@ module ActiveRecord
|
|
623
623
|
if id == :all
|
624
624
|
each { |record| record.update!(attributes) }
|
625
625
|
else
|
626
|
-
|
626
|
+
model.update!(id, attributes)
|
627
627
|
end
|
628
628
|
end
|
629
629
|
|
@@ -929,7 +929,7 @@ module ActiveRecord
|
|
929
929
|
names = touch if touch != true
|
930
930
|
names = Array.wrap(names)
|
931
931
|
options = names.extract_options!
|
932
|
-
touch_updates =
|
932
|
+
touch_updates = model.touch_attributes_with_time(*names, **options)
|
933
933
|
updates.merge!(touch_updates) unless touch_updates.empty?
|
934
934
|
end
|
935
935
|
|
@@ -960,7 +960,7 @@ module ActiveRecord
|
|
960
960
|
# Person.where(name: 'David').touch_all
|
961
961
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
|
962
962
|
def touch_all(*names, time: nil)
|
963
|
-
update_all
|
963
|
+
update_all model.touch_attributes_with_time(*names, time: time)
|
964
964
|
end
|
965
965
|
|
966
966
|
# Destroys the records by instantiating each
|
@@ -1012,20 +1012,20 @@ module ActiveRecord
|
|
1012
1012
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
1013
1013
|
end
|
1014
1014
|
|
1015
|
-
|
1015
|
+
model.with_connection do |c|
|
1016
1016
|
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
|
1017
1017
|
arel.source.left = table
|
1018
1018
|
|
1019
1019
|
group_values_arel_columns = arel_columns(group_values.uniq)
|
1020
1020
|
having_clause_ast = having_clause.ast unless having_clause.empty?
|
1021
|
-
key = if
|
1021
|
+
key = if model.composite_primary_key?
|
1022
1022
|
primary_key.map { |pk| table[pk] }
|
1023
1023
|
else
|
1024
1024
|
table[primary_key]
|
1025
1025
|
end
|
1026
1026
|
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
|
1027
1027
|
|
1028
|
-
c.delete(stmt, "#{
|
1028
|
+
c.delete(stmt, "#{model} Delete All").tap { reset }
|
1029
1029
|
end
|
1030
1030
|
end
|
1031
1031
|
|
@@ -1124,9 +1124,6 @@ module ActiveRecord
|
|
1124
1124
|
# for queries to actually be executed concurrently. Otherwise it defaults to
|
1125
1125
|
# executing them in the foreground.
|
1126
1126
|
#
|
1127
|
-
# +load_async+ will also fall back to executing in the foreground in the test environment when transactional
|
1128
|
-
# fixtures are enabled.
|
1129
|
-
#
|
1130
1127
|
# If the query was actually executed in the background, the Active Record logs will show
|
1131
1128
|
# it by prefixing the log line with <tt>ASYNC</tt>:
|
1132
1129
|
#
|
@@ -1136,7 +1133,7 @@ module ActiveRecord
|
|
1136
1133
|
return load if !c.async_enabled?
|
1137
1134
|
|
1138
1135
|
unless loaded?
|
1139
|
-
result = exec_main_query(async: c.current_transaction.
|
1136
|
+
result = exec_main_query(async: !c.current_transaction.joinable?)
|
1140
1137
|
|
1141
1138
|
if result.is_a?(Array)
|
1142
1139
|
@records = result
|
@@ -1150,6 +1147,16 @@ module ActiveRecord
|
|
1150
1147
|
self
|
1151
1148
|
end
|
1152
1149
|
|
1150
|
+
def then(&block) # :nodoc:
|
1151
|
+
if @future_result
|
1152
|
+
@future_result.then do
|
1153
|
+
yield self
|
1154
|
+
end
|
1155
|
+
else
|
1156
|
+
super
|
1157
|
+
end
|
1158
|
+
end
|
1159
|
+
|
1153
1160
|
# Returns <tt>true</tt> if the relation was scheduled on the background
|
1154
1161
|
# thread pool.
|
1155
1162
|
def scheduled?
|
@@ -1180,7 +1187,7 @@ module ActiveRecord
|
|
1180
1187
|
def reset
|
1181
1188
|
@future_result&.cancel
|
1182
1189
|
@future_result = nil
|
1183
|
-
@
|
1190
|
+
@delegate_to_model = false
|
1184
1191
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
1185
1192
|
@offsets = @take = nil
|
1186
1193
|
@cache_keys = nil
|
@@ -1200,7 +1207,7 @@ module ActiveRecord
|
|
1200
1207
|
relation.to_sql
|
1201
1208
|
end
|
1202
1209
|
else
|
1203
|
-
|
1210
|
+
model.with_connection do |conn|
|
1204
1211
|
conn.unprepared_statement { conn.to_sql(arel) }
|
1205
1212
|
end
|
1206
1213
|
end
|
@@ -1210,12 +1217,12 @@ module ActiveRecord
|
|
1210
1217
|
#
|
1211
1218
|
# User.where(name: 'Oscar').where_values_hash
|
1212
1219
|
# # => {name: "Oscar"}
|
1213
|
-
def where_values_hash(relation_table_name =
|
1220
|
+
def where_values_hash(relation_table_name = model.table_name) # :nodoc:
|
1214
1221
|
where_clause.to_h(relation_table_name)
|
1215
1222
|
end
|
1216
1223
|
|
1217
1224
|
def scope_for_create
|
1218
|
-
hash = where_clause.to_h(
|
1225
|
+
hash = where_clause.to_h(model.table_name, equality_only: true)
|
1219
1226
|
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
|
1220
1227
|
hash
|
1221
1228
|
end
|
@@ -1261,6 +1268,10 @@ module ActiveRecord
|
|
1261
1268
|
records.blank?
|
1262
1269
|
end
|
1263
1270
|
|
1271
|
+
def readonly?
|
1272
|
+
readonly_value
|
1273
|
+
end
|
1274
|
+
|
1264
1275
|
def values
|
1265
1276
|
@values.dup
|
1266
1277
|
end
|
@@ -1279,7 +1290,7 @@ module ActiveRecord
|
|
1279
1290
|
end
|
1280
1291
|
|
1281
1292
|
def empty_scope? # :nodoc:
|
1282
|
-
@values ==
|
1293
|
+
@values == model.unscoped.values
|
1283
1294
|
end
|
1284
1295
|
|
1285
1296
|
def has_limit_or_offset? # :nodoc:
|
@@ -1287,7 +1298,7 @@ module ActiveRecord
|
|
1287
1298
|
end
|
1288
1299
|
|
1289
1300
|
def alias_tracker(joins = [], aliases = nil) # :nodoc:
|
1290
|
-
ActiveRecord::Associations::AliasTracker.create(connection_pool, table.name, joins, aliases)
|
1301
|
+
ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
|
1291
1302
|
end
|
1292
1303
|
|
1293
1304
|
class StrictLoadingScope # :nodoc:
|
@@ -1317,46 +1328,46 @@ module ActiveRecord
|
|
1317
1328
|
|
1318
1329
|
private
|
1319
1330
|
def already_in_scope?(registry)
|
1320
|
-
@
|
1331
|
+
@delegate_to_model && registry.current_scope(model, true)
|
1321
1332
|
end
|
1322
1333
|
|
1323
1334
|
def global_scope?(registry)
|
1324
|
-
registry.global_current_scope(
|
1335
|
+
registry.global_current_scope(model, true)
|
1325
1336
|
end
|
1326
1337
|
|
1327
1338
|
def current_scope_restoring_block(&block)
|
1328
|
-
current_scope =
|
1339
|
+
current_scope = model.current_scope(true)
|
1329
1340
|
-> record do
|
1330
|
-
|
1341
|
+
model.current_scope = current_scope
|
1331
1342
|
yield record if block_given?
|
1332
1343
|
end
|
1333
1344
|
end
|
1334
1345
|
|
1335
1346
|
def _new(attributes, &block)
|
1336
|
-
|
1347
|
+
model.new(attributes, &block)
|
1337
1348
|
end
|
1338
1349
|
|
1339
1350
|
def _create(attributes, &block)
|
1340
|
-
|
1351
|
+
model.create(attributes, &block)
|
1341
1352
|
end
|
1342
1353
|
|
1343
1354
|
def _create!(attributes, &block)
|
1344
|
-
|
1355
|
+
model.create!(attributes, &block)
|
1345
1356
|
end
|
1346
1357
|
|
1347
1358
|
def _scoping(scope, registry, all_queries = false)
|
1348
|
-
previous = registry.current_scope(
|
1349
|
-
registry.set_current_scope(
|
1359
|
+
previous = registry.current_scope(model, true)
|
1360
|
+
registry.set_current_scope(model, scope)
|
1350
1361
|
|
1351
1362
|
if all_queries
|
1352
|
-
previous_global = registry.global_current_scope(
|
1353
|
-
registry.set_global_current_scope(
|
1363
|
+
previous_global = registry.global_current_scope(model, true)
|
1364
|
+
registry.set_global_current_scope(model, scope)
|
1354
1365
|
end
|
1355
1366
|
yield
|
1356
1367
|
ensure
|
1357
|
-
registry.set_current_scope(
|
1368
|
+
registry.set_current_scope(model, previous)
|
1358
1369
|
if all_queries
|
1359
|
-
registry.set_global_current_scope(
|
1370
|
+
registry.set_global_current_scope(model, previous_global)
|
1360
1371
|
end
|
1361
1372
|
end
|
1362
1373
|
|
@@ -1368,7 +1379,7 @@ module ActiveRecord
|
|
1368
1379
|
value = Arel::Nodes::Grouping.new(value)
|
1369
1380
|
end
|
1370
1381
|
else
|
1371
|
-
type =
|
1382
|
+
type = model.type_for_attribute(attr.name)
|
1372
1383
|
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
|
1373
1384
|
end
|
1374
1385
|
[attr, value]
|
@@ -1415,7 +1426,7 @@ module ActiveRecord
|
|
1415
1426
|
if where_clause.contradiction?
|
1416
1427
|
[].freeze
|
1417
1428
|
elsif eager_loading?
|
1418
|
-
|
1429
|
+
model.with_connection do |c|
|
1419
1430
|
apply_join_dependency do |relation, join_dependency|
|
1420
1431
|
if relation.null_relation?
|
1421
1432
|
[].freeze
|
@@ -1427,8 +1438,8 @@ module ActiveRecord
|
|
1427
1438
|
end
|
1428
1439
|
end
|
1429
1440
|
else
|
1430
|
-
|
1431
|
-
|
1441
|
+
model.with_connection do |c|
|
1442
|
+
model._query_by_sql(c, arel, async: async)
|
1432
1443
|
end
|
1433
1444
|
end
|
1434
1445
|
end
|
@@ -1441,13 +1452,13 @@ module ActiveRecord
|
|
1441
1452
|
@_join_dependency = nil
|
1442
1453
|
records
|
1443
1454
|
else
|
1444
|
-
|
1455
|
+
model._load_from_sql(rows, &block).freeze
|
1445
1456
|
end
|
1446
1457
|
end
|
1447
1458
|
|
1448
1459
|
def skip_query_cache_if_necessary(&block)
|
1449
1460
|
if skip_query_cache_value
|
1450
|
-
uncached(&block)
|
1461
|
+
model.uncached(&block)
|
1451
1462
|
else
|
1452
1463
|
yield
|
1453
1464
|
end
|
data/lib/active_record/result.rb
CHANGED
@@ -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
|
-
|
130
|
+
indexed_rows.each(&block)
|
76
131
|
else
|
77
|
-
|
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
|
-
@
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
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.
|
26
|
+
if self == scope.model
|
27
27
|
scope.clone
|
28
28
|
else
|
29
29
|
relation.merge!(scope)
|
@@ -190,7 +190,11 @@ module ActiveRecord
|
|
190
190
|
|
191
191
|
private
|
192
192
|
def singleton_method_added(name)
|
193
|
-
|
193
|
+
super
|
194
|
+
# Most Kernel extends are both singleton and instance methods so
|
195
|
+
# respond_to is a fast check, but we don't want to define methods
|
196
|
+
# only on the module (ex. Module#name)
|
197
|
+
generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
|
194
198
|
end
|
195
199
|
end
|
196
200
|
end
|
@@ -133,23 +133,26 @@ module ActiveRecord
|
|
133
133
|
relation = (callable || block).call Params.new
|
134
134
|
query_builder, binds = connection.cacheable_query(self, relation.arel)
|
135
135
|
bind_map = BindMap.new(binds)
|
136
|
-
new(query_builder, bind_map, relation.
|
136
|
+
new(query_builder, bind_map, relation.model)
|
137
137
|
end
|
138
138
|
|
139
|
-
def initialize(query_builder, bind_map,
|
139
|
+
def initialize(query_builder, bind_map, model)
|
140
140
|
@query_builder = query_builder
|
141
141
|
@bind_map = bind_map
|
142
|
-
@
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
data/lib/active_record/store.rb
CHANGED
@@ -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+,
|
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).
|
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:
|