activerecord 6.0.3 → 6.1.3
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 +968 -682
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record.rb +7 -14
- data/lib/active_record/aggregations.rb +5 -5
- data/lib/active_record/association_relation.rb +30 -12
- data/lib/active_record/associations.rb +118 -11
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +44 -28
- data/lib/active_record/associations/association_scope.rb +19 -15
- data/lib/active_record/associations/belongs_to_association.rb +22 -8
- 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 -1
- 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 +19 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -5
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -2
- 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 +72 -50
- 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/preloader.rb +11 -5
- 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 -8
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +1 -11
- data/lib/active_record/attribute_methods/primary_key.rb +6 -2
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -11
- data/lib/active_record/attribute_methods/serialization.rb +11 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
- data/lib/active_record/attribute_methods/write.rb +12 -20
- data/lib/active_record/attributes.rb +33 -8
- data/lib/active_record/autosave_association.rb +57 -40
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +1 -1
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
- 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 +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
- data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +129 -88
- 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/database_statements.rb +23 -25
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
- 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 +11 -7
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
- data/lib/active_record/connection_adapters/pool_config.rb +73 -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 +13 -54
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- 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/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
- 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/point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +74 -63
- 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 +31 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
- data/lib/active_record/connection_handling.rb +218 -71
- data/lib/active_record/core.rb +245 -61
- 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 -40
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +82 -38
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -4
- 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 -2
- data/lib/active_record/fixtures.rb +58 -9
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +35 -6
- 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 +33 -17
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +27 -8
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- 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 +47 -27
- data/lib/active_record/migration/compatibility.rb +68 -17
- data/lib/active_record/model_schema.rb +117 -13
- data/lib/active_record/nested_attributes.rb +2 -3
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +50 -45
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +11 -6
- data/lib/active_record/railtie.rb +64 -44
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +276 -99
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +71 -57
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/calculations.rb +101 -44
- data/lib/active_record/relation/delegation.rb +2 -1
- data/lib/active_record/relation/finder_methods.rb +45 -15
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +27 -25
- data/lib/active_record/relation/predicate_builder.rb +61 -38
- 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/query_methods.rb +333 -195
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -7
- data/lib/active_record/relation/where_clause.rb +107 -60
- data/lib/active_record/result.rb +41 -33
- 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/named.rb +6 -17
- 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 +2 -2
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -51
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +37 -16
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +19 -66
- data/lib/active_record/type.rb +8 -1
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type/time.rb +10 -0
- 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 +1 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/arel.rb +5 -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.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 +12 -18
- 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 -2
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -4
- data/lib/arel/visitors/to_sql.rb +89 -78
- data/lib/rails/generators/active_record/migration.rb +6 -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 +3 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +28 -29
- data/lib/active_record/advisory_lock_base.rb +0 -18
- data/lib/active_record/attribute_decorators.rb +0 -88
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
- 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 -203
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -156
- data/lib/arel/visitors/oracle.rb +0 -158
- data/lib/arel/visitors/oracle12.rb +0 -65
- data/lib/arel/visitors/where_sql.rb +0 -22
@@ -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?(primary_key)
|
35
|
+
value.public_send(primary_key)
|
37
36
|
else
|
38
37
|
value
|
39
38
|
end
|
@@ -9,11 +9,13 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def queries
|
12
|
+
return [ associated_table.join_foreign_key => values ] if values.empty?
|
13
|
+
|
12
14
|
type_to_ids_mapping.map do |type, ids|
|
13
|
-
{
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
query = {}
|
16
|
+
query[associated_table.join_foreign_type] = type if type
|
17
|
+
query[associated_table.join_foreign_key] = ids
|
18
|
+
query
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
@@ -23,12 +25,12 @@ module ActiveRecord
|
|
23
25
|
def type_to_ids_mapping
|
24
26
|
default_hash = Hash.new { |hsh, key| hsh[key] = [] }
|
25
27
|
values.each_with_object(default_hash) do |value, hash|
|
26
|
-
hash[klass(value)
|
28
|
+
hash[klass(value)&.polymorphic_name] << convert_to_id(value)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
32
|
def primary_key(value)
|
31
|
-
associated_table.
|
33
|
+
associated_table.join_primary_key(klass(value))
|
32
34
|
end
|
33
35
|
|
34
36
|
def klass(value)
|
@@ -46,6 +48,8 @@ module ActiveRecord
|
|
46
48
|
value._read_attribute(primary_key(value))
|
47
49
|
when Relation
|
48
50
|
value.select(primary_key(value))
|
51
|
+
else
|
52
|
+
value
|
49
53
|
end
|
50
54
|
end
|
51
55
|
end
|
@@ -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,64 +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
|
-
|
49
|
-
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
50
|
-
|
51
|
-
if not_behaves_as_nor?(opts)
|
52
|
-
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
53
|
-
NOT conditions will no longer behave as NOR in Rails 6.1.
|
54
|
-
To continue using NOR conditions, NOT each condition individually
|
55
|
-
(`#{
|
56
|
-
opts.flat_map { |key, value|
|
57
|
-
if value.is_a?(Hash) && value.size > 1
|
58
|
-
value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
|
59
|
-
else
|
60
|
-
".where.not(#{key.inspect} => ...)"
|
61
|
-
end
|
62
|
-
}.join
|
63
|
-
}`).
|
64
|
-
MSG
|
65
|
-
@scope.where_clause += where_clause.invert(:nor)
|
66
|
-
else
|
67
|
-
@scope.where_clause += where_clause.invert
|
68
|
-
end
|
48
|
+
@scope.where_clause += where_clause.invert
|
69
49
|
|
70
50
|
@scope
|
71
51
|
end
|
72
52
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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)
|
79
77
|
end
|
78
|
+
|
79
|
+
@scope
|
80
|
+
end
|
80
81
|
end
|
81
82
|
|
82
83
|
FROZEN_EMPTY_ARRAY = [].freeze
|
83
84
|
FROZEN_EMPTY_HASH = {}.freeze
|
84
85
|
|
85
86
|
Relation::VALUE_METHODS.each do |name|
|
86
|
-
method_name =
|
87
|
+
method_name, default =
|
87
88
|
case name
|
88
|
-
when *Relation::MULTI_VALUE_METHODS
|
89
|
-
|
90
|
-
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"]
|
91
95
|
end
|
96
|
+
|
92
97
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
93
|
-
def #{method_name}
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
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
|
102
106
|
CODE
|
103
107
|
end
|
104
108
|
|
@@ -149,9 +153,6 @@ module ActiveRecord
|
|
149
153
|
end
|
150
154
|
|
151
155
|
def includes!(*args) # :nodoc:
|
152
|
-
args.reject!(&:blank?)
|
153
|
-
args.flatten!
|
154
|
-
|
155
156
|
self.includes_values |= args
|
156
157
|
self
|
157
158
|
end
|
@@ -215,9 +216,6 @@ module ActiveRecord
|
|
215
216
|
end
|
216
217
|
|
217
218
|
def references!(*table_names) # :nodoc:
|
218
|
-
table_names.flatten!
|
219
|
-
table_names.map!(&:to_s)
|
220
|
-
|
221
219
|
self.references_values |= table_names
|
222
220
|
self
|
223
221
|
end
|
@@ -271,14 +269,12 @@ module ActiveRecord
|
|
271
269
|
return super()
|
272
270
|
end
|
273
271
|
|
274
|
-
|
272
|
+
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
|
275
273
|
spawn._select!(*fields)
|
276
274
|
end
|
277
275
|
|
278
276
|
def _select!(*fields) # :nodoc:
|
279
|
-
fields
|
280
|
-
fields.flatten!
|
281
|
-
self.select_values += fields
|
277
|
+
self.select_values |= fields
|
282
278
|
self
|
283
279
|
end
|
284
280
|
|
@@ -329,9 +325,7 @@ module ActiveRecord
|
|
329
325
|
end
|
330
326
|
|
331
327
|
def group!(*args) # :nodoc:
|
332
|
-
args
|
333
|
-
|
334
|
-
self.group_values |= args
|
328
|
+
self.group_values += args
|
335
329
|
self
|
336
330
|
end
|
337
331
|
|
@@ -355,15 +349,16 @@ module ActiveRecord
|
|
355
349
|
# User.order('name DESC, email')
|
356
350
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
357
351
|
def order(*args)
|
358
|
-
check_if_method_has_arguments!(:order, args)
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
353
|
+
sanitize_order_arguments(args)
|
354
|
+
end
|
359
355
|
spawn.order!(*args)
|
360
356
|
end
|
361
357
|
|
362
358
|
# Same as #order but operates on relation in-place instead of copying.
|
363
359
|
def order!(*args) # :nodoc:
|
364
|
-
preprocess_order_args(args)
|
365
|
-
|
366
|
-
self.order_values += args
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
361
|
+
self.order_values |= args
|
367
362
|
self
|
368
363
|
end
|
369
364
|
|
@@ -377,14 +372,16 @@ module ActiveRecord
|
|
377
372
|
#
|
378
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
379
374
|
def reorder(*args)
|
380
|
-
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
|
381
378
|
spawn.reorder!(*args)
|
382
379
|
end
|
383
380
|
|
384
381
|
# Same as #reorder but operates on relation in-place instead of copying.
|
385
382
|
def reorder!(*args) # :nodoc:
|
386
383
|
preprocess_order_args(args) unless args.all?(&:blank?)
|
387
|
-
|
384
|
+
args.uniq!
|
388
385
|
self.reordering_value = true
|
389
386
|
self.order_values = args
|
390
387
|
self
|
@@ -433,7 +430,6 @@ module ActiveRecord
|
|
433
430
|
end
|
434
431
|
|
435
432
|
def unscope!(*args) # :nodoc:
|
436
|
-
args.flatten!
|
437
433
|
self.unscope_values += args
|
438
434
|
|
439
435
|
args.each do |scope|
|
@@ -444,14 +440,14 @@ module ActiveRecord
|
|
444
440
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
445
441
|
end
|
446
442
|
assert_mutability!
|
447
|
-
@values
|
443
|
+
@values.delete(scope)
|
448
444
|
when Hash
|
449
445
|
scope.each do |key, target_value|
|
450
446
|
if key != :where
|
451
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
452
448
|
end
|
453
449
|
|
454
|
-
target_values = Array(target_value)
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
455
451
|
self.where_clause = where_clause.except(*target_values)
|
456
452
|
end
|
457
453
|
else
|
@@ -484,8 +480,7 @@ module ActiveRecord
|
|
484
480
|
# # SELECT "users".*
|
485
481
|
# # FROM "users"
|
486
482
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
487
|
-
# # INNER JOIN "comments" "
|
488
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
489
484
|
#
|
490
485
|
# You can use strings in order to customize your joins:
|
491
486
|
#
|
@@ -497,8 +492,6 @@ module ActiveRecord
|
|
497
492
|
end
|
498
493
|
|
499
494
|
def joins!(*args) # :nodoc:
|
500
|
-
args.compact!
|
501
|
-
args.flatten!
|
502
495
|
self.joins_values |= args
|
503
496
|
self
|
504
497
|
end
|
@@ -515,8 +508,6 @@ module ActiveRecord
|
|
515
508
|
alias :left_joins :left_outer_joins
|
516
509
|
|
517
510
|
def left_outer_joins!(*args) # :nodoc:
|
518
|
-
args.compact!
|
519
|
-
args.flatten!
|
520
511
|
self.left_outer_joins_values |= args
|
521
512
|
self
|
522
513
|
end
|
@@ -640,20 +631,18 @@ module ActiveRecord
|
|
640
631
|
#
|
641
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
642
633
|
# the current relation.
|
643
|
-
def where(
|
644
|
-
if
|
634
|
+
def where(*args)
|
635
|
+
if args.empty?
|
645
636
|
WhereChain.new(spawn)
|
646
|
-
elsif
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
647
638
|
self
|
648
639
|
else
|
649
|
-
spawn.where!(
|
640
|
+
spawn.where!(*args)
|
650
641
|
end
|
651
642
|
end
|
652
643
|
|
653
644
|
def where!(opts, *rest) # :nodoc:
|
654
|
-
|
655
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
656
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
657
646
|
self
|
658
647
|
end
|
659
648
|
|
@@ -671,7 +660,44 @@ module ActiveRecord
|
|
671
660
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
672
661
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
673
662
|
def rewhere(conditions)
|
674
|
-
|
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
|
675
701
|
end
|
676
702
|
|
677
703
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
@@ -679,21 +705,21 @@ module ActiveRecord
|
|
679
705
|
#
|
680
706
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
681
707
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
682
|
-
# present).
|
708
|
+
# present).
|
683
709
|
#
|
684
710
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
685
711
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
686
712
|
#
|
687
713
|
def or(other)
|
688
|
-
|
714
|
+
if other.is_a?(Relation)
|
715
|
+
spawn.or!(other)
|
716
|
+
else
|
689
717
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
690
718
|
end
|
691
|
-
|
692
|
-
spawn.or!(other)
|
693
719
|
end
|
694
720
|
|
695
721
|
def or!(other) # :nodoc:
|
696
|
-
incompatible_values =
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
697
723
|
|
698
724
|
unless incompatible_values.empty?
|
699
725
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
@@ -701,7 +727,7 @@ module ActiveRecord
|
|
701
727
|
|
702
728
|
self.where_clause = self.where_clause.or(other.where_clause)
|
703
729
|
self.having_clause = having_clause.or(other.having_clause)
|
704
|
-
self.references_values
|
730
|
+
self.references_values |= other.references_values
|
705
731
|
|
706
732
|
self
|
707
733
|
end
|
@@ -715,10 +741,7 @@ module ActiveRecord
|
|
715
741
|
end
|
716
742
|
|
717
743
|
def having!(opts, *rest) # :nodoc:
|
718
|
-
|
719
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
720
|
-
|
721
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
722
745
|
self
|
723
746
|
end
|
724
747
|
|
@@ -820,6 +843,21 @@ module ActiveRecord
|
|
820
843
|
self
|
821
844
|
end
|
822
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
|
+
|
823
861
|
# Sets attributes to be used when creating new records from a
|
824
862
|
# relation object.
|
825
863
|
#
|
@@ -961,8 +999,6 @@ module ActiveRecord
|
|
961
999
|
end
|
962
1000
|
|
963
1001
|
def optimizer_hints!(*args) # :nodoc:
|
964
|
-
args.flatten!
|
965
|
-
|
966
1002
|
self.optimizer_hints_values |= args
|
967
1003
|
self
|
968
1004
|
end
|
@@ -975,8 +1011,7 @@ module ActiveRecord
|
|
975
1011
|
end
|
976
1012
|
|
977
1013
|
def reverse_order! # :nodoc:
|
978
|
-
orders = order_values.
|
979
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
980
1015
|
self.order_values = reverse_sql_order(orders)
|
981
1016
|
self
|
982
1017
|
end
|
@@ -1011,6 +1046,14 @@ module ActiveRecord
|
|
1011
1046
|
self
|
1012
1047
|
end
|
1013
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
|
+
|
1014
1057
|
# Returns the Arel object associated with the relation.
|
1015
1058
|
def arel(aliases = nil) # :nodoc:
|
1016
1059
|
@arel ||= build_arel(aliases)
|
@@ -1031,7 +1074,60 @@ module ActiveRecord
|
|
1031
1074
|
end
|
1032
1075
|
end
|
1033
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.transform_keys do |key|
|
1085
|
+
key = key.to_s
|
1086
|
+
klass.attribute_aliases[key] || key
|
1087
|
+
end
|
1088
|
+
references = PredicateBuilder.references(opts)
|
1089
|
+
self.references_values |= references unless references.empty?
|
1090
|
+
|
1091
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1092
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1093
|
+
end
|
1094
|
+
when Arel::Nodes::Node
|
1095
|
+
parts = [opts]
|
1096
|
+
else
|
1097
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
Relation::WhereClause.new(parts)
|
1101
|
+
end
|
1102
|
+
alias :build_having_clause :build_where_clause
|
1103
|
+
|
1034
1104
|
private
|
1105
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
|
+
each_join_dependencies do |join|
|
1107
|
+
return join.base_klass if table_name == join.table_name
|
1108
|
+
end
|
1109
|
+
nil
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1113
|
+
join_dependencies.each do |join_dependency|
|
1114
|
+
join_dependency.each do |join|
|
1115
|
+
yield join
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def build_join_dependencies
|
1121
|
+
associations = joins_values | left_outer_joins_values
|
1122
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1123
|
+
associations |= includes_values unless includes_values.empty?
|
1124
|
+
|
1125
|
+
join_dependencies = []
|
1126
|
+
join_dependencies.unshift construct_join_dependency(
|
1127
|
+
select_association_list(associations, join_dependencies), nil
|
1128
|
+
)
|
1129
|
+
end
|
1130
|
+
|
1035
1131
|
def assert_mutability!
|
1036
1132
|
raise ImmutableRelation if @loaded
|
1037
1133
|
raise ImmutableRelation if defined?(@arel) && @arel
|
@@ -1040,45 +1136,44 @@ module ActiveRecord
|
|
1040
1136
|
def build_arel(aliases)
|
1041
1137
|
arel = Arel::SelectManager.new(table)
|
1042
1138
|
|
1043
|
-
|
1044
|
-
build_joins(arel, joins_values.flatten, aliases)
|
1045
|
-
elsif !left_outer_joins_values.empty?
|
1046
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
|
1047
|
-
end
|
1139
|
+
build_joins(arel.join_sources, aliases)
|
1048
1140
|
|
1049
1141
|
arel.where(where_clause.ast) unless where_clause.empty?
|
1050
1142
|
arel.having(having_clause.ast) unless having_clause.empty?
|
1051
|
-
if limit_value
|
1052
|
-
|
1053
|
-
|
1054
|
-
connection.sanitize_limit(limit_value),
|
1055
|
-
Type.default_value,
|
1056
|
-
)
|
1057
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
1058
|
-
end
|
1059
|
-
if offset_value
|
1060
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
1061
|
-
"OFFSET",
|
1062
|
-
offset_value.to_i,
|
1063
|
-
Type.default_value,
|
1064
|
-
)
|
1065
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
1066
|
-
end
|
1067
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
1143
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1144
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1145
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1068
1146
|
|
1069
1147
|
build_order(arel)
|
1070
|
-
|
1071
1148
|
build_select(arel)
|
1072
1149
|
|
1073
1150
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1074
1151
|
arel.distinct(distinct_value)
|
1075
1152
|
arel.from(build_from) unless from_clause.empty?
|
1076
1153
|
arel.lock(lock_value) if lock_value
|
1077
|
-
|
1154
|
+
|
1155
|
+
unless annotate_values.empty?
|
1156
|
+
annotates = annotate_values
|
1157
|
+
annotates = annotates.uniq if annotates.size > 1
|
1158
|
+
unless annotates == annotate_values
|
1159
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1160
|
+
Duplicated query annotations are no longer shown in queries in Rails 6.2.
|
1161
|
+
To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1162
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1163
|
+
MSG
|
1164
|
+
annotates = annotate_values
|
1165
|
+
end
|
1166
|
+
arel.comment(*annotates)
|
1167
|
+
end
|
1078
1168
|
|
1079
1169
|
arel
|
1080
1170
|
end
|
1081
1171
|
|
1172
|
+
def build_cast_value(name, value)
|
1173
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1174
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1175
|
+
end
|
1176
|
+
|
1082
1177
|
def build_from
|
1083
1178
|
opts = from_clause.value
|
1084
1179
|
name = from_clause.name
|
@@ -1094,99 +1189,101 @@ module ActiveRecord
|
|
1094
1189
|
end
|
1095
1190
|
end
|
1096
1191
|
|
1097
|
-
def select_association_list(associations)
|
1192
|
+
def select_association_list(associations, stashed_joins = nil)
|
1098
1193
|
result = []
|
1099
1194
|
associations.each do |association|
|
1100
1195
|
case association
|
1101
1196
|
when Hash, Symbol, Array
|
1102
1197
|
result << association
|
1198
|
+
when ActiveRecord::Associations::JoinDependency
|
1199
|
+
stashed_joins&.<< association
|
1103
1200
|
else
|
1104
|
-
yield if block_given?
|
1201
|
+
yield association if block_given?
|
1105
1202
|
end
|
1106
1203
|
end
|
1107
1204
|
result
|
1108
1205
|
end
|
1109
1206
|
|
1110
|
-
|
1111
|
-
select_association_list(associations) do
|
1112
|
-
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1113
|
-
end
|
1207
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1114
1208
|
end
|
1115
1209
|
|
1116
|
-
def
|
1117
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
1118
|
-
buckets[:association_join] = valid_association_list(outer_joins)
|
1119
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
1120
|
-
end
|
1121
|
-
|
1122
|
-
def build_joins(manager, joins, aliases)
|
1210
|
+
def build_join_buckets
|
1123
1211
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1124
1212
|
|
1125
1213
|
unless left_outer_joins_values.empty?
|
1126
|
-
|
1127
|
-
|
1214
|
+
stashed_left_joins = []
|
1215
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1216
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
if joins_values.empty?
|
1220
|
+
buckets[:association_join] = left_joins
|
1221
|
+
buckets[:stashed_join] = stashed_left_joins
|
1222
|
+
return buckets, Arel::Nodes::OuterJoin
|
1223
|
+
else
|
1224
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1225
|
+
end
|
1128
1226
|
end
|
1129
1227
|
|
1228
|
+
joins = joins_values.dup
|
1130
1229
|
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1131
|
-
|
1230
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1132
1231
|
end
|
1133
1232
|
|
1134
|
-
joins.
|
1135
|
-
if join.is_a?(String)
|
1136
|
-
|
1137
|
-
else
|
1138
|
-
join
|
1139
|
-
end
|
1140
|
-
end.delete_if(&:blank?).uniq!
|
1233
|
+
joins.each_with_index do |join, i|
|
1234
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1235
|
+
end
|
1141
1236
|
|
1142
1237
|
while joins.first.is_a?(Arel::Nodes::Join)
|
1143
1238
|
join_node = joins.shift
|
1144
|
-
if join_node.is_a?(Arel::Nodes::
|
1239
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1145
1240
|
buckets[:join_node] << join_node
|
1146
1241
|
else
|
1147
1242
|
buckets[:leading_join] << join_node
|
1148
1243
|
end
|
1149
1244
|
end
|
1150
1245
|
|
1151
|
-
joins
|
1152
|
-
|
1153
|
-
when Hash, Symbol, Array
|
1154
|
-
buckets[:association_join] << join
|
1155
|
-
when ActiveRecord::Associations::JoinDependency
|
1156
|
-
buckets[:stashed_join] << join
|
1157
|
-
when Arel::Nodes::Join
|
1246
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1247
|
+
if join.is_a?(Arel::Nodes::Join)
|
1158
1248
|
buckets[:join_node] << join
|
1159
1249
|
else
|
1160
1250
|
raise "unknown class: %s" % join.class.name
|
1161
1251
|
end
|
1162
1252
|
end
|
1163
1253
|
|
1164
|
-
|
1254
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1255
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1256
|
+
|
1257
|
+
return buckets, Arel::Nodes::InnerJoin
|
1165
1258
|
end
|
1166
1259
|
|
1167
|
-
def
|
1260
|
+
def build_joins(join_sources, aliases = nil)
|
1261
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1262
|
+
|
1263
|
+
buckets, join_type = build_join_buckets
|
1264
|
+
|
1168
1265
|
association_joins = buckets[:association_join]
|
1169
1266
|
stashed_joins = buckets[:stashed_join]
|
1170
1267
|
leading_joins = buckets[:leading_join]
|
1171
1268
|
join_nodes = buckets[:join_node]
|
1172
1269
|
|
1173
|
-
join_sources = manager.join_sources
|
1174
1270
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1175
1271
|
|
1176
1272
|
unless association_joins.empty? && stashed_joins.empty?
|
1177
1273
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1178
1274
|
join_dependency = construct_join_dependency(association_joins, join_type)
|
1179
|
-
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
1275
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1180
1276
|
end
|
1181
1277
|
|
1182
1278
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1279
|
+
join_sources
|
1183
1280
|
end
|
1184
1281
|
|
1185
1282
|
def build_select(arel)
|
1186
1283
|
if select_values.any?
|
1187
|
-
arel.project(*arel_columns(select_values
|
1284
|
+
arel.project(*arel_columns(select_values))
|
1188
1285
|
elsif klass.ignored_columns.any?
|
1189
|
-
arel.project(*klass.column_names.map { |field|
|
1286
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1190
1287
|
else
|
1191
1288
|
arel.project(table[Arel.star])
|
1192
1289
|
end
|
@@ -1214,7 +1311,12 @@ module ActiveRecord
|
|
1214
1311
|
from = from_clause.name || from_clause.value
|
1215
1312
|
|
1216
1313
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1217
|
-
|
1314
|
+
table[field]
|
1315
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1316
|
+
table, column = field.split(".")
|
1317
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1318
|
+
lookup_table_klass_from_join_dependencies(table)
|
1319
|
+
end
|
1218
1320
|
else
|
1219
1321
|
yield field
|
1220
1322
|
end
|
@@ -1228,7 +1330,7 @@ module ActiveRecord
|
|
1228
1330
|
|
1229
1331
|
def reverse_sql_order(order_query)
|
1230
1332
|
if order_query.empty?
|
1231
|
-
return [
|
1333
|
+
return [table[primary_key].desc] if primary_key
|
1232
1334
|
raise IrreversibleOrderError,
|
1233
1335
|
"Relation has no current order and table has no primary key to be used as default order"
|
1234
1336
|
end
|
@@ -1239,6 +1341,8 @@ module ActiveRecord
|
|
1239
1341
|
o.desc
|
1240
1342
|
when Arel::Nodes::Ordering
|
1241
1343
|
o.reverse
|
1344
|
+
when Arel::Nodes::NodeExpression
|
1345
|
+
o.desc
|
1242
1346
|
when String
|
1243
1347
|
if does_not_support_reverse?(o)
|
1244
1348
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
@@ -1265,9 +1369,7 @@ module ActiveRecord
|
|
1265
1369
|
end
|
1266
1370
|
|
1267
1371
|
def build_order(arel)
|
1268
|
-
orders = order_values.
|
1269
|
-
orders.reject!(&:blank?)
|
1270
|
-
|
1372
|
+
orders = order_values.compact_blank
|
1271
1373
|
arel.order(*orders) unless orders.empty?
|
1272
1374
|
end
|
1273
1375
|
|
@@ -1287,12 +1389,6 @@ module ActiveRecord
|
|
1287
1389
|
end
|
1288
1390
|
|
1289
1391
|
def preprocess_order_args(order_args)
|
1290
|
-
order_args.reject!(&:blank?)
|
1291
|
-
order_args.map! do |arg|
|
1292
|
-
klass.sanitize_sql_for_order(arg)
|
1293
|
-
end
|
1294
|
-
order_args.flatten!
|
1295
|
-
|
1296
1392
|
@klass.disallow_raw_sql!(
|
1297
1393
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1298
1394
|
permit: connection.column_name_with_order_matcher
|
@@ -1300,9 +1396,8 @@ module ActiveRecord
|
|
1300
1396
|
|
1301
1397
|
validate_order_args(order_args)
|
1302
1398
|
|
1303
|
-
references = order_args
|
1304
|
-
|
1305
|
-
references!(references) if references.any?
|
1399
|
+
references = column_references(order_args)
|
1400
|
+
self.references_values |= references unless references.empty?
|
1306
1401
|
|
1307
1402
|
# if a symbol is given we prepend the quoted table name
|
1308
1403
|
order_args.map! do |arg|
|
@@ -1313,9 +1408,9 @@ module ActiveRecord
|
|
1313
1408
|
arg.map { |field, dir|
|
1314
1409
|
case field
|
1315
1410
|
when Arel::Nodes::SqlLiteral
|
1316
|
-
field.
|
1411
|
+
field.public_send(dir.downcase)
|
1317
1412
|
else
|
1318
|
-
order_column(field.to_s).
|
1413
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1319
1414
|
end
|
1320
1415
|
}
|
1321
1416
|
else
|
@@ -1324,16 +1419,54 @@ module ActiveRecord
|
|
1324
1419
|
end.flatten!
|
1325
1420
|
end
|
1326
1421
|
|
1422
|
+
def sanitize_order_arguments(order_args)
|
1423
|
+
order_args.map! do |arg|
|
1424
|
+
klass.sanitize_sql_for_order(arg)
|
1425
|
+
end
|
1426
|
+
order_args.flatten!
|
1427
|
+
order_args.compact_blank!
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
def column_references(order_args)
|
1431
|
+
references = order_args.grep(String)
|
1432
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1433
|
+
references
|
1434
|
+
end
|
1435
|
+
|
1327
1436
|
def order_column(field)
|
1328
1437
|
arel_column(field) do |attr_name|
|
1329
1438
|
if attr_name == "count" && !group_values.empty?
|
1330
|
-
|
1439
|
+
table[attr_name]
|
1331
1440
|
else
|
1332
1441
|
Arel.sql(connection.quote_table_name(attr_name))
|
1333
1442
|
end
|
1334
1443
|
end
|
1335
1444
|
end
|
1336
1445
|
|
1446
|
+
def resolve_arel_attributes(attrs)
|
1447
|
+
attrs.flat_map do |attr|
|
1448
|
+
case attr
|
1449
|
+
when Arel::Predications
|
1450
|
+
attr
|
1451
|
+
when Hash
|
1452
|
+
attr.flat_map do |table, columns|
|
1453
|
+
table = table.to_s
|
1454
|
+
Array(columns).map do |column|
|
1455
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
else
|
1459
|
+
attr = attr.to_s
|
1460
|
+
if attr.include?(".")
|
1461
|
+
table, column = attr.split(".", 2)
|
1462
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1463
|
+
else
|
1464
|
+
attr
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1337
1470
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1338
1471
|
# blank-like object were initially passed into the query method, then this
|
1339
1472
|
# method will not raise an error.
|
@@ -1350,35 +1483,40 @@ module ActiveRecord
|
|
1350
1483
|
# check_if_method_has_arguments!("references", args)
|
1351
1484
|
# ...
|
1352
1485
|
# end
|
1353
|
-
def check_if_method_has_arguments!(method_name, args)
|
1486
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1354
1487
|
if args.blank?
|
1355
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1488
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
+
elsif block_given?
|
1490
|
+
yield args
|
1491
|
+
else
|
1492
|
+
args.flatten!
|
1493
|
+
args.compact_blank!
|
1356
1494
|
end
|
1357
1495
|
end
|
1358
1496
|
|
1359
|
-
|
1360
|
-
|
1497
|
+
STRUCTURAL_VALUE_METHODS = (
|
1498
|
+
Relation::VALUE_METHODS -
|
1499
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1500
|
+
).freeze # :nodoc:
|
1501
|
+
|
1502
|
+
def structurally_incompatible_values_for(other)
|
1361
1503
|
values = other.values
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1504
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1505
|
+
v1, v2 = @values[method], values[method]
|
1506
|
+
if v1.is_a?(Array)
|
1507
|
+
next true unless v2.is_a?(Array)
|
1508
|
+
v1 = v1.uniq
|
1509
|
+
v2 = v2.uniq
|
1510
|
+
end
|
1511
|
+
v1 == v2
|
1365
1512
|
end
|
1366
1513
|
end
|
1514
|
+
end
|
1367
1515
|
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
DEFAULT_VALUES = {
|
1374
|
-
create_with: FROZEN_EMPTY_HASH,
|
1375
|
-
where: Relation::WhereClause.empty,
|
1376
|
-
having: Relation::WhereClause.empty,
|
1377
|
-
from: Relation::FromClause.empty
|
1378
|
-
}
|
1379
|
-
|
1380
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1381
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1382
|
-
end
|
1516
|
+
class Relation # :nodoc:
|
1517
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1518
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1519
|
+
class WhereClauseFactory # :nodoc:
|
1520
|
+
end
|
1383
1521
|
end
|
1384
1522
|
end
|