activerecord 6.0.1 → 6.1.7.2
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 +1363 -647
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- data/lib/active_record/aggregations.rb +5 -6
- data/lib/active_record/association_relation.rb +26 -15
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +55 -37
- data/lib/active_record/associations/association_scope.rb +19 -15
- data/lib/active_record/associations/belongs_to_association.rb +23 -10
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +38 -13
- data/lib/active_record/associations/collection_proxy.rb +14 -7
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -3
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +73 -42
- data/lib/active_record/associations/preloader/association.rb +49 -25
- data/lib/active_record/associations/preloader/through_association.rb +2 -2
- data/lib/active_record/associations/preloader.rb +12 -7
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +119 -12
- data/lib/active_record/attribute_assignment.rb +10 -9
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
- data/lib/active_record/attribute_methods/dirty.rb +3 -13
- data/lib/active_record/attribute_methods/primary_key.rb +6 -4
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -12
- data/lib/active_record/attribute_methods/serialization.rb +11 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +12 -21
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attributes.rb +33 -9
- data/lib/active_record/autosave_association.rb +56 -41
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +153 -24
- data/lib/active_record/coders/yaml_column.rb +24 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +190 -136
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -38
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -35
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +145 -52
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
- data/lib/active_record/connection_adapters/abstract/transaction.rb +94 -36
- data/lib/active_record/connection_adapters/abstract_adapter.rb +63 -77
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +136 -111
- data/lib/active_record/connection_adapters/column.rb +15 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -13
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +30 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +80 -66
- data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -57
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +218 -87
- data/lib/active_record/core.rb +276 -68
- data/lib/active_record/counter_cache.rb +4 -1
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -41
- data/lib/active_record/database_configurations.rb +125 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +2 -3
- data/lib/active_record/enum.rb +80 -38
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -5
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -3
- data/lib/active_record/fixture_set/table_rows.rb +0 -1
- data/lib/active_record/fixtures.rb +58 -12
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +40 -21
- data/lib/active_record/insert_all.rb +42 -9
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +18 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +33 -18
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +28 -9
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +6 -2
- data/lib/active_record/middleware/database_selector.rb +4 -2
- data/lib/active_record/migration/command_recorder.rb +53 -45
- data/lib/active_record/migration/compatibility.rb +75 -21
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +115 -85
- data/lib/active_record/model_schema.rb +117 -15
- data/lib/active_record/nested_attributes.rb +2 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +50 -46
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +12 -7
- data/lib/active_record/railtie.rb +65 -45
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +280 -99
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +77 -63
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +38 -32
- data/lib/active_record/relation/calculations.rb +106 -45
- data/lib/active_record/relation/delegation.rb +9 -7
- data/lib/active_record/relation/finder_methods.rb +45 -16
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +27 -26
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +59 -40
- data/lib/active_record/relation/query_methods.rb +341 -188
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -8
- data/lib/active_record/relation/where_clause.rb +111 -62
- data/lib/active_record/relation.rb +116 -83
- data/lib/active_record/result.rb +41 -34
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/default.rb +1 -4
- data/lib/active_record/scoping/named.rb +7 -18
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +20 -4
- data/lib/active_record/store.rb +9 -4
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -36
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +87 -20
- data/lib/active_record/timestamp.rb +4 -7
- data/lib/active_record/touch_later.rb +20 -21
- data/lib/active_record/transactions.rb +25 -72
- data/lib/active_record/type/adapter_specific_registry.rb +2 -5
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +8 -2
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/active_record/validations.rb +3 -3
- data/lib/active_record.rb +7 -13
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/predications.rb +17 -24
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors/dot.rb +14 -3
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -5
- data/lib/arel/visitors/sqlite.rb +0 -1
- data/lib/arel/visitors/to_sql.rb +89 -79
- data/lib/arel/visitors/visitor.rb +0 -1
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +5 -9
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +6 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +30 -29
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
require "active_record/relation/from_clause"
|
|
4
4
|
require "active_record/relation/query_attribute"
|
|
5
5
|
require "active_record/relation/where_clause"
|
|
6
|
-
require "active_record/relation/where_clause_factory"
|
|
7
6
|
require "active_model/forbidden_attributes_protection"
|
|
7
|
+
require "active_support/core_ext/array/wrap"
|
|
8
8
|
|
|
9
9
|
module ActiveRecord
|
|
10
10
|
module QueryMethods
|
|
@@ -15,8 +15,6 @@ module ActiveRecord
|
|
|
15
15
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
|
16
16
|
# In this case, #where must be chained with #not to return a new relation.
|
|
17
17
|
class WhereChain
|
|
18
|
-
include ActiveModel::ForbiddenAttributesProtection
|
|
19
|
-
|
|
20
18
|
def initialize(scope)
|
|
21
19
|
@scope = scope
|
|
22
20
|
end
|
|
@@ -41,53 +39,70 @@ module ActiveRecord
|
|
|
41
39
|
#
|
|
42
40
|
# User.where.not(name: %w(Ko1 Nobu))
|
|
43
41
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
|
42
|
+
#
|
|
43
|
+
# User.where.not(name: "Jon", role: "admin")
|
|
44
|
+
# # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
|
|
44
45
|
def not(opts, *rest)
|
|
45
|
-
|
|
46
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
|
46
47
|
|
|
47
|
-
where_clause
|
|
48
|
+
@scope.where_clause += where_clause.invert
|
|
48
49
|
|
|
49
|
-
@scope
|
|
50
|
+
@scope
|
|
51
|
+
end
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
# Returns a new relation with left outer joins and where clause to identify
|
|
54
|
+
# missing relations.
|
|
55
|
+
#
|
|
56
|
+
# For example, posts that are missing a related author:
|
|
57
|
+
#
|
|
58
|
+
# Post.where.missing(:author)
|
|
59
|
+
# # SELECT "posts".* FROM "posts"
|
|
60
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
61
|
+
# # WHERE "authors"."id" IS NULL
|
|
62
|
+
#
|
|
63
|
+
# Additionally, multiple relations can be combined. This will return posts
|
|
64
|
+
# that are missing both an author and any comments:
|
|
65
|
+
#
|
|
66
|
+
# Post.where.missing(:author, :comments)
|
|
67
|
+
# # SELECT "posts".* FROM "posts"
|
|
68
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
69
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
70
|
+
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
|
71
|
+
def missing(*args)
|
|
72
|
+
args.each do |arg|
|
|
73
|
+
reflection = @scope.klass._reflect_on_association(arg)
|
|
74
|
+
opts = { reflection.table_name => { reflection.association_primary_key => nil } }
|
|
75
|
+
@scope.left_outer_joins!(arg)
|
|
76
|
+
@scope.where!(opts)
|
|
60
77
|
end
|
|
61
78
|
|
|
62
79
|
@scope
|
|
63
80
|
end
|
|
64
|
-
|
|
65
|
-
private
|
|
66
|
-
def not_behaves_as_nor?(opts)
|
|
67
|
-
opts.is_a?(Hash) && opts.size > 1
|
|
68
|
-
end
|
|
69
81
|
end
|
|
70
82
|
|
|
71
83
|
FROZEN_EMPTY_ARRAY = [].freeze
|
|
72
84
|
FROZEN_EMPTY_HASH = {}.freeze
|
|
73
85
|
|
|
74
86
|
Relation::VALUE_METHODS.each do |name|
|
|
75
|
-
method_name =
|
|
87
|
+
method_name, default =
|
|
76
88
|
case name
|
|
77
|
-
when *Relation::MULTI_VALUE_METHODS
|
|
78
|
-
|
|
79
|
-
when *Relation::
|
|
89
|
+
when *Relation::MULTI_VALUE_METHODS
|
|
90
|
+
["#{name}_values", "FROZEN_EMPTY_ARRAY"]
|
|
91
|
+
when *Relation::SINGLE_VALUE_METHODS
|
|
92
|
+
["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
|
|
93
|
+
when *Relation::CLAUSE_METHODS
|
|
94
|
+
["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
|
|
80
95
|
end
|
|
96
|
+
|
|
81
97
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
82
|
-
def #{method_name}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end # end
|
|
98
|
+
def #{method_name} # def includes_values
|
|
99
|
+
@values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
|
|
100
|
+
end # end
|
|
101
|
+
|
|
102
|
+
def #{method_name}=(value) # def includes_values=(value)
|
|
103
|
+
assert_mutability! # assert_mutability!
|
|
104
|
+
@values[:#{name}] = value # @values[:includes] = value
|
|
105
|
+
end # end
|
|
91
106
|
CODE
|
|
92
107
|
end
|
|
93
108
|
|
|
@@ -138,9 +153,6 @@ module ActiveRecord
|
|
|
138
153
|
end
|
|
139
154
|
|
|
140
155
|
def includes!(*args) # :nodoc:
|
|
141
|
-
args.reject!(&:blank?)
|
|
142
|
-
args.flatten!
|
|
143
|
-
|
|
144
156
|
self.includes_values |= args
|
|
145
157
|
self
|
|
146
158
|
end
|
|
@@ -157,7 +169,7 @@ module ActiveRecord
|
|
|
157
169
|
end
|
|
158
170
|
|
|
159
171
|
def eager_load!(*args) # :nodoc:
|
|
160
|
-
self.eager_load_values
|
|
172
|
+
self.eager_load_values |= args
|
|
161
173
|
self
|
|
162
174
|
end
|
|
163
175
|
|
|
@@ -171,7 +183,7 @@ module ActiveRecord
|
|
|
171
183
|
end
|
|
172
184
|
|
|
173
185
|
def preload!(*args) # :nodoc:
|
|
174
|
-
self.preload_values
|
|
186
|
+
self.preload_values |= args
|
|
175
187
|
self
|
|
176
188
|
end
|
|
177
189
|
|
|
@@ -204,9 +216,6 @@ module ActiveRecord
|
|
|
204
216
|
end
|
|
205
217
|
|
|
206
218
|
def references!(*table_names) # :nodoc:
|
|
207
|
-
table_names.flatten!
|
|
208
|
-
table_names.map!(&:to_s)
|
|
209
|
-
|
|
210
219
|
self.references_values |= table_names
|
|
211
220
|
self
|
|
212
221
|
end
|
|
@@ -260,14 +269,12 @@ module ActiveRecord
|
|
|
260
269
|
return super()
|
|
261
270
|
end
|
|
262
271
|
|
|
263
|
-
|
|
272
|
+
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
|
|
264
273
|
spawn._select!(*fields)
|
|
265
274
|
end
|
|
266
275
|
|
|
267
276
|
def _select!(*fields) # :nodoc:
|
|
268
|
-
fields
|
|
269
|
-
fields.flatten!
|
|
270
|
-
self.select_values += fields
|
|
277
|
+
self.select_values |= fields
|
|
271
278
|
self
|
|
272
279
|
end
|
|
273
280
|
|
|
@@ -318,8 +325,6 @@ module ActiveRecord
|
|
|
318
325
|
end
|
|
319
326
|
|
|
320
327
|
def group!(*args) # :nodoc:
|
|
321
|
-
args.flatten!
|
|
322
|
-
|
|
323
328
|
self.group_values += args
|
|
324
329
|
self
|
|
325
330
|
end
|
|
@@ -344,15 +349,16 @@ module ActiveRecord
|
|
|
344
349
|
# User.order('name DESC, email')
|
|
345
350
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
|
346
351
|
def order(*args)
|
|
347
|
-
check_if_method_has_arguments!(:order, args)
|
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
|
353
|
+
sanitize_order_arguments(args)
|
|
354
|
+
end
|
|
348
355
|
spawn.order!(*args)
|
|
349
356
|
end
|
|
350
357
|
|
|
351
358
|
# Same as #order but operates on relation in-place instead of copying.
|
|
352
359
|
def order!(*args) # :nodoc:
|
|
353
|
-
preprocess_order_args(args)
|
|
354
|
-
|
|
355
|
-
self.order_values += args
|
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
|
361
|
+
self.order_values |= args
|
|
356
362
|
self
|
|
357
363
|
end
|
|
358
364
|
|
|
@@ -366,14 +372,16 @@ module ActiveRecord
|
|
|
366
372
|
#
|
|
367
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
|
368
374
|
def reorder(*args)
|
|
369
|
-
check_if_method_has_arguments!(:reorder, args)
|
|
375
|
+
check_if_method_has_arguments!(:reorder, args) do
|
|
376
|
+
sanitize_order_arguments(args) unless args.all?(&:blank?)
|
|
377
|
+
end
|
|
370
378
|
spawn.reorder!(*args)
|
|
371
379
|
end
|
|
372
380
|
|
|
373
381
|
# Same as #reorder but operates on relation in-place instead of copying.
|
|
374
382
|
def reorder!(*args) # :nodoc:
|
|
375
383
|
preprocess_order_args(args) unless args.all?(&:blank?)
|
|
376
|
-
|
|
384
|
+
args.uniq!
|
|
377
385
|
self.reordering_value = true
|
|
378
386
|
self.order_values = args
|
|
379
387
|
self
|
|
@@ -422,7 +430,6 @@ module ActiveRecord
|
|
|
422
430
|
end
|
|
423
431
|
|
|
424
432
|
def unscope!(*args) # :nodoc:
|
|
425
|
-
args.flatten!
|
|
426
433
|
self.unscope_values += args
|
|
427
434
|
|
|
428
435
|
args.each do |scope|
|
|
@@ -433,14 +440,14 @@ module ActiveRecord
|
|
|
433
440
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
|
434
441
|
end
|
|
435
442
|
assert_mutability!
|
|
436
|
-
@values
|
|
443
|
+
@values.delete(scope)
|
|
437
444
|
when Hash
|
|
438
445
|
scope.each do |key, target_value|
|
|
439
446
|
if key != :where
|
|
440
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
|
441
448
|
end
|
|
442
449
|
|
|
443
|
-
target_values = Array(target_value)
|
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
|
444
451
|
self.where_clause = where_clause.except(*target_values)
|
|
445
452
|
end
|
|
446
453
|
else
|
|
@@ -473,8 +480,7 @@ module ActiveRecord
|
|
|
473
480
|
# # SELECT "users".*
|
|
474
481
|
# # FROM "users"
|
|
475
482
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
476
|
-
# # INNER JOIN "comments" "
|
|
477
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
478
484
|
#
|
|
479
485
|
# You can use strings in order to customize your joins:
|
|
480
486
|
#
|
|
@@ -486,9 +492,7 @@ module ActiveRecord
|
|
|
486
492
|
end
|
|
487
493
|
|
|
488
494
|
def joins!(*args) # :nodoc:
|
|
489
|
-
args
|
|
490
|
-
args.flatten!
|
|
491
|
-
self.joins_values += args
|
|
495
|
+
self.joins_values |= args
|
|
492
496
|
self
|
|
493
497
|
end
|
|
494
498
|
|
|
@@ -504,9 +508,7 @@ module ActiveRecord
|
|
|
504
508
|
alias :left_joins :left_outer_joins
|
|
505
509
|
|
|
506
510
|
def left_outer_joins!(*args) # :nodoc:
|
|
507
|
-
args
|
|
508
|
-
args.flatten!
|
|
509
|
-
self.left_outer_joins_values += args
|
|
511
|
+
self.left_outer_joins_values |= args
|
|
510
512
|
self
|
|
511
513
|
end
|
|
512
514
|
|
|
@@ -629,20 +631,18 @@ module ActiveRecord
|
|
|
629
631
|
#
|
|
630
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
|
631
633
|
# the current relation.
|
|
632
|
-
def where(
|
|
633
|
-
if
|
|
634
|
+
def where(*args)
|
|
635
|
+
if args.empty?
|
|
634
636
|
WhereChain.new(spawn)
|
|
635
|
-
elsif
|
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
|
636
638
|
self
|
|
637
639
|
else
|
|
638
|
-
spawn.where!(
|
|
640
|
+
spawn.where!(*args)
|
|
639
641
|
end
|
|
640
642
|
end
|
|
641
643
|
|
|
642
644
|
def where!(opts, *rest) # :nodoc:
|
|
643
|
-
|
|
644
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
|
645
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
|
646
646
|
self
|
|
647
647
|
end
|
|
648
648
|
|
|
@@ -660,7 +660,44 @@ module ActiveRecord
|
|
|
660
660
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
|
661
661
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
|
662
662
|
def rewhere(conditions)
|
|
663
|
-
|
|
663
|
+
scope = spawn
|
|
664
|
+
where_clause = scope.build_where_clause(conditions)
|
|
665
|
+
|
|
666
|
+
scope.unscope!(where: where_clause.extract_attributes)
|
|
667
|
+
scope.where_clause += where_clause
|
|
668
|
+
scope
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Returns a new relation, which is the logical intersection of this relation and the one passed
|
|
672
|
+
# as an argument.
|
|
673
|
+
#
|
|
674
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
|
675
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
|
676
|
+
# present).
|
|
677
|
+
#
|
|
678
|
+
# Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
|
|
679
|
+
# # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
|
|
680
|
+
#
|
|
681
|
+
def and(other)
|
|
682
|
+
if other.is_a?(Relation)
|
|
683
|
+
spawn.and!(other)
|
|
684
|
+
else
|
|
685
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def and!(other) # :nodoc:
|
|
690
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
|
691
|
+
|
|
692
|
+
unless incompatible_values.empty?
|
|
693
|
+
raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
self.where_clause |= other.where_clause
|
|
697
|
+
self.having_clause |= other.having_clause
|
|
698
|
+
self.references_values |= other.references_values
|
|
699
|
+
|
|
700
|
+
self
|
|
664
701
|
end
|
|
665
702
|
|
|
666
703
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
|
@@ -668,21 +705,21 @@ module ActiveRecord
|
|
|
668
705
|
#
|
|
669
706
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
|
670
707
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
|
671
|
-
# present).
|
|
708
|
+
# present).
|
|
672
709
|
#
|
|
673
710
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
|
674
711
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
|
675
712
|
#
|
|
676
713
|
def or(other)
|
|
677
|
-
|
|
714
|
+
if other.is_a?(Relation)
|
|
715
|
+
spawn.or!(other)
|
|
716
|
+
else
|
|
678
717
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
|
679
718
|
end
|
|
680
|
-
|
|
681
|
-
spawn.or!(other)
|
|
682
719
|
end
|
|
683
720
|
|
|
684
721
|
def or!(other) # :nodoc:
|
|
685
|
-
incompatible_values =
|
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
|
686
723
|
|
|
687
724
|
unless incompatible_values.empty?
|
|
688
725
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
@@ -690,7 +727,7 @@ module ActiveRecord
|
|
|
690
727
|
|
|
691
728
|
self.where_clause = self.where_clause.or(other.where_clause)
|
|
692
729
|
self.having_clause = having_clause.or(other.having_clause)
|
|
693
|
-
self.references_values
|
|
730
|
+
self.references_values |= other.references_values
|
|
694
731
|
|
|
695
732
|
self
|
|
696
733
|
end
|
|
@@ -704,10 +741,7 @@ module ActiveRecord
|
|
|
704
741
|
end
|
|
705
742
|
|
|
706
743
|
def having!(opts, *rest) # :nodoc:
|
|
707
|
-
|
|
708
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
|
709
|
-
|
|
710
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
|
711
745
|
self
|
|
712
746
|
end
|
|
713
747
|
|
|
@@ -809,6 +843,21 @@ module ActiveRecord
|
|
|
809
843
|
self
|
|
810
844
|
end
|
|
811
845
|
|
|
846
|
+
# Sets the returned relation to strict_loading mode. This will raise an error
|
|
847
|
+
# if the record tries to lazily load an association.
|
|
848
|
+
#
|
|
849
|
+
# user = User.strict_loading.first
|
|
850
|
+
# user.comments.to_a
|
|
851
|
+
# => ActiveRecord::StrictLoadingViolationError
|
|
852
|
+
def strict_loading(value = true)
|
|
853
|
+
spawn.strict_loading!(value)
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def strict_loading!(value = true) # :nodoc:
|
|
857
|
+
self.strict_loading_value = value
|
|
858
|
+
self
|
|
859
|
+
end
|
|
860
|
+
|
|
812
861
|
# Sets attributes to be used when creating new records from a
|
|
813
862
|
# relation object.
|
|
814
863
|
#
|
|
@@ -950,8 +999,6 @@ module ActiveRecord
|
|
|
950
999
|
end
|
|
951
1000
|
|
|
952
1001
|
def optimizer_hints!(*args) # :nodoc:
|
|
953
|
-
args.flatten!
|
|
954
|
-
|
|
955
1002
|
self.optimizer_hints_values |= args
|
|
956
1003
|
self
|
|
957
1004
|
end
|
|
@@ -964,8 +1011,7 @@ module ActiveRecord
|
|
|
964
1011
|
end
|
|
965
1012
|
|
|
966
1013
|
def reverse_order! # :nodoc:
|
|
967
|
-
orders = order_values.
|
|
968
|
-
orders.reject!(&:blank?)
|
|
1014
|
+
orders = order_values.compact_blank
|
|
969
1015
|
self.order_values = reverse_sql_order(orders)
|
|
970
1016
|
self
|
|
971
1017
|
end
|
|
@@ -989,6 +1035,8 @@ module ActiveRecord
|
|
|
989
1035
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
|
990
1036
|
#
|
|
991
1037
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
|
1038
|
+
#
|
|
1039
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
|
992
1040
|
def annotate(*args)
|
|
993
1041
|
check_if_method_has_arguments!(:annotate, args)
|
|
994
1042
|
spawn.annotate!(*args)
|
|
@@ -1000,6 +1048,14 @@ module ActiveRecord
|
|
|
1000
1048
|
self
|
|
1001
1049
|
end
|
|
1002
1050
|
|
|
1051
|
+
# Deduplicate multiple values.
|
|
1052
|
+
def uniq!(name)
|
|
1053
|
+
if values = @values[name]
|
|
1054
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
|
1055
|
+
end
|
|
1056
|
+
self
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1003
1059
|
# Returns the Arel object associated with the relation.
|
|
1004
1060
|
def arel(aliases = nil) # :nodoc:
|
|
1005
1061
|
@arel ||= build_arel(aliases)
|
|
@@ -1020,54 +1076,106 @@ module ActiveRecord
|
|
|
1020
1076
|
end
|
|
1021
1077
|
end
|
|
1022
1078
|
|
|
1079
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
|
1080
|
+
opts = sanitize_forbidden_attributes(opts)
|
|
1081
|
+
|
|
1082
|
+
case opts
|
|
1083
|
+
when String, Array
|
|
1084
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
|
1085
|
+
when Hash
|
|
1086
|
+
opts = opts.transform_keys do |key|
|
|
1087
|
+
key = key.to_s
|
|
1088
|
+
klass.attribute_aliases[key] || key
|
|
1089
|
+
end
|
|
1090
|
+
references = PredicateBuilder.references(opts)
|
|
1091
|
+
self.references_values |= references unless references.empty?
|
|
1092
|
+
|
|
1093
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
|
1094
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
|
1095
|
+
end
|
|
1096
|
+
when Arel::Nodes::Node
|
|
1097
|
+
parts = [opts]
|
|
1098
|
+
else
|
|
1099
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
Relation::WhereClause.new(parts)
|
|
1103
|
+
end
|
|
1104
|
+
alias :build_having_clause :build_where_clause
|
|
1105
|
+
|
|
1023
1106
|
private
|
|
1107
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1108
|
+
each_join_dependencies do |join|
|
|
1109
|
+
return join.base_klass if table_name == join.table_name
|
|
1110
|
+
end
|
|
1111
|
+
nil
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
|
1115
|
+
join_dependencies.each do |join_dependency|
|
|
1116
|
+
join_dependency.each do |join|
|
|
1117
|
+
yield join
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
def build_join_dependencies
|
|
1123
|
+
associations = joins_values | left_outer_joins_values
|
|
1124
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
|
1125
|
+
associations |= includes_values unless includes_values.empty?
|
|
1126
|
+
|
|
1127
|
+
join_dependencies = []
|
|
1128
|
+
join_dependencies.unshift construct_join_dependency(
|
|
1129
|
+
select_association_list(associations, join_dependencies), nil
|
|
1130
|
+
)
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1024
1133
|
def assert_mutability!
|
|
1025
1134
|
raise ImmutableRelation if @loaded
|
|
1026
1135
|
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1027
1136
|
end
|
|
1028
1137
|
|
|
1029
|
-
def build_arel(aliases)
|
|
1138
|
+
def build_arel(aliases = nil)
|
|
1030
1139
|
arel = Arel::SelectManager.new(table)
|
|
1031
1140
|
|
|
1032
|
-
|
|
1033
|
-
build_joins(arel, joins_values.flatten, aliases)
|
|
1034
|
-
elsif !left_outer_joins_values.empty?
|
|
1035
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
|
|
1036
|
-
end
|
|
1141
|
+
build_joins(arel.join_sources, aliases)
|
|
1037
1142
|
|
|
1038
1143
|
arel.where(where_clause.ast) unless where_clause.empty?
|
|
1039
1144
|
arel.having(having_clause.ast) unless having_clause.empty?
|
|
1040
|
-
if limit_value
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
connection.sanitize_limit(limit_value),
|
|
1044
|
-
Type.default_value,
|
|
1045
|
-
)
|
|
1046
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
|
1047
|
-
end
|
|
1048
|
-
if offset_value
|
|
1049
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
|
1050
|
-
"OFFSET",
|
|
1051
|
-
offset_value.to_i,
|
|
1052
|
-
Type.default_value,
|
|
1053
|
-
)
|
|
1054
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
|
1055
|
-
end
|
|
1056
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
|
1145
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
|
1146
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
|
1147
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
|
1057
1148
|
|
|
1058
1149
|
build_order(arel)
|
|
1059
|
-
|
|
1060
1150
|
build_select(arel)
|
|
1061
1151
|
|
|
1062
1152
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
1063
1153
|
arel.distinct(distinct_value)
|
|
1064
1154
|
arel.from(build_from) unless from_clause.empty?
|
|
1065
1155
|
arel.lock(lock_value) if lock_value
|
|
1066
|
-
|
|
1156
|
+
|
|
1157
|
+
unless annotate_values.empty?
|
|
1158
|
+
annotates = annotate_values
|
|
1159
|
+
annotates = annotates.uniq if annotates.size > 1
|
|
1160
|
+
unless annotates == annotate_values
|
|
1161
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
1162
|
+
Duplicated query annotations are no longer shown in queries in Rails 7.0.
|
|
1163
|
+
To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
|
1164
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
|
1165
|
+
MSG
|
|
1166
|
+
annotates = annotate_values
|
|
1167
|
+
end
|
|
1168
|
+
arel.comment(*annotates)
|
|
1169
|
+
end
|
|
1067
1170
|
|
|
1068
1171
|
arel
|
|
1069
1172
|
end
|
|
1070
1173
|
|
|
1174
|
+
def build_cast_value(name, value)
|
|
1175
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
|
1176
|
+
Arel::Nodes::BindParam.new(cast_value)
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1071
1179
|
def build_from
|
|
1072
1180
|
opts = from_clause.value
|
|
1073
1181
|
name = from_clause.name
|
|
@@ -1083,99 +1191,101 @@ module ActiveRecord
|
|
|
1083
1191
|
end
|
|
1084
1192
|
end
|
|
1085
1193
|
|
|
1086
|
-
def select_association_list(associations)
|
|
1194
|
+
def select_association_list(associations, stashed_joins = nil)
|
|
1087
1195
|
result = []
|
|
1088
1196
|
associations.each do |association|
|
|
1089
1197
|
case association
|
|
1090
1198
|
when Hash, Symbol, Array
|
|
1091
1199
|
result << association
|
|
1200
|
+
when ActiveRecord::Associations::JoinDependency
|
|
1201
|
+
stashed_joins&.<< association
|
|
1092
1202
|
else
|
|
1093
|
-
yield if block_given?
|
|
1203
|
+
yield association if block_given?
|
|
1094
1204
|
end
|
|
1095
1205
|
end
|
|
1096
1206
|
result
|
|
1097
1207
|
end
|
|
1098
1208
|
|
|
1099
|
-
|
|
1100
|
-
select_association_list(associations) do
|
|
1101
|
-
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1102
|
-
end
|
|
1209
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
|
1103
1210
|
end
|
|
1104
1211
|
|
|
1105
|
-
def
|
|
1106
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1107
|
-
buckets[:association_join] = valid_association_list(outer_joins)
|
|
1108
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
|
1109
|
-
end
|
|
1110
|
-
|
|
1111
|
-
def build_joins(manager, joins, aliases)
|
|
1212
|
+
def build_join_buckets
|
|
1112
1213
|
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1113
1214
|
|
|
1114
1215
|
unless left_outer_joins_values.empty?
|
|
1115
|
-
|
|
1116
|
-
|
|
1216
|
+
stashed_left_joins = []
|
|
1217
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
|
1218
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
if joins_values.empty?
|
|
1222
|
+
buckets[:association_join] = left_joins
|
|
1223
|
+
buckets[:stashed_join] = stashed_left_joins
|
|
1224
|
+
return buckets, Arel::Nodes::OuterJoin
|
|
1225
|
+
else
|
|
1226
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
|
1227
|
+
end
|
|
1117
1228
|
end
|
|
1118
1229
|
|
|
1230
|
+
joins = joins_values.dup
|
|
1119
1231
|
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
|
1120
|
-
|
|
1232
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
|
1121
1233
|
end
|
|
1122
1234
|
|
|
1123
|
-
joins.
|
|
1124
|
-
if join.is_a?(String)
|
|
1125
|
-
|
|
1126
|
-
else
|
|
1127
|
-
join
|
|
1128
|
-
end
|
|
1129
|
-
end.delete_if(&:blank?).uniq!
|
|
1235
|
+
joins.each_with_index do |join, i|
|
|
1236
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
|
1237
|
+
end
|
|
1130
1238
|
|
|
1131
1239
|
while joins.first.is_a?(Arel::Nodes::Join)
|
|
1132
1240
|
join_node = joins.shift
|
|
1133
|
-
if join_node.is_a?(Arel::Nodes::
|
|
1241
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
|
1134
1242
|
buckets[:join_node] << join_node
|
|
1135
1243
|
else
|
|
1136
1244
|
buckets[:leading_join] << join_node
|
|
1137
1245
|
end
|
|
1138
1246
|
end
|
|
1139
1247
|
|
|
1140
|
-
joins
|
|
1141
|
-
|
|
1142
|
-
when Hash, Symbol, Array
|
|
1143
|
-
buckets[:association_join] << join
|
|
1144
|
-
when ActiveRecord::Associations::JoinDependency
|
|
1145
|
-
buckets[:stashed_join] << join
|
|
1146
|
-
when Arel::Nodes::Join
|
|
1248
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
|
1249
|
+
if join.is_a?(Arel::Nodes::Join)
|
|
1147
1250
|
buckets[:join_node] << join
|
|
1148
1251
|
else
|
|
1149
1252
|
raise "unknown class: %s" % join.class.name
|
|
1150
1253
|
end
|
|
1151
1254
|
end
|
|
1152
1255
|
|
|
1153
|
-
|
|
1256
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
|
1257
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
|
1258
|
+
|
|
1259
|
+
return buckets, Arel::Nodes::InnerJoin
|
|
1154
1260
|
end
|
|
1155
1261
|
|
|
1156
|
-
def
|
|
1262
|
+
def build_joins(join_sources, aliases = nil)
|
|
1263
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
|
1264
|
+
|
|
1265
|
+
buckets, join_type = build_join_buckets
|
|
1266
|
+
|
|
1157
1267
|
association_joins = buckets[:association_join]
|
|
1158
1268
|
stashed_joins = buckets[:stashed_join]
|
|
1159
1269
|
leading_joins = buckets[:leading_join]
|
|
1160
1270
|
join_nodes = buckets[:join_node]
|
|
1161
1271
|
|
|
1162
|
-
join_sources = manager.join_sources
|
|
1163
1272
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
|
1164
1273
|
|
|
1165
1274
|
unless association_joins.empty? && stashed_joins.empty?
|
|
1166
1275
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
|
1167
1276
|
join_dependency = construct_join_dependency(association_joins, join_type)
|
|
1168
|
-
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
|
1277
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
|
1169
1278
|
end
|
|
1170
1279
|
|
|
1171
1280
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
|
1281
|
+
join_sources
|
|
1172
1282
|
end
|
|
1173
1283
|
|
|
1174
1284
|
def build_select(arel)
|
|
1175
1285
|
if select_values.any?
|
|
1176
|
-
arel.project(*arel_columns(select_values
|
|
1286
|
+
arel.project(*arel_columns(select_values))
|
|
1177
1287
|
elsif klass.ignored_columns.any?
|
|
1178
|
-
arel.project(*klass.column_names.map { |field|
|
|
1288
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
|
1179
1289
|
else
|
|
1180
1290
|
arel.project(table[Arel.star])
|
|
1181
1291
|
end
|
|
@@ -1203,19 +1313,26 @@ module ActiveRecord
|
|
|
1203
1313
|
from = from_clause.name || from_clause.value
|
|
1204
1314
|
|
|
1205
1315
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
|
1206
|
-
|
|
1316
|
+
table[field]
|
|
1317
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
|
1318
|
+
table, column = field.split(".")
|
|
1319
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
|
1320
|
+
lookup_table_klass_from_join_dependencies(table)
|
|
1321
|
+
end
|
|
1207
1322
|
else
|
|
1208
1323
|
yield field
|
|
1209
1324
|
end
|
|
1210
1325
|
end
|
|
1211
1326
|
|
|
1212
1327
|
def table_name_matches?(from)
|
|
1213
|
-
|
|
1328
|
+
table_name = Regexp.escape(table.name)
|
|
1329
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
|
1330
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
1214
1331
|
end
|
|
1215
1332
|
|
|
1216
1333
|
def reverse_sql_order(order_query)
|
|
1217
1334
|
if order_query.empty?
|
|
1218
|
-
return [
|
|
1335
|
+
return [table[primary_key].desc] if primary_key
|
|
1219
1336
|
raise IrreversibleOrderError,
|
|
1220
1337
|
"Relation has no current order and table has no primary key to be used as default order"
|
|
1221
1338
|
end
|
|
@@ -1226,6 +1343,8 @@ module ActiveRecord
|
|
|
1226
1343
|
o.desc
|
|
1227
1344
|
when Arel::Nodes::Ordering
|
|
1228
1345
|
o.reverse
|
|
1346
|
+
when Arel::Nodes::NodeExpression
|
|
1347
|
+
o.desc
|
|
1229
1348
|
when String
|
|
1230
1349
|
if does_not_support_reverse?(o)
|
|
1231
1350
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
|
@@ -1252,9 +1371,7 @@ module ActiveRecord
|
|
|
1252
1371
|
end
|
|
1253
1372
|
|
|
1254
1373
|
def build_order(arel)
|
|
1255
|
-
orders = order_values.
|
|
1256
|
-
orders.reject!(&:blank?)
|
|
1257
|
-
|
|
1374
|
+
orders = order_values.compact_blank
|
|
1258
1375
|
arel.order(*orders) unless orders.empty?
|
|
1259
1376
|
end
|
|
1260
1377
|
|
|
@@ -1274,12 +1391,6 @@ module ActiveRecord
|
|
|
1274
1391
|
end
|
|
1275
1392
|
|
|
1276
1393
|
def preprocess_order_args(order_args)
|
|
1277
|
-
order_args.reject!(&:blank?)
|
|
1278
|
-
order_args.map! do |arg|
|
|
1279
|
-
klass.sanitize_sql_for_order(arg)
|
|
1280
|
-
end
|
|
1281
|
-
order_args.flatten!
|
|
1282
|
-
|
|
1283
1394
|
@klass.disallow_raw_sql!(
|
|
1284
1395
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
|
1285
1396
|
permit: connection.column_name_with_order_matcher
|
|
@@ -1287,9 +1398,8 @@ module ActiveRecord
|
|
|
1287
1398
|
|
|
1288
1399
|
validate_order_args(order_args)
|
|
1289
1400
|
|
|
1290
|
-
references = order_args
|
|
1291
|
-
|
|
1292
|
-
references!(references) if references.any?
|
|
1401
|
+
references = column_references(order_args)
|
|
1402
|
+
self.references_values |= references unless references.empty?
|
|
1293
1403
|
|
|
1294
1404
|
# if a symbol is given we prepend the quoted table name
|
|
1295
1405
|
order_args.map! do |arg|
|
|
@@ -1300,9 +1410,9 @@ module ActiveRecord
|
|
|
1300
1410
|
arg.map { |field, dir|
|
|
1301
1411
|
case field
|
|
1302
1412
|
when Arel::Nodes::SqlLiteral
|
|
1303
|
-
field.
|
|
1413
|
+
field.public_send(dir.downcase)
|
|
1304
1414
|
else
|
|
1305
|
-
order_column(field.to_s).
|
|
1415
|
+
order_column(field.to_s).public_send(dir.downcase)
|
|
1306
1416
|
end
|
|
1307
1417
|
}
|
|
1308
1418
|
else
|
|
@@ -1311,16 +1421,54 @@ module ActiveRecord
|
|
|
1311
1421
|
end.flatten!
|
|
1312
1422
|
end
|
|
1313
1423
|
|
|
1424
|
+
def sanitize_order_arguments(order_args)
|
|
1425
|
+
order_args.map! do |arg|
|
|
1426
|
+
klass.sanitize_sql_for_order(arg)
|
|
1427
|
+
end
|
|
1428
|
+
order_args.flatten!
|
|
1429
|
+
order_args.compact_blank!
|
|
1430
|
+
end
|
|
1431
|
+
|
|
1432
|
+
def column_references(order_args)
|
|
1433
|
+
references = order_args.grep(String)
|
|
1434
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
|
1435
|
+
references
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1314
1438
|
def order_column(field)
|
|
1315
1439
|
arel_column(field) do |attr_name|
|
|
1316
1440
|
if attr_name == "count" && !group_values.empty?
|
|
1317
|
-
|
|
1441
|
+
table[attr_name]
|
|
1318
1442
|
else
|
|
1319
1443
|
Arel.sql(connection.quote_table_name(attr_name))
|
|
1320
1444
|
end
|
|
1321
1445
|
end
|
|
1322
1446
|
end
|
|
1323
1447
|
|
|
1448
|
+
def resolve_arel_attributes(attrs)
|
|
1449
|
+
attrs.flat_map do |attr|
|
|
1450
|
+
case attr
|
|
1451
|
+
when Arel::Predications
|
|
1452
|
+
attr
|
|
1453
|
+
when Hash
|
|
1454
|
+
attr.flat_map do |table, columns|
|
|
1455
|
+
table = table.to_s
|
|
1456
|
+
Array(columns).map do |column|
|
|
1457
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
|
1458
|
+
end
|
|
1459
|
+
end
|
|
1460
|
+
else
|
|
1461
|
+
attr = attr.to_s
|
|
1462
|
+
if attr.include?(".")
|
|
1463
|
+
table, column = attr.split(".", 2)
|
|
1464
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
|
1465
|
+
else
|
|
1466
|
+
attr
|
|
1467
|
+
end
|
|
1468
|
+
end
|
|
1469
|
+
end
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1324
1472
|
# Checks to make sure that the arguments are not blank. Note that if some
|
|
1325
1473
|
# blank-like object were initially passed into the query method, then this
|
|
1326
1474
|
# method will not raise an error.
|
|
@@ -1337,35 +1485,40 @@ module ActiveRecord
|
|
|
1337
1485
|
# check_if_method_has_arguments!("references", args)
|
|
1338
1486
|
# ...
|
|
1339
1487
|
# end
|
|
1340
|
-
def check_if_method_has_arguments!(method_name, args)
|
|
1488
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
|
1341
1489
|
if args.blank?
|
|
1342
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
|
1490
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
|
1491
|
+
elsif block_given?
|
|
1492
|
+
yield args
|
|
1493
|
+
else
|
|
1494
|
+
args.flatten!
|
|
1495
|
+
args.compact_blank!
|
|
1343
1496
|
end
|
|
1344
1497
|
end
|
|
1345
1498
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1499
|
+
STRUCTURAL_VALUE_METHODS = (
|
|
1500
|
+
Relation::VALUE_METHODS -
|
|
1501
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
|
1502
|
+
).freeze # :nodoc:
|
|
1503
|
+
|
|
1504
|
+
def structurally_incompatible_values_for(other)
|
|
1348
1505
|
values = other.values
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1506
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
|
1507
|
+
v1, v2 = @values[method], values[method]
|
|
1508
|
+
if v1.is_a?(Array)
|
|
1509
|
+
next true unless v2.is_a?(Array)
|
|
1510
|
+
v1 = v1.uniq
|
|
1511
|
+
v2 = v2.uniq
|
|
1512
|
+
end
|
|
1513
|
+
v1 == v2
|
|
1352
1514
|
end
|
|
1353
1515
|
end
|
|
1516
|
+
end
|
|
1354
1517
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
DEFAULT_VALUES = {
|
|
1361
|
-
create_with: FROZEN_EMPTY_HASH,
|
|
1362
|
-
where: Relation::WhereClause.empty,
|
|
1363
|
-
having: Relation::WhereClause.empty,
|
|
1364
|
-
from: Relation::FromClause.empty
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
|
1368
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
|
1369
|
-
end
|
|
1518
|
+
class Relation # :nodoc:
|
|
1519
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
|
1520
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
|
1521
|
+
class WhereClauseFactory # :nodoc:
|
|
1522
|
+
end
|
|
1370
1523
|
end
|
|
1371
1524
|
end
|