activerecord 6.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +872 -582
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record.rb +7 -13
- data/lib/active_record/aggregations.rb +1 -2
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations.rb +116 -13
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +49 -29
- data/lib/active_record/associations/association_scope.rb +17 -15
- data/lib/active_record/associations/belongs_to_association.rb +15 -5
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +9 -3
- 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 +25 -8
- 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.rb +77 -42
- data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/preloader.rb +13 -8
- data/lib/active_record/associations/preloader/association.rb +51 -25
- data/lib/active_record/associations/preloader/through_association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/attribute_assignment.rb +10 -9
- data/lib/active_record/attribute_methods.rb +64 -54
- 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/attributes.rb +32 -8
- data/lib/active_record/autosave_association.rb +63 -44
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +153 -24
- data/lib/active_record/coders/yaml_column.rb +1 -2
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +86 -37
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +4 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- 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 +137 -52
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
- data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
- data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -76
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
- 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 +31 -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 +1 -1
- 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 +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -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 +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -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.rb +2 -0
- 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/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/quoting.rb +4 -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 +81 -57
- data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -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 +61 -57
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_handling.rb +211 -81
- data/lib/active_record/core.rb +237 -69
- data/lib/active_record/counter_cache.rb +4 -1
- data/lib/active_record/database_configurations.rb +124 -85
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -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/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 +40 -16
- 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 +54 -11
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +40 -21
- data/lib/active_record/insert_all.rb +39 -10
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +16 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +22 -17
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +27 -9
- data/lib/active_record/middleware/database_selector.rb +4 -2
- data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/migration.rb +114 -84
- data/lib/active_record/migration/command_recorder.rb +53 -45
- data/lib/active_record/migration/compatibility.rb +70 -20
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/model_schema.rb +120 -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/databases.rake +267 -93
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +77 -63
- data/lib/active_record/relation.rb +108 -67
- data/lib/active_record/relation/batches.rb +38 -32
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/calculations.rb +102 -45
- data/lib/active_record/relation/delegation.rb +9 -7
- data/lib/active_record/relation/finder_methods.rb +55 -17
- 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.rb +55 -35
- 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 +3 -3
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +340 -180
- 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 +104 -58
- 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.rb +0 -1
- data/lib/active_record/scoping/default.rb +0 -1
- data/lib/active_record/scoping/named.rb +7 -18
- 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 +3 -3
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +39 -36
- data/lib/active_record/tasks/database_tasks.rb +139 -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 +38 -16
- data/lib/active_record/timestamp.rb +4 -7
- data/lib/active_record/touch_later.rb +20 -21
- data/lib/active_record/transactions.rb +22 -71
- data/lib/active_record/type.rb +8 -2
- 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_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations.rb +3 -3
- 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/arel.rb +15 -12
- 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.rb +3 -1
- 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 +72 -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/predications.rb +17 -24
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors.rb +0 -7
- 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/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration.rb +6 -2
- 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/model/model_generator.rb +38 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +27 -24
- 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
@@ -20,20 +20,19 @@ module ActiveRecord
|
|
20
20
|
case values.length
|
21
21
|
when 0 then NullPredicate
|
22
22
|
when 1 then predicate_builder.build(attribute, values.first)
|
23
|
-
else
|
24
|
-
values.map! do |v|
|
25
|
-
predicate_builder.build_bind_attribute(attribute.name, v)
|
26
|
-
end
|
27
|
-
values.empty? ? NullPredicate : attribute.in(values)
|
23
|
+
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
28
24
|
end
|
29
25
|
|
30
26
|
unless nils.empty?
|
31
|
-
values_predicate = values_predicate.or(
|
27
|
+
values_predicate = values_predicate.or(attribute.eq(nil))
|
32
28
|
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
if ranges.empty?
|
31
|
+
values_predicate
|
32
|
+
else
|
33
|
+
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
|
34
|
+
array_predicates.inject(values_predicate, &:or)
|
35
|
+
end
|
37
36
|
end
|
38
37
|
|
39
38
|
private
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def queries
|
12
|
-
[associated_table.
|
12
|
+
[associated_table.join_foreign_key => ids]
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
@@ -27,13 +27,12 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def primary_key
|
30
|
-
associated_table.
|
30
|
+
associated_table.join_primary_key
|
31
31
|
end
|
32
32
|
|
33
33
|
def convert_to_id(value)
|
34
|
-
|
35
|
-
|
36
|
-
value._read_attribute(primary_key)
|
34
|
+
if value.respond_to?(:id)
|
35
|
+
value.id
|
37
36
|
else
|
38
37
|
value
|
39
38
|
end
|
@@ -11,8 +11,8 @@ module ActiveRecord
|
|
11
11
|
def queries
|
12
12
|
type_to_ids_mapping.map do |type, ids|
|
13
13
|
{
|
14
|
-
associated_table.
|
15
|
-
associated_table.
|
14
|
+
associated_table.join_foreign_type => type,
|
15
|
+
associated_table.join_foreign_key => ids
|
16
16
|
}
|
17
17
|
end
|
18
18
|
end
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def primary_key(value)
|
31
|
-
associated_table.
|
31
|
+
associated_table.join_primary_key(klass(value))
|
32
32
|
end
|
33
33
|
|
34
34
|
def klass(value)
|
@@ -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
|
@@ -1000,6 +1046,14 @@ module ActiveRecord
|
|
1000
1046
|
self
|
1001
1047
|
end
|
1002
1048
|
|
1049
|
+
# Deduplicate multiple values.
|
1050
|
+
def uniq!(name)
|
1051
|
+
if values = @values[name]
|
1052
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1053
|
+
end
|
1054
|
+
self
|
1055
|
+
end
|
1056
|
+
|
1003
1057
|
# Returns the Arel object associated with the relation.
|
1004
1058
|
def arel(aliases = nil) # :nodoc:
|
1005
1059
|
@arel ||= build_arel(aliases)
|
@@ -1020,7 +1074,57 @@ module ActiveRecord
|
|
1020
1074
|
end
|
1021
1075
|
end
|
1022
1076
|
|
1077
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1078
|
+
opts = sanitize_forbidden_attributes(opts)
|
1079
|
+
|
1080
|
+
case opts
|
1081
|
+
when String, Array
|
1082
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1083
|
+
when Hash
|
1084
|
+
opts = opts.stringify_keys
|
1085
|
+
references = PredicateBuilder.references(opts)
|
1086
|
+
self.references_values |= references unless references.empty?
|
1087
|
+
|
1088
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1089
|
+
lookup_reflection_from_join_dependencies(table_name)
|
1090
|
+
end
|
1091
|
+
when Arel::Nodes::Node
|
1092
|
+
parts = [opts]
|
1093
|
+
else
|
1094
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
Relation::WhereClause.new(parts)
|
1098
|
+
end
|
1099
|
+
alias :build_having_clause :build_where_clause
|
1100
|
+
|
1023
1101
|
private
|
1102
|
+
def lookup_reflection_from_join_dependencies(table_name)
|
1103
|
+
each_join_dependencies do |join|
|
1104
|
+
return join.reflection if table_name == join.table_name
|
1105
|
+
end
|
1106
|
+
nil
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1110
|
+
join_dependencies.each do |join_dependency|
|
1111
|
+
join_dependency.each do |join|
|
1112
|
+
yield join
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def build_join_dependencies
|
1118
|
+
associations = joins_values | left_outer_joins_values
|
1119
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1120
|
+
associations |= includes_values unless includes_values.empty?
|
1121
|
+
|
1122
|
+
join_dependencies = []
|
1123
|
+
join_dependencies.unshift construct_join_dependency(
|
1124
|
+
select_association_list(associations, join_dependencies), nil
|
1125
|
+
)
|
1126
|
+
end
|
1127
|
+
|
1024
1128
|
def assert_mutability!
|
1025
1129
|
raise ImmutableRelation if @loaded
|
1026
1130
|
raise ImmutableRelation if defined?(@arel) && @arel
|
@@ -1029,45 +1133,44 @@ module ActiveRecord
|
|
1029
1133
|
def build_arel(aliases)
|
1030
1134
|
arel = Arel::SelectManager.new(table)
|
1031
1135
|
|
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
|
1136
|
+
build_joins(arel.join_sources, aliases)
|
1037
1137
|
|
1038
1138
|
arel.where(where_clause.ast) unless where_clause.empty?
|
1039
1139
|
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?
|
1140
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1141
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1142
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1057
1143
|
|
1058
1144
|
build_order(arel)
|
1059
|
-
|
1060
1145
|
build_select(arel)
|
1061
1146
|
|
1062
1147
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1063
1148
|
arel.distinct(distinct_value)
|
1064
1149
|
arel.from(build_from) unless from_clause.empty?
|
1065
1150
|
arel.lock(lock_value) if lock_value
|
1066
|
-
|
1151
|
+
|
1152
|
+
unless annotate_values.empty?
|
1153
|
+
annotates = annotate_values
|
1154
|
+
annotates = annotates.uniq if annotates.size > 1
|
1155
|
+
unless annotates == annotate_values
|
1156
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1157
|
+
Duplicated query annotations are no longer shown in queries in Rails 6.2.
|
1158
|
+
To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1159
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1160
|
+
MSG
|
1161
|
+
annotates = annotate_values
|
1162
|
+
end
|
1163
|
+
arel.comment(*annotates)
|
1164
|
+
end
|
1067
1165
|
|
1068
1166
|
arel
|
1069
1167
|
end
|
1070
1168
|
|
1169
|
+
def build_cast_value(name, value)
|
1170
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1171
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1172
|
+
end
|
1173
|
+
|
1071
1174
|
def build_from
|
1072
1175
|
opts = from_clause.value
|
1073
1176
|
name = from_clause.name
|
@@ -1083,87 +1186,101 @@ module ActiveRecord
|
|
1083
1186
|
end
|
1084
1187
|
end
|
1085
1188
|
|
1086
|
-
def
|
1189
|
+
def select_association_list(associations, stashed_joins = nil)
|
1190
|
+
result = []
|
1087
1191
|
associations.each do |association|
|
1088
1192
|
case association
|
1089
1193
|
when Hash, Symbol, Array
|
1090
|
-
|
1194
|
+
result << association
|
1195
|
+
when ActiveRecord::Associations::JoinDependency
|
1196
|
+
stashed_joins&.<< association
|
1091
1197
|
else
|
1092
|
-
|
1198
|
+
yield association if block_given?
|
1093
1199
|
end
|
1094
1200
|
end
|
1201
|
+
result
|
1095
1202
|
end
|
1096
1203
|
|
1097
|
-
|
1098
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
1099
|
-
buckets[:association_join] = valid_association_list(outer_joins)
|
1100
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
1204
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1101
1205
|
end
|
1102
1206
|
|
1103
|
-
def
|
1207
|
+
def build_join_buckets
|
1104
1208
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1105
1209
|
|
1106
1210
|
unless left_outer_joins_values.empty?
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1211
|
+
stashed_left_joins = []
|
1212
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1213
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1214
|
+
end
|
1110
1215
|
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1216
|
+
if joins_values.empty?
|
1217
|
+
buckets[:association_join] = left_joins
|
1218
|
+
buckets[:stashed_join] = stashed_left_joins
|
1219
|
+
return buckets, Arel::Nodes::OuterJoin
|
1114
1220
|
else
|
1115
|
-
|
1221
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1116
1222
|
end
|
1117
|
-
end
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
joins = joins_values.dup
|
1226
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1227
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
joins.each_with_index do |join, i|
|
1231
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1232
|
+
end
|
1118
1233
|
|
1119
1234
|
while joins.first.is_a?(Arel::Nodes::Join)
|
1120
1235
|
join_node = joins.shift
|
1121
|
-
if join_node.is_a?(Arel::Nodes::
|
1236
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1122
1237
|
buckets[:join_node] << join_node
|
1123
1238
|
else
|
1124
1239
|
buckets[:leading_join] << join_node
|
1125
1240
|
end
|
1126
1241
|
end
|
1127
1242
|
|
1128
|
-
joins
|
1129
|
-
|
1130
|
-
when Hash, Symbol, Array
|
1131
|
-
buckets[:association_join] << join
|
1132
|
-
when ActiveRecord::Associations::JoinDependency
|
1133
|
-
buckets[:stashed_join] << join
|
1134
|
-
when Arel::Nodes::Join
|
1243
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1244
|
+
if join.is_a?(Arel::Nodes::Join)
|
1135
1245
|
buckets[:join_node] << join
|
1136
1246
|
else
|
1137
1247
|
raise "unknown class: %s" % join.class.name
|
1138
1248
|
end
|
1139
1249
|
end
|
1140
1250
|
|
1141
|
-
|
1251
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1252
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1253
|
+
|
1254
|
+
return buckets, Arel::Nodes::InnerJoin
|
1142
1255
|
end
|
1143
1256
|
|
1144
|
-
def
|
1257
|
+
def build_joins(join_sources, aliases = nil)
|
1258
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1259
|
+
|
1260
|
+
buckets, join_type = build_join_buckets
|
1261
|
+
|
1145
1262
|
association_joins = buckets[:association_join]
|
1146
1263
|
stashed_joins = buckets[:stashed_join]
|
1147
1264
|
leading_joins = buckets[:leading_join]
|
1148
1265
|
join_nodes = buckets[:join_node]
|
1149
1266
|
|
1150
|
-
join_sources = manager.join_sources
|
1151
1267
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1152
1268
|
|
1153
1269
|
unless association_joins.empty? && stashed_joins.empty?
|
1154
1270
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1155
1271
|
join_dependency = construct_join_dependency(association_joins, join_type)
|
1156
|
-
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
1272
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1157
1273
|
end
|
1158
1274
|
|
1159
1275
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1276
|
+
join_sources
|
1160
1277
|
end
|
1161
1278
|
|
1162
1279
|
def build_select(arel)
|
1163
1280
|
if select_values.any?
|
1164
|
-
arel.project(*arel_columns(select_values
|
1281
|
+
arel.project(*arel_columns(select_values))
|
1165
1282
|
elsif klass.ignored_columns.any?
|
1166
|
-
arel.project(*klass.column_names.map { |field|
|
1283
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1167
1284
|
else
|
1168
1285
|
arel.project(table[Arel.star])
|
1169
1286
|
end
|
@@ -1191,19 +1308,26 @@ module ActiveRecord
|
|
1191
1308
|
from = from_clause.name || from_clause.value
|
1192
1309
|
|
1193
1310
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1194
|
-
|
1311
|
+
table[field]
|
1312
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1313
|
+
table, column = field.split(".")
|
1314
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1315
|
+
lookup_reflection_from_join_dependencies(table)
|
1316
|
+
end
|
1195
1317
|
else
|
1196
1318
|
yield field
|
1197
1319
|
end
|
1198
1320
|
end
|
1199
1321
|
|
1200
1322
|
def table_name_matches?(from)
|
1201
|
-
|
1323
|
+
table_name = Regexp.escape(table.name)
|
1324
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1325
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1202
1326
|
end
|
1203
1327
|
|
1204
1328
|
def reverse_sql_order(order_query)
|
1205
1329
|
if order_query.empty?
|
1206
|
-
return [
|
1330
|
+
return [table[primary_key].desc] if primary_key
|
1207
1331
|
raise IrreversibleOrderError,
|
1208
1332
|
"Relation has no current order and table has no primary key to be used as default order"
|
1209
1333
|
end
|
@@ -1214,6 +1338,8 @@ module ActiveRecord
|
|
1214
1338
|
o.desc
|
1215
1339
|
when Arel::Nodes::Ordering
|
1216
1340
|
o.reverse
|
1341
|
+
when Arel::Nodes::NodeExpression
|
1342
|
+
o.desc
|
1217
1343
|
when String
|
1218
1344
|
if does_not_support_reverse?(o)
|
1219
1345
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
@@ -1240,9 +1366,7 @@ module ActiveRecord
|
|
1240
1366
|
end
|
1241
1367
|
|
1242
1368
|
def build_order(arel)
|
1243
|
-
orders = order_values.
|
1244
|
-
orders.reject!(&:blank?)
|
1245
|
-
|
1369
|
+
orders = order_values.compact_blank
|
1246
1370
|
arel.order(*orders) unless orders.empty?
|
1247
1371
|
end
|
1248
1372
|
|
@@ -1262,12 +1386,6 @@ module ActiveRecord
|
|
1262
1386
|
end
|
1263
1387
|
|
1264
1388
|
def preprocess_order_args(order_args)
|
1265
|
-
order_args.reject!(&:blank?)
|
1266
|
-
order_args.map! do |arg|
|
1267
|
-
klass.sanitize_sql_for_order(arg)
|
1268
|
-
end
|
1269
|
-
order_args.flatten!
|
1270
|
-
|
1271
1389
|
@klass.disallow_raw_sql!(
|
1272
1390
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1273
1391
|
permit: connection.column_name_with_order_matcher
|
@@ -1275,9 +1393,8 @@ module ActiveRecord
|
|
1275
1393
|
|
1276
1394
|
validate_order_args(order_args)
|
1277
1395
|
|
1278
|
-
references = order_args
|
1279
|
-
|
1280
|
-
references!(references) if references.any?
|
1396
|
+
references = column_references(order_args)
|
1397
|
+
self.references_values |= references unless references.empty?
|
1281
1398
|
|
1282
1399
|
# if a symbol is given we prepend the quoted table name
|
1283
1400
|
order_args.map! do |arg|
|
@@ -1288,9 +1405,9 @@ module ActiveRecord
|
|
1288
1405
|
arg.map { |field, dir|
|
1289
1406
|
case field
|
1290
1407
|
when Arel::Nodes::SqlLiteral
|
1291
|
-
field.
|
1408
|
+
field.public_send(dir.downcase)
|
1292
1409
|
else
|
1293
|
-
order_column(field.to_s).
|
1410
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1294
1411
|
end
|
1295
1412
|
}
|
1296
1413
|
else
|
@@ -1299,16 +1416,54 @@ module ActiveRecord
|
|
1299
1416
|
end.flatten!
|
1300
1417
|
end
|
1301
1418
|
|
1419
|
+
def sanitize_order_arguments(order_args)
|
1420
|
+
order_args.map! do |arg|
|
1421
|
+
klass.sanitize_sql_for_order(arg)
|
1422
|
+
end
|
1423
|
+
order_args.flatten!
|
1424
|
+
order_args.compact_blank!
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
def column_references(order_args)
|
1428
|
+
references = order_args.grep(String)
|
1429
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1430
|
+
references
|
1431
|
+
end
|
1432
|
+
|
1302
1433
|
def order_column(field)
|
1303
1434
|
arel_column(field) do |attr_name|
|
1304
1435
|
if attr_name == "count" && !group_values.empty?
|
1305
|
-
|
1436
|
+
table[attr_name]
|
1306
1437
|
else
|
1307
1438
|
Arel.sql(connection.quote_table_name(attr_name))
|
1308
1439
|
end
|
1309
1440
|
end
|
1310
1441
|
end
|
1311
1442
|
|
1443
|
+
def resolve_arel_attributes(attrs)
|
1444
|
+
attrs.flat_map do |attr|
|
1445
|
+
case attr
|
1446
|
+
when Arel::Predications
|
1447
|
+
attr
|
1448
|
+
when Hash
|
1449
|
+
attr.flat_map do |table, columns|
|
1450
|
+
table = table.to_s
|
1451
|
+
Array(columns).map do |column|
|
1452
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
else
|
1456
|
+
attr = attr.to_s
|
1457
|
+
if attr.include?(".")
|
1458
|
+
table, column = attr.split(".", 2)
|
1459
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1460
|
+
else
|
1461
|
+
attr
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
|
1312
1467
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1313
1468
|
# blank-like object were initially passed into the query method, then this
|
1314
1469
|
# method will not raise an error.
|
@@ -1325,35 +1480,40 @@ module ActiveRecord
|
|
1325
1480
|
# check_if_method_has_arguments!("references", args)
|
1326
1481
|
# ...
|
1327
1482
|
# end
|
1328
|
-
def check_if_method_has_arguments!(method_name, args)
|
1483
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1329
1484
|
if args.blank?
|
1330
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1485
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1486
|
+
elsif block_given?
|
1487
|
+
yield args
|
1488
|
+
else
|
1489
|
+
args.flatten!
|
1490
|
+
args.compact_blank!
|
1331
1491
|
end
|
1332
1492
|
end
|
1333
1493
|
|
1334
|
-
|
1335
|
-
|
1494
|
+
STRUCTURAL_VALUE_METHODS = (
|
1495
|
+
Relation::VALUE_METHODS -
|
1496
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1497
|
+
).freeze # :nodoc:
|
1498
|
+
|
1499
|
+
def structurally_incompatible_values_for(other)
|
1336
1500
|
values = other.values
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1501
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1502
|
+
v1, v2 = @values[method], values[method]
|
1503
|
+
if v1.is_a?(Array)
|
1504
|
+
next true unless v2.is_a?(Array)
|
1505
|
+
v1 = v1.uniq
|
1506
|
+
v2 = v2.uniq
|
1507
|
+
end
|
1508
|
+
v1 == v2
|
1340
1509
|
end
|
1341
1510
|
end
|
1511
|
+
end
|
1342
1512
|
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
DEFAULT_VALUES = {
|
1349
|
-
create_with: FROZEN_EMPTY_HASH,
|
1350
|
-
where: Relation::WhereClause.empty,
|
1351
|
-
having: Relation::WhereClause.empty,
|
1352
|
-
from: Relation::FromClause.empty
|
1353
|
-
}
|
1354
|
-
|
1355
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1356
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1357
|
-
end
|
1513
|
+
class Relation # :nodoc:
|
1514
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1515
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1516
|
+
class WhereClauseFactory # :nodoc:
|
1517
|
+
end
|
1358
1518
|
end
|
1359
1519
|
end
|