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