activerecord 7.1.5.1 → 7.2.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 +515 -2445
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +76 -70
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -15
@@ -87,6 +87,14 @@ module ActiveRecord
|
|
87
87
|
#
|
88
88
|
# Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2)
|
89
89
|
# # returns an Array of the required fields.
|
90
|
+
#
|
91
|
+
# ==== Edge Cases
|
92
|
+
#
|
93
|
+
# Person.find(37) # raises ActiveRecord::RecordNotFound exception if the record with the given ID does not exist.
|
94
|
+
# Person.find([37]) # raises ActiveRecord::RecordNotFound exception if the record with the given ID in the input array does not exist.
|
95
|
+
# Person.find(nil) # raises ActiveRecord::RecordNotFound exception if the argument is nil.
|
96
|
+
# Person.find([]) # returns an empty array if the argument is an empty array.
|
97
|
+
# Person.find # raises ActiveRecord::RecordNotFound exception if the argument is not provided.
|
90
98
|
def find(*args)
|
91
99
|
return super if block_given?
|
92
100
|
find_with_ids(*args)
|
@@ -366,7 +374,11 @@ module ActiveRecord
|
|
366
374
|
relation = construct_relation_for_exists(conditions)
|
367
375
|
return false if relation.where_clause.contradiction?
|
368
376
|
|
369
|
-
skip_query_cache_if_necessary
|
377
|
+
skip_query_cache_if_necessary do
|
378
|
+
with_connection do |c|
|
379
|
+
c.select_rows(relation.arel, "#{name} Exists?").size == 1
|
380
|
+
end
|
381
|
+
end
|
370
382
|
end
|
371
383
|
|
372
384
|
# Returns true if the relation contains the given record or false otherwise.
|
@@ -459,7 +471,9 @@ module ActiveRecord
|
|
459
471
|
)
|
460
472
|
)
|
461
473
|
relation = skip_query_cache_if_necessary do
|
462
|
-
klass.
|
474
|
+
klass.with_connection do |c|
|
475
|
+
c.distinct_relation_for_primary_key(relation)
|
476
|
+
end
|
463
477
|
end
|
464
478
|
end
|
465
479
|
|
@@ -7,16 +7,15 @@ module ActiveRecord
|
|
7
7
|
class HashMerger # :nodoc:
|
8
8
|
attr_reader :relation, :hash
|
9
9
|
|
10
|
-
def initialize(relation, hash
|
10
|
+
def initialize(relation, hash)
|
11
11
|
hash.assert_valid_keys(*Relation::VALUE_METHODS)
|
12
12
|
|
13
13
|
@relation = relation
|
14
14
|
@hash = hash
|
15
|
-
@rewhere = rewhere
|
16
15
|
end
|
17
16
|
|
18
17
|
def merge
|
19
|
-
Merger.new(relation, other
|
18
|
+
Merger.new(relation, other).merge
|
20
19
|
end
|
21
20
|
|
22
21
|
# Applying values to a relation has some side effects. E.g.
|
@@ -44,11 +43,10 @@ module ActiveRecord
|
|
44
43
|
class Merger # :nodoc:
|
45
44
|
attr_reader :relation, :values, :other
|
46
45
|
|
47
|
-
def initialize(relation, other
|
46
|
+
def initialize(relation, other)
|
48
47
|
@relation = relation
|
49
48
|
@values = other.values
|
50
49
|
@other = other
|
51
|
-
@rewhere = rewhere
|
52
50
|
end
|
53
51
|
|
54
52
|
NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS -
|
@@ -178,7 +176,7 @@ module ActiveRecord
|
|
178
176
|
def merge_clauses
|
179
177
|
relation.from_clause = other.from_clause if replace_from_clause?
|
180
178
|
|
181
|
-
where_clause = relation.where_clause.merge(other.where_clause
|
179
|
+
where_clause = relation.where_clause.merge(other.where_clause)
|
182
180
|
relation.where_clause = where_clause unless where_clause.empty?
|
183
181
|
|
184
182
|
having_clause = relation.having_clause.merge(other.having_clause)
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
13
13
|
return attribute.in([]) if value.empty?
|
14
14
|
|
15
15
|
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
16
|
-
nils = values.
|
16
|
+
nils = values.compact!
|
17
17
|
ranges = values.extract! { |v| v.is_a?(Range) }
|
18
18
|
|
19
19
|
values_predicate =
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
23
23
|
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
if nils
|
27
27
|
values_predicate = values_predicate.or(attribute.eq(nil))
|
28
28
|
end
|
29
29
|
|
@@ -28,9 +28,9 @@ module ActiveRecord
|
|
28
28
|
def self.references(attributes)
|
29
29
|
attributes.each_with_object([]) do |(key, value), result|
|
30
30
|
if value.is_a?(Hash)
|
31
|
-
result << Arel.sql(key)
|
31
|
+
result << Arel.sql(key, retryable: true)
|
32
32
|
elsif (idx = key.rindex("."))
|
33
|
-
result << Arel.sql(key[0, idx])
|
33
|
+
result << Arel.sql(key[0, idx], retryable: true)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -142,7 +142,7 @@ module ActiveRecord
|
|
142
142
|
queries.first
|
143
143
|
else
|
144
144
|
queries.map! { |query| query.reduce(&:and) }
|
145
|
-
queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
|
145
|
+
queries = queries.reduce { |result, query| Arel::Nodes::Or.new([result, query]) }
|
146
146
|
Arel::Nodes::Grouping.new(queries)
|
147
147
|
end
|
148
148
|
end
|
@@ -72,10 +72,26 @@ module ActiveRecord
|
|
72
72
|
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
73
73
|
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
74
74
|
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
75
|
+
#
|
76
|
+
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
|
77
|
+
#
|
78
|
+
# Post.left_joins(:author).where.associated(:author)
|
79
|
+
# # SELECT "posts".* FROM "posts"
|
80
|
+
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
|
81
|
+
# # WHERE "authors"."id" IS NOT NULL
|
82
|
+
#
|
83
|
+
# Post.left_joins(:comments).where.associated(:author)
|
84
|
+
# # SELECT "posts".* FROM "posts"
|
85
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
86
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
87
|
+
# # WHERE "author"."id" IS NOT NULL
|
75
88
|
def associated(*associations)
|
76
89
|
associations.each do |association|
|
77
90
|
reflection = scope_association_reflection(association)
|
78
|
-
@scope.
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
92
|
+
@scope.joins!(association)
|
93
|
+
end
|
94
|
+
|
79
95
|
if reflection.options[:class_name]
|
80
96
|
self.not(association => { reflection.association_primary_key => nil })
|
81
97
|
else
|
@@ -419,6 +435,17 @@ module ActiveRecord
|
|
419
435
|
# # )
|
420
436
|
# # SELECT * FROM posts
|
421
437
|
#
|
438
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
439
|
+
#
|
440
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
441
|
+
# # => ActiveRecord::Relation
|
442
|
+
# # WITH posts_with_tags_or_comments AS (
|
443
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
444
|
+
# # UNION ALL
|
445
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
446
|
+
# # )
|
447
|
+
# # SELECT * FROM posts
|
448
|
+
#
|
422
449
|
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
423
450
|
#
|
424
451
|
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
@@ -457,6 +484,7 @@ module ActiveRecord
|
|
457
484
|
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
458
485
|
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
459
486
|
def with(*args)
|
487
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
460
488
|
check_if_method_has_arguments!(__callee__, args)
|
461
489
|
spawn.with!(*args)
|
462
490
|
end
|
@@ -467,6 +495,30 @@ module ActiveRecord
|
|
467
495
|
self
|
468
496
|
end
|
469
497
|
|
498
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
499
|
+
#
|
500
|
+
# Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
|
501
|
+
# # => ActiveRecord::Relation
|
502
|
+
# # WITH post_and_replies AS (
|
503
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
504
|
+
# # UNION ALL
|
505
|
+
# # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
|
506
|
+
# # )
|
507
|
+
# # SELECT * FROM posts
|
508
|
+
#
|
509
|
+
# See `#with` for more information.
|
510
|
+
def with_recursive(*args)
|
511
|
+
check_if_method_has_arguments!(__callee__, args)
|
512
|
+
spawn.with_recursive!(*args)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Like #with_recursive but modifies the relation in place.
|
516
|
+
def with_recursive!(*args) # :nodoc:
|
517
|
+
self.with_values += args
|
518
|
+
@with_is_recursive = true
|
519
|
+
self
|
520
|
+
end
|
521
|
+
|
470
522
|
# Allows you to change a previously set select statement.
|
471
523
|
#
|
472
524
|
# Post.select(:title, :body)
|
@@ -606,7 +658,8 @@ module ActiveRecord
|
|
606
658
|
self
|
607
659
|
end
|
608
660
|
|
609
|
-
#
|
661
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
662
|
+
# ordered and filtered by a specific set of +values+.
|
610
663
|
#
|
611
664
|
# User.in_order_of(:id, [1, 5, 3])
|
612
665
|
# # SELECT "users".* FROM "users"
|
@@ -617,8 +670,34 @@ module ActiveRecord
|
|
617
670
|
# # WHEN "users"."id" = 3 THEN 3
|
618
671
|
# # END ASC
|
619
672
|
#
|
673
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
674
|
+
# on the database adapter and the column definition.
|
675
|
+
#
|
676
|
+
# class Conversation < ActiveRecord::Base
|
677
|
+
# enum :status, [ :active, :archived ]
|
678
|
+
# end
|
679
|
+
#
|
680
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
681
|
+
# # SELECT "conversations".* FROM "conversations"
|
682
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
683
|
+
# # ORDER BY CASE
|
684
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
685
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
686
|
+
# # END ASC
|
687
|
+
#
|
688
|
+
# +values+ can also include +nil+.
|
689
|
+
#
|
690
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
691
|
+
# # SELECT "conversations".* FROM "conversations"
|
692
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
693
|
+
# # ORDER BY CASE
|
694
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
695
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
696
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
697
|
+
# # END ASC
|
698
|
+
#
|
620
699
|
def in_order_of(column, values)
|
621
|
-
klass.disallow_raw_sql!([column], permit:
|
700
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
622
701
|
return spawn.none! if values.empty?
|
623
702
|
|
624
703
|
references = column_references([column])
|
@@ -1082,7 +1161,7 @@ module ActiveRecord
|
|
1082
1161
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
1083
1162
|
end
|
1084
1163
|
|
1085
|
-
self.where_clause =
|
1164
|
+
self.where_clause = where_clause.or(other.where_clause)
|
1086
1165
|
self.having_clause = having_clause.or(other.having_clause)
|
1087
1166
|
self.references_values |= other.references_values
|
1088
1167
|
|
@@ -1453,6 +1532,9 @@ module ActiveRecord
|
|
1453
1532
|
# Post.excluding(post_one, post_two)
|
1454
1533
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
1455
1534
|
#
|
1535
|
+
# Post.excluding(Post.drafts)
|
1536
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
1537
|
+
#
|
1456
1538
|
# This can also be called on associations. As with the above example, either
|
1457
1539
|
# a single record of collection thereof may be specified:
|
1458
1540
|
#
|
@@ -1468,14 +1550,15 @@ module ActiveRecord
|
|
1468
1550
|
# is passed in) are not instances of the same model that the relation is
|
1469
1551
|
# scoping.
|
1470
1552
|
def excluding(*records)
|
1553
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
1471
1554
|
records.flatten!(1)
|
1472
1555
|
records.compact!
|
1473
1556
|
|
1474
|
-
unless records.all?(klass)
|
1557
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
1475
1558
|
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
1476
1559
|
end
|
1477
1560
|
|
1478
|
-
spawn.excluding!(records)
|
1561
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
1479
1562
|
end
|
1480
1563
|
alias :without :excluding
|
1481
1564
|
|
@@ -1487,7 +1570,7 @@ module ActiveRecord
|
|
1487
1570
|
|
1488
1571
|
# Returns the Arel object associated with the relation.
|
1489
1572
|
def arel(aliases = nil) # :nodoc:
|
1490
|
-
@arel ||= build_arel(aliases)
|
1573
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
1491
1574
|
end
|
1492
1575
|
|
1493
1576
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
@@ -1508,9 +1591,21 @@ module ActiveRecord
|
|
1508
1591
|
def build_where_clause(opts, rest = []) # :nodoc:
|
1509
1592
|
opts = sanitize_forbidden_attributes(opts)
|
1510
1593
|
|
1594
|
+
if opts.is_a?(Array)
|
1595
|
+
opts, *rest = opts
|
1596
|
+
end
|
1597
|
+
|
1511
1598
|
case opts
|
1512
|
-
when String
|
1513
|
-
|
1599
|
+
when String
|
1600
|
+
if rest.empty?
|
1601
|
+
parts = [Arel.sql(opts)]
|
1602
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
1603
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
1604
|
+
elsif opts.include?("?")
|
1605
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
1606
|
+
else
|
1607
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1608
|
+
end
|
1514
1609
|
when Hash
|
1515
1610
|
opts = opts.transform_keys do |key|
|
1516
1611
|
if key.is_a?(Array)
|
@@ -1546,6 +1641,46 @@ module ActiveRecord
|
|
1546
1641
|
spawn.async!
|
1547
1642
|
end
|
1548
1643
|
|
1644
|
+
def build_named_bound_sql_literal(statement, values)
|
1645
|
+
bound_values = values.transform_values do |value|
|
1646
|
+
if ActiveRecord::Relation === value
|
1647
|
+
Arel.sql(value.to_sql)
|
1648
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1649
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1650
|
+
values.empty? ? nil : values
|
1651
|
+
else
|
1652
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1653
|
+
value
|
1654
|
+
end
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
begin
|
1658
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
1659
|
+
rescue Arel::BindError => error
|
1660
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1661
|
+
end
|
1662
|
+
end
|
1663
|
+
|
1664
|
+
def build_bound_sql_literal(statement, values)
|
1665
|
+
bound_values = values.map do |value|
|
1666
|
+
if ActiveRecord::Relation === value
|
1667
|
+
Arel.sql(value.to_sql)
|
1668
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1669
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1670
|
+
values.empty? ? nil : values
|
1671
|
+
else
|
1672
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1673
|
+
value
|
1674
|
+
end
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
begin
|
1678
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
1679
|
+
rescue Arel::BindError => error
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1549
1684
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1550
1685
|
each_join_dependencies do |join|
|
1551
1686
|
return join.base_klass if table_name == join.table_name
|
@@ -1571,11 +1706,10 @@ module ActiveRecord
|
|
1571
1706
|
end
|
1572
1707
|
|
1573
1708
|
def assert_mutability!
|
1574
|
-
raise ImmutableRelation if @loaded
|
1575
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
1709
|
+
raise ImmutableRelation if @loaded || @arel
|
1576
1710
|
end
|
1577
1711
|
|
1578
|
-
def build_arel(aliases = nil)
|
1712
|
+
def build_arel(connection, aliases = nil)
|
1579
1713
|
arel = Arel::SelectManager.new(table)
|
1580
1714
|
|
1581
1715
|
build_joins(arel.join_sources, aliases)
|
@@ -1747,20 +1881,23 @@ module ActiveRecord
|
|
1747
1881
|
build_with_value_from_hash(with_value)
|
1748
1882
|
end
|
1749
1883
|
|
1750
|
-
arel.with(with_statements)
|
1884
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
1751
1885
|
end
|
1752
1886
|
|
1753
1887
|
def build_with_value_from_hash(hash)
|
1754
1888
|
hash.map do |name, value|
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1889
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
1890
|
+
end
|
1891
|
+
end
|
1892
|
+
|
1893
|
+
def build_with_expression_from_value(value)
|
1894
|
+
case value
|
1895
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1896
|
+
when ActiveRecord::Relation then value.arel
|
1897
|
+
when Arel::SelectManager then value
|
1898
|
+
when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
|
1899
|
+
else
|
1900
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1764
1901
|
end
|
1765
1902
|
end
|
1766
1903
|
|
@@ -1777,12 +1914,14 @@ module ActiveRecord
|
|
1777
1914
|
case field
|
1778
1915
|
when Symbol
|
1779
1916
|
arel_column(field.to_s) do |attr_name|
|
1780
|
-
|
1917
|
+
adapter_class.quote_table_name(attr_name)
|
1781
1918
|
end
|
1782
1919
|
when String
|
1783
1920
|
arel_column(field, &:itself)
|
1784
1921
|
when Proc
|
1785
1922
|
field.call
|
1923
|
+
when Hash
|
1924
|
+
arel_columns_from_hash(field)
|
1786
1925
|
else
|
1787
1926
|
field
|
1788
1927
|
end
|
@@ -1807,7 +1946,7 @@ module ActiveRecord
|
|
1807
1946
|
|
1808
1947
|
def table_name_matches?(from)
|
1809
1948
|
table_name = Regexp.escape(table.name)
|
1810
|
-
quoted_table_name = Regexp.escape(
|
1949
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
1811
1950
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1812
1951
|
end
|
1813
1952
|
|
@@ -1863,7 +2002,9 @@ module ActiveRecord
|
|
1863
2002
|
args.each do |arg|
|
1864
2003
|
next unless arg.is_a?(Hash)
|
1865
2004
|
arg.each do |_key, value|
|
1866
|
-
|
2005
|
+
if value.is_a?(Hash)
|
2006
|
+
validate_order_args([value])
|
2007
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
1867
2008
|
raise ArgumentError,
|
1868
2009
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1869
2010
|
end
|
@@ -1871,10 +2012,14 @@ module ActiveRecord
|
|
1871
2012
|
end
|
1872
2013
|
end
|
1873
2014
|
|
2015
|
+
def flattened_args(args)
|
2016
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
2017
|
+
end
|
2018
|
+
|
1874
2019
|
def preprocess_order_args(order_args)
|
1875
2020
|
@klass.disallow_raw_sql!(
|
1876
|
-
order_args
|
1877
|
-
permit:
|
2021
|
+
flattened_args(order_args),
|
2022
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
1878
2023
|
)
|
1879
2024
|
|
1880
2025
|
validate_order_args(order_args)
|
@@ -1888,14 +2033,20 @@ module ActiveRecord
|
|
1888
2033
|
when Symbol
|
1889
2034
|
order_column(arg.to_s).asc
|
1890
2035
|
when Hash
|
1891
|
-
arg.map
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
2036
|
+
arg.map do |key, value|
|
2037
|
+
if value.is_a?(Hash)
|
2038
|
+
value.map do |field, dir|
|
2039
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
2040
|
+
end
|
1895
2041
|
else
|
1896
|
-
|
2042
|
+
case key
|
2043
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
2044
|
+
key.public_send(value.downcase)
|
2045
|
+
else
|
2046
|
+
order_column(key.to_s).public_send(value.downcase)
|
2047
|
+
end
|
1897
2048
|
end
|
1898
|
-
|
2049
|
+
end
|
1899
2050
|
else
|
1900
2051
|
arg
|
1901
2052
|
end
|
@@ -1912,29 +2063,17 @@ module ActiveRecord
|
|
1912
2063
|
order_args.flat_map do |arg|
|
1913
2064
|
case arg
|
1914
2065
|
when String, Symbol
|
1915
|
-
extract_table_name_from(arg)
|
1916
|
-
when Hash
|
1917
2066
|
arg
|
1918
|
-
|
1919
|
-
|
1920
|
-
when Hash
|
1921
|
-
key.to_s
|
1922
|
-
else
|
1923
|
-
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
1924
|
-
end
|
1925
|
-
end
|
1926
|
-
when Arel::Attribute
|
1927
|
-
arg.relation.name
|
1928
|
-
when Arel::Nodes::Ordering
|
1929
|
-
if arg.expr.is_a?(Arel::Attribute)
|
1930
|
-
arg.expr.relation.name
|
1931
|
-
end
|
2067
|
+
when Hash
|
2068
|
+
arg.keys.select { |e| e.is_a?(String) || e.is_a?(Symbol) }
|
1932
2069
|
end
|
1933
|
-
end.
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
2070
|
+
end.filter_map do |arg|
|
2071
|
+
arg =~ /^\W?(\w+)\W?\./ && $1
|
2072
|
+
end +
|
2073
|
+
order_args
|
2074
|
+
.select { |e| e.is_a?(Hash) }
|
2075
|
+
.flat_map { |e| e.map { |k, v| k if v.is_a?(Hash) } }
|
2076
|
+
.compact
|
1938
2077
|
end
|
1939
2078
|
|
1940
2079
|
def order_column(field)
|
@@ -1942,7 +2081,7 @@ module ActiveRecord
|
|
1942
2081
|
if attr_name == "count" && !group_values.empty?
|
1943
2082
|
table[attr_name]
|
1944
2083
|
else
|
1945
|
-
Arel.sql(
|
2084
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
1946
2085
|
end
|
1947
2086
|
end
|
1948
2087
|
end
|
@@ -2010,14 +2149,14 @@ module ActiveRecord
|
|
2010
2149
|
def process_select_args(fields)
|
2011
2150
|
fields.flat_map do |field|
|
2012
2151
|
if field.is_a?(Hash)
|
2013
|
-
|
2152
|
+
arel_columns_from_hash(field)
|
2014
2153
|
else
|
2015
2154
|
field
|
2016
2155
|
end
|
2017
2156
|
end
|
2018
2157
|
end
|
2019
2158
|
|
2020
|
-
def
|
2159
|
+
def arel_columns_from_hash(fields)
|
2021
2160
|
fields.flat_map do |key, columns_aliases|
|
2022
2161
|
case columns_aliases
|
2023
2162
|
when Hash
|
@@ -3,6 +3,9 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
class Relation
|
5
5
|
module RecordFetchWarning
|
6
|
+
# Deprecated: subscribe to sql.active_record notifications and
|
7
|
+
# access the row count field to detect large result set sizes.
|
8
|
+
#
|
6
9
|
# When this module is prepended to ActiveRecord::Relation and
|
7
10
|
# +config.active_record.warn_on_records_fetched_greater_than+ is
|
8
11
|
# set to an integer, if the number of records a query returns is
|
@@ -41,26 +41,10 @@ module ActiveRecord
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def merge!(other, *rest) # :nodoc:
|
44
|
-
options = rest.extract_options!
|
45
|
-
|
46
|
-
if options.key?(:rewhere)
|
47
|
-
if options[:rewhere]
|
48
|
-
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
49
|
-
Specifying `Relation#merge(rewhere: true)` is deprecated, as that has now been
|
50
|
-
the default since Rails 7.0. Setting the rewhere option will error in Rails 7.2
|
51
|
-
MSG
|
52
|
-
else
|
53
|
-
ActiveRecord.deprecator.warn(<<-MSG.squish)
|
54
|
-
`Relation#merge(rewhere: false)` is deprecated without replacement,
|
55
|
-
and will be removed in Rails 7.2
|
56
|
-
MSG
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
44
|
if other.is_a?(Hash)
|
61
|
-
Relation::HashMerger.new(self, other
|
45
|
+
Relation::HashMerger.new(self, other).merge
|
62
46
|
elsif other.is_a?(Relation)
|
63
|
-
Relation::Merger.new(self, other
|
47
|
+
Relation::Merger.new(self, other).merge
|
64
48
|
elsif other.respond_to?(:to_proc)
|
65
49
|
instance_exec(&other)
|
66
50
|
else
|
@@ -23,12 +23,8 @@ module ActiveRecord
|
|
23
23
|
WhereClause.new(predicates | other.predicates)
|
24
24
|
end
|
25
25
|
|
26
|
-
def merge(other
|
27
|
-
predicates =
|
28
|
-
except_predicates(other.extract_attributes)
|
29
|
-
else
|
30
|
-
predicates_unreferenced_by(other)
|
31
|
-
end
|
26
|
+
def merge(other)
|
27
|
+
predicates = except_predicates(other.extract_attributes)
|
32
28
|
|
33
29
|
WhereClause.new(predicates | other.predicates)
|
34
30
|
end
|
@@ -51,7 +47,11 @@ module ActiveRecord
|
|
51
47
|
right = right.ast
|
52
48
|
right = right.expr if right.is_a?(Arel::Nodes::Grouping)
|
53
49
|
|
54
|
-
or_clause = Arel::Nodes::Or
|
50
|
+
or_clause = if left.is_a?(Arel::Nodes::Or)
|
51
|
+
Arel::Nodes::Or.new(left.children + [right])
|
52
|
+
else
|
53
|
+
Arel::Nodes::Or.new([left, right])
|
54
|
+
end
|
55
55
|
|
56
56
|
common.predicates << Arel::Nodes::Grouping.new(or_clause)
|
57
57
|
common
|
@@ -156,18 +156,6 @@ module ActiveRecord
|
|
156
156
|
equalities
|
157
157
|
end
|
158
158
|
|
159
|
-
def predicates_unreferenced_by(other)
|
160
|
-
referenced_columns = other.referenced_columns
|
161
|
-
|
162
|
-
predicates.reject do |node|
|
163
|
-
attr = extract_attribute(node) || begin
|
164
|
-
node.left if equality_node?(node) && node.left.is_a?(Arel::Predications)
|
165
|
-
end
|
166
|
-
|
167
|
-
attr && referenced_columns[attr]
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
159
|
def equality_node?(node)
|
172
160
|
!node.is_a?(String) && node.equality?
|
173
161
|
end
|