activerecord 7.2.2.1 → 7.2.3
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 +324 -7
- data/README.rdoc +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/collection_association.rb +9 -7
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +37 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +49 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +5 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +32 -3
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -3
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_handling.rb +12 -8
- data/lib/active_record/core.rb +28 -8
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/delegated_type.rb +18 -18
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +21 -20
- data/lib/active_record/enum.rb +13 -12
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration.rb +2 -1
- data/lib/active_record/query_logs.rb +4 -0
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +2 -2
- data/lib/active_record/railties/databases.rake +2 -1
- data/lib/active_record/relation/calculations.rb +35 -30
- data/lib/active_record/relation/finder_methods.rb +14 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +16 -9
- data/lib/active_record/relation/where_clause.rb +8 -2
- data/lib/active_record/relation.rb +15 -5
- data/lib/active_record/schema_dumper.rb +29 -11
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +7 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -0
- data/lib/active_record/transactions.rb +3 -1
- data/lib/active_record.rb +1 -1
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +3 -1
- metadata +10 -13
|
@@ -28,6 +28,10 @@ module ActiveRecord
|
|
|
28
28
|
# * +database+
|
|
29
29
|
# * +source_location+
|
|
30
30
|
#
|
|
31
|
+
# WARNING: Calculating the +source_location+ of a query can be slow, so you should consider its impact if using it in a production environment.
|
|
32
|
+
#
|
|
33
|
+
# Also see {config.active_record.verbose_query_logs}[https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs].
|
|
34
|
+
#
|
|
31
35
|
# Action Controller adds default tags when loaded:
|
|
32
36
|
#
|
|
33
37
|
# * +controller+
|
|
@@ -46,8 +46,8 @@ module ActiveRecord
|
|
|
46
46
|
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
|
47
47
|
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
|
|
48
48
|
#
|
|
49
|
-
# Note that building your own SQL query string from user input may expose your application to
|
|
50
|
-
# injection attacks
|
|
49
|
+
# Note that building your own SQL query string from user input {may expose your application to
|
|
50
|
+
# injection attacks}[https://guides.rubyonrails.org/security.html#sql-injection].
|
|
51
51
|
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
|
52
52
|
result = with_connection do |c|
|
|
53
53
|
_query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
|
|
@@ -55,7 +55,7 @@ module ActiveRecord
|
|
|
55
55
|
_load_from_sql(result, &block)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
# Same as
|
|
58
|
+
# Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
|
|
59
59
|
def async_find_by_sql(sql, binds = [], preparable: nil, &block)
|
|
60
60
|
result = with_connection do |c|
|
|
61
61
|
_query_by_sql(c, sql, binds, preparable: preparable, async: true)
|
|
@@ -112,7 +112,7 @@ module ActiveRecord
|
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
# Same as
|
|
115
|
+
# Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
|
|
116
116
|
def async_count_by_sql(sql)
|
|
117
117
|
with_connection do |c|
|
|
118
118
|
c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
|
|
@@ -23,8 +23,8 @@ module ActiveRecord
|
|
|
23
23
|
config.action_dispatch.rescue_responses.merge!(
|
|
24
24
|
"ActiveRecord::RecordNotFound" => :not_found,
|
|
25
25
|
"ActiveRecord::StaleObjectError" => :conflict,
|
|
26
|
-
"ActiveRecord::RecordInvalid" =>
|
|
27
|
-
"ActiveRecord::RecordNotSaved" =>
|
|
26
|
+
"ActiveRecord::RecordInvalid" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
|
|
27
|
+
"ActiveRecord::RecordNotSaved" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
config.active_record.use_schema_cache_dump = true
|
|
@@ -473,7 +473,8 @@ db_namespace = namespace :db do
|
|
|
473
473
|
|
|
474
474
|
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
|
|
475
475
|
task load: [:load_config, :check_protected_environments] do
|
|
476
|
-
|
|
476
|
+
schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
|
|
477
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(schema_format, ENV["SCHEMA"])
|
|
477
478
|
end
|
|
478
479
|
|
|
479
480
|
namespace :dump do
|
|
@@ -60,37 +60,37 @@ module ActiveRecord
|
|
|
60
60
|
# Person.distinct.count(:age)
|
|
61
61
|
# # => counts the number of different age values
|
|
62
62
|
#
|
|
63
|
-
# If
|
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
|
64
64
|
# it returns a Hash whose keys represent the aggregated column,
|
|
65
65
|
# and the values are the respective amounts:
|
|
66
66
|
#
|
|
67
67
|
# Person.group(:city).count
|
|
68
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
|
69
69
|
#
|
|
70
|
-
# If
|
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
|
71
71
|
# keys are an array containing the individual values of each column and the value
|
|
72
|
-
# of each key would be the
|
|
72
|
+
# of each key would be the count.
|
|
73
73
|
#
|
|
74
74
|
# Article.group(:status, :category).count
|
|
75
75
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
|
76
76
|
#
|
|
77
|
-
# If
|
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
|
78
78
|
#
|
|
79
79
|
# Person.select(:age).count
|
|
80
80
|
# # => counts the number of different age values
|
|
81
81
|
#
|
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
|
82
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
|
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
|
84
84
|
#
|
|
85
|
-
# When given a block,
|
|
86
|
-
#
|
|
87
|
-
# Returns the number of records for which the block returns a truthy value.
|
|
85
|
+
# When given a block, calls the block with each record in the relation and
|
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
|
88
87
|
#
|
|
89
88
|
# Person.count { |person| person.age > 21 }
|
|
90
89
|
# # => counts the number of people older that 21
|
|
91
90
|
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
|
93
|
+
# relation, loading all records could result in performance issues.
|
|
94
94
|
def count(column_name = nil)
|
|
95
95
|
if block_given?
|
|
96
96
|
unless column_name.nil?
|
|
@@ -159,16 +159,15 @@ module ActiveRecord
|
|
|
159
159
|
#
|
|
160
160
|
# Person.sum(:age) # => 4562
|
|
161
161
|
#
|
|
162
|
-
# When given a block,
|
|
163
|
-
#
|
|
164
|
-
# Returns the sum of +initial_value_or_column+ and the block return
|
|
165
|
-
# values:
|
|
162
|
+
# When given a block, calls the block with each record in the relation and
|
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
|
166
164
|
#
|
|
167
165
|
# Person.sum { |person| person.age } # => 4562
|
|
168
166
|
# Person.sum(1000) { |person| person.age } # => 5562
|
|
169
167
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
|
170
|
+
# relation, loading all records could result in performance issues.
|
|
172
171
|
def sum(initial_value_or_column = 0, &block)
|
|
173
172
|
if block_given?
|
|
174
173
|
map(&block).sum(initial_value_or_column)
|
|
@@ -406,6 +405,15 @@ module ActiveRecord
|
|
|
406
405
|
async.ids
|
|
407
406
|
end
|
|
408
407
|
|
|
408
|
+
protected
|
|
409
|
+
def aggregate_column(column_name)
|
|
410
|
+
return column_name if Arel::Expressions === column_name
|
|
411
|
+
|
|
412
|
+
arel_column(column_name.to_s) do |name|
|
|
413
|
+
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
409
417
|
private
|
|
410
418
|
def all_attributes?(column_names)
|
|
411
419
|
(column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
|
|
@@ -446,14 +454,6 @@ module ActiveRecord
|
|
|
446
454
|
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
|
447
455
|
end
|
|
448
456
|
|
|
449
|
-
def aggregate_column(column_name)
|
|
450
|
-
return column_name if Arel::Expressions === column_name
|
|
451
|
-
|
|
452
|
-
arel_column(column_name.to_s) do |name|
|
|
453
|
-
column_name == :all ? Arel.sql("*", retryable: true) : Arel.sql(name)
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
|
|
457
457
|
def operation_over_aggregate_column(column, operation, distinct)
|
|
458
458
|
operation == "count" ? column.count(distinct) : column.public_send(operation)
|
|
459
459
|
end
|
|
@@ -469,7 +469,7 @@ module ActiveRecord
|
|
|
469
469
|
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
|
|
470
470
|
relation = unscope(:order).distinct!(false)
|
|
471
471
|
|
|
472
|
-
column = aggregate_column(column_name)
|
|
472
|
+
column = relation.aggregate_column(column_name)
|
|
473
473
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
474
474
|
select_value.distinct = true if operation == "sum" && distinct
|
|
475
475
|
|
|
@@ -479,7 +479,11 @@ module ActiveRecord
|
|
|
479
479
|
end
|
|
480
480
|
|
|
481
481
|
query_result = if relation.where_clause.contradiction?
|
|
482
|
-
|
|
482
|
+
if @async
|
|
483
|
+
FutureResult.wrap(ActiveRecord::Result.empty)
|
|
484
|
+
else
|
|
485
|
+
ActiveRecord::Result.empty
|
|
486
|
+
end
|
|
483
487
|
else
|
|
484
488
|
skip_query_cache_if_necessary do
|
|
485
489
|
@klass.with_connection do |c|
|
|
@@ -508,7 +512,9 @@ module ActiveRecord
|
|
|
508
512
|
associated = association && association.belongs_to? # only count belongs_to associations
|
|
509
513
|
group_fields = Array(association.foreign_key) if associated
|
|
510
514
|
end
|
|
511
|
-
|
|
515
|
+
|
|
516
|
+
relation = except(:group).distinct!(false)
|
|
517
|
+
group_fields = relation.arel_columns(group_fields)
|
|
512
518
|
|
|
513
519
|
@klass.with_connection do |connection|
|
|
514
520
|
column_alias_tracker = ColumnAliasTracker.new(connection)
|
|
@@ -519,7 +525,7 @@ module ActiveRecord
|
|
|
519
525
|
}
|
|
520
526
|
group_columns = group_aliases.zip(group_fields)
|
|
521
527
|
|
|
522
|
-
column = aggregate_column(column_name)
|
|
528
|
+
column = relation.aggregate_column(column_name)
|
|
523
529
|
column_alias = column_alias_tracker.alias_for("#{operation} #{column_name.to_s.downcase}")
|
|
524
530
|
select_value = operation_over_aggregate_column(column, operation, distinct)
|
|
525
531
|
select_value.as(adapter_class.quote_column_name(column_alias))
|
|
@@ -536,7 +542,6 @@ module ActiveRecord
|
|
|
536
542
|
end
|
|
537
543
|
}
|
|
538
544
|
|
|
539
|
-
relation = except(:group).distinct!(false)
|
|
540
545
|
relation.group_values = group_fields
|
|
541
546
|
relation.select_values = select_values
|
|
542
547
|
|
|
@@ -662,7 +667,7 @@ module ActiveRecord
|
|
|
662
667
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
|
663
668
|
else
|
|
664
669
|
column_alias = Arel.sql("count_column")
|
|
665
|
-
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
|
670
|
+
relation.select_values = [ relation.aggregate_column(column_name).as(column_alias) ]
|
|
666
671
|
end
|
|
667
672
|
|
|
668
673
|
subquery_alias = Arel.sql("subquery_for_count", retryable: true)
|
|
@@ -24,22 +24,22 @@ module ActiveRecord
|
|
|
24
24
|
# TravelRoute.primary_key = [:origin, :destination]
|
|
25
25
|
#
|
|
26
26
|
# TravelRoute.find(["Ottawa", "London"])
|
|
27
|
-
# => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
27
|
+
# # => #<TravelRoute origin: "Ottawa", destination: "London">
|
|
28
28
|
#
|
|
29
29
|
# TravelRoute.find([["Paris", "Montreal"]])
|
|
30
|
-
# => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
30
|
+
# # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
|
31
31
|
#
|
|
32
32
|
# TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
|
|
33
|
-
# => [
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
33
|
+
# # => [
|
|
34
|
+
# # #<TravelRoute origin: "New York", destination: "Las Vegas">,
|
|
35
|
+
# # #<TravelRoute origin: "New York", destination: "Portland">
|
|
36
|
+
# # ]
|
|
37
37
|
#
|
|
38
38
|
# TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
|
|
39
|
-
# => [
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
39
|
+
# # => [
|
|
40
|
+
# # #<TravelRoute origin: "Berlin", destination: "London">,
|
|
41
|
+
# # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
|
|
42
|
+
# # ]
|
|
43
43
|
#
|
|
44
44
|
# NOTE: The returned records are in the same order as the ids you provide.
|
|
45
45
|
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
|
|
@@ -424,12 +424,13 @@ module ActiveRecord
|
|
|
424
424
|
error << " with#{conditions}" if conditions
|
|
425
425
|
raise RecordNotFound.new(error, name, key)
|
|
426
426
|
elsif Array.wrap(ids).size == 1
|
|
427
|
-
|
|
427
|
+
id = Array.wrap(ids)[0]
|
|
428
|
+
error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
|
|
428
429
|
raise RecordNotFound.new(error, name, key, ids)
|
|
429
430
|
else
|
|
430
431
|
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
|
|
431
|
-
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
|
432
|
-
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
|
|
432
|
+
error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
|
433
|
+
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
|
|
433
434
|
raise RecordNotFound.new(error, name, key, ids)
|
|
434
435
|
end
|
|
435
436
|
end
|
|
@@ -35,7 +35,7 @@ module ActiveRecord
|
|
|
35
35
|
def nil?
|
|
36
36
|
unless value_before_type_cast.is_a?(StatementCache::Substitute)
|
|
37
37
|
value_before_type_cast.nil? ||
|
|
38
|
-
type.respond_to?(:subtype) && serializable? && value_for_database.nil?
|
|
38
|
+
(type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -491,7 +491,8 @@ module ActiveRecord
|
|
|
491
491
|
|
|
492
492
|
# Like #with, but modifies relation in place.
|
|
493
493
|
def with!(*args) # :nodoc:
|
|
494
|
-
|
|
494
|
+
args = process_with_args(args)
|
|
495
|
+
self.with_values |= args
|
|
495
496
|
self
|
|
496
497
|
end
|
|
497
498
|
|
|
@@ -502,7 +503,7 @@ module ActiveRecord
|
|
|
502
503
|
# # WITH post_and_replies AS (
|
|
503
504
|
# # (SELECT * FROM posts WHERE id = 42)
|
|
504
505
|
# # UNION ALL
|
|
505
|
-
# # (SELECT * FROM posts JOIN
|
|
506
|
+
# # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
|
|
506
507
|
# # )
|
|
507
508
|
# # SELECT * FROM posts
|
|
508
509
|
#
|
|
@@ -514,7 +515,8 @@ module ActiveRecord
|
|
|
514
515
|
|
|
515
516
|
# Like #with_recursive but modifies the relation in place.
|
|
516
517
|
def with_recursive!(*args) # :nodoc:
|
|
517
|
-
|
|
518
|
+
args = process_with_args(args)
|
|
519
|
+
self.with_values |= args
|
|
518
520
|
@with_is_recursive = true
|
|
519
521
|
self
|
|
520
522
|
end
|
|
@@ -1277,13 +1279,13 @@ module ActiveRecord
|
|
|
1277
1279
|
#
|
|
1278
1280
|
# users = User.readonly
|
|
1279
1281
|
# users.first.save
|
|
1280
|
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1282
|
+
# # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1281
1283
|
#
|
|
1282
1284
|
# To make a readonly relation writable, pass +false+.
|
|
1283
1285
|
#
|
|
1284
1286
|
# users.readonly(false)
|
|
1285
1287
|
# users.first.save
|
|
1286
|
-
# => true
|
|
1288
|
+
# # => true
|
|
1287
1289
|
def readonly(value = true)
|
|
1288
1290
|
spawn.readonly!(value)
|
|
1289
1291
|
end
|
|
@@ -1298,7 +1300,7 @@ module ActiveRecord
|
|
|
1298
1300
|
#
|
|
1299
1301
|
# user = User.strict_loading.first
|
|
1300
1302
|
# user.comments.to_a
|
|
1301
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
1303
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
1302
1304
|
def strict_loading(value = true)
|
|
1303
1305
|
spawn.strict_loading!(value)
|
|
1304
1306
|
end
|
|
@@ -1896,8 +1898,6 @@ module ActiveRecord
|
|
|
1896
1898
|
return if with_values.empty?
|
|
1897
1899
|
|
|
1898
1900
|
with_statements = with_values.map do |with_value|
|
|
1899
|
-
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
|
1900
|
-
|
|
1901
1901
|
build_with_value_from_hash(with_value)
|
|
1902
1902
|
end
|
|
1903
1903
|
|
|
@@ -2096,7 +2096,7 @@ module ActiveRecord
|
|
|
2096
2096
|
arg.expr.relation.name
|
|
2097
2097
|
end
|
|
2098
2098
|
end
|
|
2099
|
-
end.
|
|
2099
|
+
end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
|
|
2100
2100
|
end
|
|
2101
2101
|
|
|
2102
2102
|
def extract_table_name_from(string)
|
|
@@ -2208,6 +2208,13 @@ module ActiveRecord
|
|
|
2208
2208
|
end
|
|
2209
2209
|
end
|
|
2210
2210
|
|
|
2211
|
+
def process_with_args(args)
|
|
2212
|
+
args.flat_map do |arg|
|
|
2213
|
+
raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
|
|
2214
|
+
arg.map { |k, v| { k => v } }
|
|
2215
|
+
end
|
|
2216
|
+
end
|
|
2217
|
+
|
|
2211
2218
|
STRUCTURAL_VALUE_METHODS = (
|
|
2212
2219
|
Relation::VALUE_METHODS -
|
|
2213
2220
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
|
@@ -135,10 +135,14 @@ module ActiveRecord
|
|
|
135
135
|
|
|
136
136
|
def extract_attribute(node)
|
|
137
137
|
attr_node = nil
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
|
|
139
|
+
valid_attrs = Arel.fetch_attribute(node) do |attr|
|
|
140
|
+
!attr_node || attr_node == attr # all attr nodes should be the same
|
|
141
|
+
ensure
|
|
140
142
|
attr_node = attr
|
|
141
143
|
end
|
|
144
|
+
return unless valid_attrs # all nested nodes should yield an attribute
|
|
145
|
+
|
|
142
146
|
attr_node
|
|
143
147
|
end
|
|
144
148
|
|
|
@@ -172,6 +176,8 @@ module ActiveRecord
|
|
|
172
176
|
end
|
|
173
177
|
|
|
174
178
|
def except_predicates(columns)
|
|
179
|
+
return predicates if columns.empty?
|
|
180
|
+
|
|
175
181
|
attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
|
|
176
182
|
non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
|
|
177
183
|
|
|
@@ -265,7 +265,12 @@ module ActiveRecord
|
|
|
265
265
|
# such situation.
|
|
266
266
|
def create_or_find_by(attributes, &block)
|
|
267
267
|
with_connection do |connection|
|
|
268
|
-
|
|
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
|
|
269
274
|
rescue ActiveRecord::RecordNotUnique
|
|
270
275
|
if connection.transaction_open?
|
|
271
276
|
where(attributes).lock.find_by!(attributes)
|
|
@@ -280,7 +285,12 @@ module ActiveRecord
|
|
|
280
285
|
# is raised if the created record is invalid.
|
|
281
286
|
def create_or_find_by!(attributes, &block)
|
|
282
287
|
with_connection do |connection|
|
|
283
|
-
|
|
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
|
|
284
294
|
rescue ActiveRecord::RecordNotUnique
|
|
285
295
|
if connection.transaction_open?
|
|
286
296
|
where(attributes).lock.find_by!(attributes)
|
|
@@ -290,7 +300,7 @@ module ActiveRecord
|
|
|
290
300
|
end
|
|
291
301
|
end
|
|
292
302
|
|
|
293
|
-
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core
|
|
303
|
+
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
|
|
294
304
|
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
|
|
295
305
|
def find_or_initialize_by(attributes, &block)
|
|
296
306
|
find_by(attributes) || new(attributes, &block)
|
|
@@ -813,7 +823,7 @@ module ActiveRecord
|
|
|
813
823
|
#
|
|
814
824
|
# [:returning]
|
|
815
825
|
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
816
|
-
#
|
|
826
|
+
# upserted records, which by default is the primary key.
|
|
817
827
|
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
818
828
|
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
819
829
|
# clause entirely.
|
|
@@ -942,7 +952,7 @@ module ActiveRecord
|
|
|
942
952
|
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
943
953
|
# If no time argument is passed, the current time is used as default.
|
|
944
954
|
#
|
|
945
|
-
#
|
|
955
|
+
# ==== Examples
|
|
946
956
|
#
|
|
947
957
|
# # Touch all records
|
|
948
958
|
# Person.all.touch_all
|
|
@@ -202,12 +202,17 @@ module ActiveRecord
|
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
indexes_in_create(table, tbl)
|
|
205
|
-
check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
|
205
|
+
remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
|
206
206
|
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
|
207
207
|
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
|
208
208
|
|
|
209
209
|
tbl.puts " end"
|
|
210
210
|
|
|
211
|
+
if remaining
|
|
212
|
+
tbl.puts
|
|
213
|
+
tbl.print remaining.string
|
|
214
|
+
end
|
|
215
|
+
|
|
211
216
|
stream.print tbl.string
|
|
212
217
|
rescue => e
|
|
213
218
|
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
@@ -272,24 +277,37 @@ module ActiveRecord
|
|
|
272
277
|
|
|
273
278
|
def check_constraints_in_create(table, stream)
|
|
274
279
|
if (check_constraints = @connection.check_constraints(table)).any?
|
|
275
|
-
|
|
276
|
-
parts = [
|
|
277
|
-
"t.check_constraint #{check_constraint.expression.inspect}"
|
|
278
|
-
]
|
|
280
|
+
check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
|
|
279
281
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
unless check_valid.empty?
|
|
283
|
+
check_constraint_statements = check_valid.map do |check|
|
|
284
|
+
" t.check_constraint #{check_parts(check).join(', ')}"
|
|
282
285
|
end
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
" #{parts.join(', ')}"
|
|
287
|
+
stream.puts check_constraint_statements.sort.join("\n")
|
|
287
288
|
end
|
|
288
289
|
|
|
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
|
|
297
|
+
|
|
298
|
+
remaining.puts add_check_constraint_statements.sort.join("\n")
|
|
299
|
+
remaining
|
|
300
|
+
end
|
|
290
301
|
end
|
|
291
302
|
end
|
|
292
303
|
|
|
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
|
|
309
|
+
end
|
|
310
|
+
|
|
293
311
|
def foreign_keys(table, stream)
|
|
294
312
|
if (foreign_keys = @connection.foreign_keys(table)).any?
|
|
295
313
|
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
|
@@ -30,13 +30,13 @@ module ActiveRecord
|
|
|
30
30
|
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
|
|
31
31
|
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
|
|
32
32
|
#
|
|
33
|
-
#
|
|
33
|
+
# ==== Options
|
|
34
34
|
#
|
|
35
|
-
# [
|
|
35
|
+
# [+:length+]
|
|
36
36
|
# Length of the Secure Random, with a minimum of 24 characters. It will
|
|
37
37
|
# default to 24.
|
|
38
38
|
#
|
|
39
|
-
# [
|
|
39
|
+
# [+:on+]
|
|
40
40
|
# The callback when the value is generated. When called with <tt>on:
|
|
41
41
|
# :initialize</tt>, the value is generated in an
|
|
42
42
|
# <tt>after_initialize</tt> callback, otherwise the value will be used
|
|
@@ -57,12 +57,12 @@ module ActiveRecord
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Works like find_signed, but will raise an
|
|
60
|
+
# Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
|
|
61
61
|
# exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
|
|
62
|
-
# or has been tampered with. It will also raise an
|
|
62
|
+
# or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
|
|
63
63
|
# the valid signed id can't find a record.
|
|
64
64
|
#
|
|
65
|
-
#
|
|
65
|
+
# ==== Examples
|
|
66
66
|
#
|
|
67
67
|
# User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
|
|
68
68
|
#
|
|
@@ -76,8 +76,9 @@ module ActiveRecord
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
|
|
79
|
-
# with the class-level +signed_id_verifier_secret+, which within
|
|
80
|
-
# Rails.application.key_generator.
|
|
79
|
+
# with the class-level +signed_id_verifier_secret+, which within Rails comes from
|
|
80
|
+
# {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
|
|
81
|
+
# By default, it's SHA256 for the digest and JSON for the serialization.
|
|
81
82
|
def signed_id_verifier
|
|
82
83
|
@signed_id_verifier ||= begin
|
|
83
84
|
secret = signed_id_verifier_secret
|
|
@@ -93,7 +94,7 @@ module ActiveRecord
|
|
|
93
94
|
|
|
94
95
|
# Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
|
|
95
96
|
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
96
|
-
# your custom verifier for that in advance. See
|
|
97
|
+
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
97
98
|
def signed_id_verifier=(verifier)
|
|
98
99
|
@signed_id_verifier = verifier
|
|
99
100
|
end
|
|
@@ -132,6 +132,13 @@ module ActiveRecord
|
|
|
132
132
|
tempfile = Tempfile.open("uncommented_structure.sql")
|
|
133
133
|
begin
|
|
134
134
|
File.foreach(filename) do |line|
|
|
135
|
+
next if line.start_with?("\\restrict ")
|
|
136
|
+
|
|
137
|
+
if line.start_with?("\\unrestrict ")
|
|
138
|
+
removing_comments = true
|
|
139
|
+
next
|
|
140
|
+
end
|
|
141
|
+
|
|
135
142
|
unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
|
|
136
143
|
tempfile << line
|
|
137
144
|
removing_comments = false
|
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
|
13
13
|
scope: [:kind, :name]
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
attr_accessor :_new_record_before_last_commit # :nodoc:
|
|
16
|
+
attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:
|
|
17
17
|
|
|
18
18
|
# = Active Record \Transactions
|
|
19
19
|
#
|
|
@@ -418,6 +418,7 @@ module ActiveRecord
|
|
|
418
418
|
status = yield
|
|
419
419
|
raise ActiveRecord::Rollback unless status
|
|
420
420
|
end
|
|
421
|
+
@_last_transaction_return_status = status
|
|
421
422
|
status
|
|
422
423
|
end
|
|
423
424
|
end
|
|
@@ -433,6 +434,7 @@ module ActiveRecord
|
|
|
433
434
|
def init_internals
|
|
434
435
|
super
|
|
435
436
|
@_start_transaction_state = nil
|
|
437
|
+
@_last_transaction_return_status = nil
|
|
436
438
|
@_committed_already_called = nil
|
|
437
439
|
@_new_record_before_last_commit = nil
|
|
438
440
|
end
|
data/lib/active_record.rb
CHANGED
|
@@ -86,7 +86,6 @@ module ActiveRecord
|
|
|
86
86
|
autoload :Timestamp
|
|
87
87
|
autoload :TokenFor
|
|
88
88
|
autoload :TouchLater
|
|
89
|
-
autoload :Transaction
|
|
90
89
|
autoload :Transactions
|
|
91
90
|
autoload :Translation
|
|
92
91
|
autoload :Validations
|
|
@@ -108,6 +107,7 @@ module ActiveRecord
|
|
|
108
107
|
autoload :Result
|
|
109
108
|
autoload :StatementCache
|
|
110
109
|
autoload :TableMetadata
|
|
110
|
+
autoload :Transaction
|
|
111
111
|
autoload :Type
|
|
112
112
|
|
|
113
113
|
autoload_under "relation" do
|