activerecord 7.1.3.4 → 7.2.2.1
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 +652 -2032
- 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 +15 -8
- data/lib/active_record/associations/belongs_to_association.rb +18 -11
- 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 +11 -5
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/has_many_association.rb +3 -3
- data/lib/active_record/associations/has_many_through_association.rb +7 -1
- data/lib/active_record/associations/has_one_association.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -12
- 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 +62 -289
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- 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 +11 -6
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +89 -58
- data/lib/active_record/attributes.rb +61 -47
- data/lib/active_record/autosave_association.rb +17 -31
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +1 -1
- 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 +270 -58
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +35 -18
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +190 -75
- 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 +23 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -62
- data/lib/active_record/connection_adapters/abstract_adapter.rb +38 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +73 -19
- 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 +8 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +16 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -32
- 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 +6 -0
- 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 +18 -12
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +29 -24
- 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 +127 -77
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +15 -15
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +32 -65
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +93 -40
- data/lib/active_record/counter_cache.rb +23 -10
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +19 -4
- data/lib/active_record/database_configurations/hash_config.rb +44 -36
- 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 +30 -6
- data/lib/active_record/destroy_association_async_job.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +3 -3
- data/lib/active_record/encryption/encrypted_attribute_type.rb +26 -6
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/key_provider.rb +1 -1
- 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/scheme.rb +8 -4
- data/lib/active_record/encryption.rb +2 -0
- data/lib/active_record/enum.rb +19 -2
- data/lib/active_record/errors.rb +46 -20
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +17 -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 +8 -7
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +4 -1
- data/lib/active_record/message_pack.rb +2 -2
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +11 -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 +38 -70
- data/lib/active_record/nested_attributes.rb +24 -5
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +32 -354
- data/lib/active_record/query_cache.rb +19 -8
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/query_logs_formatter.rb +1 -1
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -68
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +42 -45
- data/lib/active_record/reflection.rb +106 -38
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +14 -8
- data/lib/active_record/relation/calculations.rb +96 -63
- 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/association_query_value.rb +9 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +245 -65
- 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 +500 -66
- data/lib/active_record/result.rb +32 -45
- 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/scoping/named.rb +1 -0
- data/lib/active_record/signed_id.rb +20 -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 +98 -48
- 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 +87 -89
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +5 -3
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +70 -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 +15 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +150 -41
- 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 +8 -3
- 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/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +31 -17
- data/lib/arel.rb +7 -3
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +21 -15
@@ -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
|
@@ -157,7 +173,7 @@ module ActiveRecord
|
|
157
173
|
end # end
|
158
174
|
|
159
175
|
def #{method_name}=(value) # def includes_values=(value)
|
160
|
-
|
176
|
+
assert_modifiable! # assert_modifiable!
|
161
177
|
@values[:#{name}] = value # @values[:includes] = value
|
162
178
|
end # end
|
163
179
|
CODE
|
@@ -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])
|
@@ -667,7 +746,7 @@ module ActiveRecord
|
|
667
746
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
668
747
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
669
748
|
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
-
:having, :optimizer_hints])
|
749
|
+
:having, :optimizer_hints, :with])
|
671
750
|
|
672
751
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
673
752
|
# This is useful when passing around chains of relations and would like to
|
@@ -717,7 +796,7 @@ module ActiveRecord
|
|
717
796
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
718
797
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
719
798
|
end
|
720
|
-
|
799
|
+
assert_modifiable!
|
721
800
|
@values.delete(scope)
|
722
801
|
when Hash
|
723
802
|
scope.each do |key, target_value|
|
@@ -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)
|
@@ -1541,11 +1636,71 @@ module ActiveRecord
|
|
1541
1636
|
self
|
1542
1637
|
end
|
1543
1638
|
|
1639
|
+
protected
|
1640
|
+
def arel_columns(columns)
|
1641
|
+
columns.flat_map do |field|
|
1642
|
+
case field
|
1643
|
+
when Symbol
|
1644
|
+
arel_column(field.to_s) do |attr_name|
|
1645
|
+
adapter_class.quote_table_name(attr_name)
|
1646
|
+
end
|
1647
|
+
when String
|
1648
|
+
arel_column(field, &:itself)
|
1649
|
+
when Proc
|
1650
|
+
field.call
|
1651
|
+
when Hash
|
1652
|
+
arel_columns_from_hash(field)
|
1653
|
+
else
|
1654
|
+
field
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
|
1544
1659
|
private
|
1545
1660
|
def async
|
1546
1661
|
spawn.async!
|
1547
1662
|
end
|
1548
1663
|
|
1664
|
+
def build_named_bound_sql_literal(statement, values)
|
1665
|
+
bound_values = values.transform_values 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})", nil, bound_values)
|
1679
|
+
rescue Arel::BindError => error
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def build_bound_sql_literal(statement, values)
|
1685
|
+
bound_values = values.map do |value|
|
1686
|
+
if ActiveRecord::Relation === value
|
1687
|
+
Arel.sql(value.to_sql)
|
1688
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1689
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1690
|
+
values.empty? ? nil : values
|
1691
|
+
else
|
1692
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1693
|
+
value
|
1694
|
+
end
|
1695
|
+
end
|
1696
|
+
|
1697
|
+
begin
|
1698
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
1699
|
+
rescue Arel::BindError => error
|
1700
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
|
1549
1704
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1550
1705
|
each_join_dependencies do |join|
|
1551
1706
|
return join.base_klass if table_name == join.table_name
|
@@ -1570,12 +1725,11 @@ module ActiveRecord
|
|
1570
1725
|
)
|
1571
1726
|
end
|
1572
1727
|
|
1573
|
-
def
|
1574
|
-
raise
|
1575
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
1728
|
+
def assert_modifiable!
|
1729
|
+
raise UnmodifiableRelation if @loaded || @arel
|
1576
1730
|
end
|
1577
1731
|
|
1578
|
-
def build_arel(aliases = nil)
|
1732
|
+
def build_arel(connection, aliases = nil)
|
1579
1733
|
arel = Arel::SelectManager.new(table)
|
1580
1734
|
|
1581
1735
|
build_joins(arel.join_sources, aliases)
|
@@ -1747,20 +1901,37 @@ module ActiveRecord
|
|
1747
1901
|
build_with_value_from_hash(with_value)
|
1748
1902
|
end
|
1749
1903
|
|
1750
|
-
arel.with(with_statements)
|
1904
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
1751
1905
|
end
|
1752
1906
|
|
1753
1907
|
def build_with_value_from_hash(hash)
|
1754
1908
|
hash.map do |name, value|
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1909
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
1910
|
+
end
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
def build_with_expression_from_value(value, nested = false)
|
1914
|
+
case value
|
1915
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1916
|
+
when ActiveRecord::Relation
|
1917
|
+
if nested
|
1918
|
+
value.arel.ast
|
1919
|
+
else
|
1920
|
+
value.arel
|
1921
|
+
end
|
1922
|
+
when Arel::SelectManager then value
|
1923
|
+
when Array
|
1924
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
1925
|
+
|
1926
|
+
parts = value.map do |query|
|
1927
|
+
build_with_expression_from_value(query, true)
|
1928
|
+
end
|
1929
|
+
|
1930
|
+
parts.reduce do |result, value|
|
1931
|
+
Arel::Nodes::UnionAll.new(result, value)
|
1932
|
+
end
|
1933
|
+
else
|
1934
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1764
1935
|
end
|
1765
1936
|
end
|
1766
1937
|
|
@@ -1772,31 +1943,14 @@ module ActiveRecord
|
|
1772
1943
|
).join_sources.first
|
1773
1944
|
end
|
1774
1945
|
|
1775
|
-
def arel_columns(columns)
|
1776
|
-
columns.flat_map do |field|
|
1777
|
-
case field
|
1778
|
-
when Symbol
|
1779
|
-
arel_column(field.to_s) do |attr_name|
|
1780
|
-
connection.quote_table_name(attr_name)
|
1781
|
-
end
|
1782
|
-
when String
|
1783
|
-
arel_column(field, &:itself)
|
1784
|
-
when Proc
|
1785
|
-
field.call
|
1786
|
-
else
|
1787
|
-
field
|
1788
|
-
end
|
1789
|
-
end
|
1790
|
-
end
|
1791
|
-
|
1792
1946
|
def arel_column(field)
|
1793
1947
|
field = klass.attribute_aliases[field] || field
|
1794
1948
|
from = from_clause.name || from_clause.value
|
1795
1949
|
|
1796
1950
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1797
1951
|
table[field]
|
1798
|
-
elsif
|
1799
|
-
table,
|
1952
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
1953
|
+
self.references_values |= [Arel.sql(table, retryable: true)]
|
1800
1954
|
predicate_builder.resolve_arel_attribute(table, column) do
|
1801
1955
|
lookup_table_klass_from_join_dependencies(table)
|
1802
1956
|
end
|
@@ -1807,7 +1961,7 @@ module ActiveRecord
|
|
1807
1961
|
|
1808
1962
|
def table_name_matches?(from)
|
1809
1963
|
table_name = Regexp.escape(table.name)
|
1810
|
-
quoted_table_name = Regexp.escape(
|
1964
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
1811
1965
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1812
1966
|
end
|
1813
1967
|
|
@@ -1863,7 +2017,9 @@ module ActiveRecord
|
|
1863
2017
|
args.each do |arg|
|
1864
2018
|
next unless arg.is_a?(Hash)
|
1865
2019
|
arg.each do |_key, value|
|
1866
|
-
|
2020
|
+
if value.is_a?(Hash)
|
2021
|
+
validate_order_args([value])
|
2022
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
1867
2023
|
raise ArgumentError,
|
1868
2024
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1869
2025
|
end
|
@@ -1871,10 +2027,14 @@ module ActiveRecord
|
|
1871
2027
|
end
|
1872
2028
|
end
|
1873
2029
|
|
2030
|
+
def flattened_args(args)
|
2031
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
2032
|
+
end
|
2033
|
+
|
1874
2034
|
def preprocess_order_args(order_args)
|
1875
2035
|
@klass.disallow_raw_sql!(
|
1876
|
-
order_args
|
1877
|
-
permit:
|
2036
|
+
flattened_args(order_args),
|
2037
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
1878
2038
|
)
|
1879
2039
|
|
1880
2040
|
validate_order_args(order_args)
|
@@ -1888,14 +2048,20 @@ module ActiveRecord
|
|
1888
2048
|
when Symbol
|
1889
2049
|
order_column(arg.to_s).asc
|
1890
2050
|
when Hash
|
1891
|
-
arg.map
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
2051
|
+
arg.map do |key, value|
|
2052
|
+
if value.is_a?(Hash)
|
2053
|
+
value.map do |field, dir|
|
2054
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
2055
|
+
end
|
1895
2056
|
else
|
1896
|
-
|
2057
|
+
case key
|
2058
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
2059
|
+
key.public_send(value.downcase)
|
2060
|
+
else
|
2061
|
+
order_column(key.to_s).public_send(value.downcase)
|
2062
|
+
end
|
1897
2063
|
end
|
1898
|
-
|
2064
|
+
end
|
1899
2065
|
else
|
1900
2066
|
arg
|
1901
2067
|
end
|
@@ -1909,18 +2075,32 @@ module ActiveRecord
|
|
1909
2075
|
end
|
1910
2076
|
|
1911
2077
|
def column_references(order_args)
|
1912
|
-
|
2078
|
+
order_args.flat_map do |arg|
|
1913
2079
|
case arg
|
1914
2080
|
when String, Symbol
|
1915
|
-
arg
|
2081
|
+
extract_table_name_from(arg)
|
1916
2082
|
when Hash
|
1917
|
-
arg
|
1918
|
-
|
2083
|
+
arg
|
2084
|
+
.map do |key, value|
|
2085
|
+
case value
|
2086
|
+
when Hash
|
2087
|
+
key.to_s
|
2088
|
+
else
|
2089
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
2090
|
+
end
|
2091
|
+
end
|
2092
|
+
when Arel::Attribute
|
2093
|
+
arg.relation.name
|
2094
|
+
when Arel::Nodes::Ordering
|
2095
|
+
if arg.expr.is_a?(Arel::Attribute)
|
2096
|
+
arg.expr.relation.name
|
1919
2097
|
end
|
1920
2098
|
end
|
1921
|
-
end
|
1922
|
-
|
1923
|
-
|
2099
|
+
end.compact
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
def extract_table_name_from(string)
|
2103
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
1924
2104
|
end
|
1925
2105
|
|
1926
2106
|
def order_column(field)
|
@@ -1928,7 +2108,7 @@ module ActiveRecord
|
|
1928
2108
|
if attr_name == "count" && !group_values.empty?
|
1929
2109
|
table[attr_name]
|
1930
2110
|
else
|
1931
|
-
Arel.sql(
|
2111
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
1932
2112
|
end
|
1933
2113
|
end
|
1934
2114
|
end
|
@@ -1996,14 +2176,14 @@ module ActiveRecord
|
|
1996
2176
|
def process_select_args(fields)
|
1997
2177
|
fields.flat_map do |field|
|
1998
2178
|
if field.is_a?(Hash)
|
1999
|
-
|
2179
|
+
arel_columns_from_hash(field)
|
2000
2180
|
else
|
2001
2181
|
field
|
2002
2182
|
end
|
2003
2183
|
end
|
2004
2184
|
end
|
2005
2185
|
|
2006
|
-
def
|
2186
|
+
def arel_columns_from_hash(fields)
|
2007
2187
|
fields.flat_map do |key, columns_aliases|
|
2008
2188
|
case columns_aliases
|
2009
2189
|
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
|