activerecord 6.0.6.1 → 6.1.0.rc1
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 +764 -942
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/association_relation.rb +22 -14
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +39 -27
- data/lib/active_record/associations/association_scope.rb +11 -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 -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 -13
- data/lib/active_record/associations/collection_proxy.rb +12 -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/join_association.rb +29 -14
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +63 -49
- data/lib/active_record/associations/preloader/association.rb +13 -5
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +5 -3
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations.rb +114 -11
- data/lib/active_record/attribute_assignment.rb +10 -8
- 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 +4 -4
- 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/attribute_methods.rb +52 -48
- data/lib/active_record/attributes.rb +27 -7
- data/lib/active_record/autosave_association.rb +47 -30
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +32 -22
- data/lib/active_record/coders/yaml_column.rb +2 -24
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +180 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/quoting.rb +35 -44
- 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 +110 -30
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
- data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -70
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
- 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 +22 -24
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
- 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 +3 -3
- 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 +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 +12 -53
- 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 +2 -10
- 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/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- 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 +72 -55
- 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 +30 -5
- 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 +36 -3
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_handling.rb +210 -71
- data/lib/active_record/core.rb +214 -58
- 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/database_configurations.rb +124 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +33 -23
- 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 +54 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +32 -5
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +15 -4
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +13 -16
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +26 -8
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/migration/command_recorder.rb +47 -27
- data/lib/active_record/migration/compatibility.rb +67 -17
- data/lib/active_record/migration.rb +113 -83
- data/lib/active_record/model_schema.rb +88 -42
- 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/databases.rake +253 -98
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +59 -44
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/calculations.rb +100 -43
- data/lib/active_record/relation/finder_methods.rb +44 -14
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +20 -23
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
- 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/predicate_builder.rb +57 -33
- data/lib/active_record/relation/query_methods.rb +319 -198
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +6 -5
- data/lib/active_record/relation/where_clause.rb +104 -57
- data/lib/active_record/relation.rb +90 -64
- 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 +0 -4
- data/lib/active_record/scoping/named.rb +1 -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 +36 -52
- data/lib/active_record/tasks/database_tasks.rb +139 -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 +36 -33
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +15 -64
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type.rb +8 -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/associated.rb +1 -1
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -14
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +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/nodes.rb +3 -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/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/arel/visitors.rb +0 -7
- data/lib/arel.rb +5 -13
- 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/migration.rb +6 -1
- 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 +30 -32
- 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
@@ -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,8 +325,6 @@ module ActiveRecord
|
|
329
325
|
end
|
330
326
|
|
331
327
|
def group!(*args) # :nodoc:
|
332
|
-
args.flatten!
|
333
|
-
|
334
328
|
self.group_values += args
|
335
329
|
self
|
336
330
|
end
|
@@ -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
|
@@ -1000,8 +1035,6 @@ module ActiveRecord
|
|
1000
1035
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1001
1036
|
#
|
1002
1037
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1003
|
-
#
|
1004
|
-
# Some escaping is performed, however untrusted user input should not be used.
|
1005
1038
|
def annotate(*args)
|
1006
1039
|
check_if_method_has_arguments!(:annotate, args)
|
1007
1040
|
spawn.annotate!(*args)
|
@@ -1013,6 +1046,14 @@ module ActiveRecord
|
|
1013
1046
|
self
|
1014
1047
|
end
|
1015
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
|
+
|
1016
1057
|
# Returns the Arel object associated with the relation.
|
1017
1058
|
def arel(aliases = nil) # :nodoc:
|
1018
1059
|
@arel ||= build_arel(aliases)
|
@@ -1033,7 +1074,57 @@ module ActiveRecord
|
|
1033
1074
|
end
|
1034
1075
|
end
|
1035
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
|
+
|
1036
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
|
+
|
1037
1128
|
def assert_mutability!
|
1038
1129
|
raise ImmutableRelation if @loaded
|
1039
1130
|
raise ImmutableRelation if defined?(@arel) && @arel
|
@@ -1042,45 +1133,44 @@ module ActiveRecord
|
|
1042
1133
|
def build_arel(aliases)
|
1043
1134
|
arel = Arel::SelectManager.new(table)
|
1044
1135
|
|
1045
|
-
|
1046
|
-
build_joins(arel, joins_values.flatten, aliases)
|
1047
|
-
elsif !left_outer_joins_values.empty?
|
1048
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
|
1049
|
-
end
|
1136
|
+
build_joins(arel.join_sources, aliases)
|
1050
1137
|
|
1051
1138
|
arel.where(where_clause.ast) unless where_clause.empty?
|
1052
1139
|
arel.having(having_clause.ast) unless having_clause.empty?
|
1053
|
-
if limit_value
|
1054
|
-
|
1055
|
-
|
1056
|
-
connection.sanitize_limit(limit_value),
|
1057
|
-
Type.default_value,
|
1058
|
-
)
|
1059
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
1060
|
-
end
|
1061
|
-
if offset_value
|
1062
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
1063
|
-
"OFFSET",
|
1064
|
-
offset_value.to_i,
|
1065
|
-
Type.default_value,
|
1066
|
-
)
|
1067
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
1068
|
-
end
|
1069
|
-
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?
|
1070
1143
|
|
1071
1144
|
build_order(arel)
|
1072
|
-
|
1073
1145
|
build_select(arel)
|
1074
1146
|
|
1075
1147
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1076
1148
|
arel.distinct(distinct_value)
|
1077
1149
|
arel.from(build_from) unless from_clause.empty?
|
1078
1150
|
arel.lock(lock_value) if lock_value
|
1079
|
-
|
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
|
1080
1165
|
|
1081
1166
|
arel
|
1082
1167
|
end
|
1083
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
|
+
|
1084
1174
|
def build_from
|
1085
1175
|
opts = from_clause.value
|
1086
1176
|
name = from_clause.name
|
@@ -1105,47 +1195,41 @@ module ActiveRecord
|
|
1105
1195
|
when ActiveRecord::Associations::JoinDependency
|
1106
1196
|
stashed_joins&.<< association
|
1107
1197
|
else
|
1108
|
-
yield if block_given?
|
1198
|
+
yield association if block_given?
|
1109
1199
|
end
|
1110
1200
|
end
|
1111
1201
|
result
|
1112
1202
|
end
|
1113
1203
|
|
1114
|
-
def valid_association_list(associations, stashed_joins)
|
1115
|
-
select_association_list(associations, stashed_joins) do
|
1116
|
-
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1117
|
-
end
|
1118
|
-
end
|
1119
|
-
|
1120
|
-
def build_left_outer_joins(manager, outer_joins, aliases)
|
1121
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
1122
|
-
buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
|
1123
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
1124
|
-
end
|
1125
|
-
|
1126
1204
|
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1127
1205
|
end
|
1128
1206
|
|
1129
|
-
def
|
1207
|
+
def build_join_buckets
|
1130
1208
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1131
1209
|
|
1132
1210
|
unless left_outer_joins_values.empty?
|
1133
1211
|
stashed_left_joins = []
|
1134
|
-
left_joins =
|
1135
|
-
|
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
|
1215
|
+
|
1216
|
+
if joins_values.empty?
|
1217
|
+
buckets[:association_join] = left_joins
|
1218
|
+
buckets[:stashed_join] = stashed_left_joins
|
1219
|
+
return buckets, Arel::Nodes::OuterJoin
|
1220
|
+
else
|
1221
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1222
|
+
end
|
1136
1223
|
end
|
1137
1224
|
|
1225
|
+
joins = joins_values.dup
|
1138
1226
|
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1139
1227
|
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1140
1228
|
end
|
1141
1229
|
|
1142
|
-
joins.
|
1143
|
-
if join.is_a?(String)
|
1144
|
-
|
1145
|
-
else
|
1146
|
-
join
|
1147
|
-
end
|
1148
|
-
end.delete_if(&:blank?).uniq!
|
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
|
1149
1233
|
|
1150
1234
|
while joins.first.is_a?(Arel::Nodes::Join)
|
1151
1235
|
join_node = joins.shift
|
@@ -1156,13 +1240,8 @@ module ActiveRecord
|
|
1156
1240
|
end
|
1157
1241
|
end
|
1158
1242
|
|
1159
|
-
joins
|
1160
|
-
|
1161
|
-
when Hash, Symbol, Array
|
1162
|
-
buckets[:association_join] << join
|
1163
|
-
when ActiveRecord::Associations::JoinDependency
|
1164
|
-
buckets[:stashed_join] << join
|
1165
|
-
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)
|
1166
1245
|
buckets[:join_node] << join
|
1167
1246
|
else
|
1168
1247
|
raise "unknown class: %s" % join.class.name
|
@@ -1172,32 +1251,36 @@ module ActiveRecord
|
|
1172
1251
|
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1173
1252
|
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1174
1253
|
|
1175
|
-
|
1254
|
+
return buckets, Arel::Nodes::InnerJoin
|
1176
1255
|
end
|
1177
1256
|
|
1178
|
-
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
|
+
|
1179
1262
|
association_joins = buckets[:association_join]
|
1180
1263
|
stashed_joins = buckets[:stashed_join]
|
1181
1264
|
leading_joins = buckets[:leading_join]
|
1182
1265
|
join_nodes = buckets[:join_node]
|
1183
1266
|
|
1184
|
-
join_sources = manager.join_sources
|
1185
1267
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1186
1268
|
|
1187
1269
|
unless association_joins.empty? && stashed_joins.empty?
|
1188
1270
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1189
1271
|
join_dependency = construct_join_dependency(association_joins, join_type)
|
1190
|
-
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))
|
1191
1273
|
end
|
1192
1274
|
|
1193
1275
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1276
|
+
join_sources
|
1194
1277
|
end
|
1195
1278
|
|
1196
1279
|
def build_select(arel)
|
1197
1280
|
if select_values.any?
|
1198
|
-
arel.project(*arel_columns(select_values
|
1281
|
+
arel.project(*arel_columns(select_values))
|
1199
1282
|
elsif klass.ignored_columns.any?
|
1200
|
-
arel.project(*klass.column_names.map { |field|
|
1283
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1201
1284
|
else
|
1202
1285
|
arel.project(table[Arel.star])
|
1203
1286
|
end
|
@@ -1225,7 +1308,12 @@ module ActiveRecord
|
|
1225
1308
|
from = from_clause.name || from_clause.value
|
1226
1309
|
|
1227
1310
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1228
|
-
|
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
|
1229
1317
|
else
|
1230
1318
|
yield field
|
1231
1319
|
end
|
@@ -1239,7 +1327,7 @@ module ActiveRecord
|
|
1239
1327
|
|
1240
1328
|
def reverse_sql_order(order_query)
|
1241
1329
|
if order_query.empty?
|
1242
|
-
return [
|
1330
|
+
return [table[primary_key].desc] if primary_key
|
1243
1331
|
raise IrreversibleOrderError,
|
1244
1332
|
"Relation has no current order and table has no primary key to be used as default order"
|
1245
1333
|
end
|
@@ -1250,6 +1338,8 @@ module ActiveRecord
|
|
1250
1338
|
o.desc
|
1251
1339
|
when Arel::Nodes::Ordering
|
1252
1340
|
o.reverse
|
1341
|
+
when Arel::Nodes::NodeExpression
|
1342
|
+
o.desc
|
1253
1343
|
when String
|
1254
1344
|
if does_not_support_reverse?(o)
|
1255
1345
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
@@ -1276,9 +1366,7 @@ module ActiveRecord
|
|
1276
1366
|
end
|
1277
1367
|
|
1278
1368
|
def build_order(arel)
|
1279
|
-
orders = order_values.
|
1280
|
-
orders.reject!(&:blank?)
|
1281
|
-
|
1369
|
+
orders = order_values.compact_blank
|
1282
1370
|
arel.order(*orders) unless orders.empty?
|
1283
1371
|
end
|
1284
1372
|
|
@@ -1298,12 +1386,6 @@ module ActiveRecord
|
|
1298
1386
|
end
|
1299
1387
|
|
1300
1388
|
def preprocess_order_args(order_args)
|
1301
|
-
order_args.reject!(&:blank?)
|
1302
|
-
order_args.map! do |arg|
|
1303
|
-
klass.sanitize_sql_for_order(arg)
|
1304
|
-
end
|
1305
|
-
order_args.flatten!
|
1306
|
-
|
1307
1389
|
@klass.disallow_raw_sql!(
|
1308
1390
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1309
1391
|
permit: connection.column_name_with_order_matcher
|
@@ -1311,9 +1393,8 @@ module ActiveRecord
|
|
1311
1393
|
|
1312
1394
|
validate_order_args(order_args)
|
1313
1395
|
|
1314
|
-
references = order_args
|
1315
|
-
|
1316
|
-
references!(references) if references.any?
|
1396
|
+
references = column_references(order_args)
|
1397
|
+
self.references_values |= references unless references.empty?
|
1317
1398
|
|
1318
1399
|
# if a symbol is given we prepend the quoted table name
|
1319
1400
|
order_args.map! do |arg|
|
@@ -1324,9 +1405,9 @@ module ActiveRecord
|
|
1324
1405
|
arg.map { |field, dir|
|
1325
1406
|
case field
|
1326
1407
|
when Arel::Nodes::SqlLiteral
|
1327
|
-
field.
|
1408
|
+
field.public_send(dir.downcase)
|
1328
1409
|
else
|
1329
|
-
order_column(field.to_s).
|
1410
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1330
1411
|
end
|
1331
1412
|
}
|
1332
1413
|
else
|
@@ -1335,16 +1416,54 @@ module ActiveRecord
|
|
1335
1416
|
end.flatten!
|
1336
1417
|
end
|
1337
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
|
+
|
1338
1433
|
def order_column(field)
|
1339
1434
|
arel_column(field) do |attr_name|
|
1340
1435
|
if attr_name == "count" && !group_values.empty?
|
1341
|
-
|
1436
|
+
table[attr_name]
|
1342
1437
|
else
|
1343
1438
|
Arel.sql(connection.quote_table_name(attr_name))
|
1344
1439
|
end
|
1345
1440
|
end
|
1346
1441
|
end
|
1347
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
|
+
|
1348
1467
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1349
1468
|
# blank-like object were initially passed into the query method, then this
|
1350
1469
|
# method will not raise an error.
|
@@ -1361,38 +1480,40 @@ module ActiveRecord
|
|
1361
1480
|
# check_if_method_has_arguments!("references", args)
|
1362
1481
|
# ...
|
1363
1482
|
# end
|
1364
|
-
def check_if_method_has_arguments!(method_name, args)
|
1483
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1365
1484
|
if args.blank?
|
1366
|
-
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!
|
1367
1491
|
end
|
1368
1492
|
end
|
1369
1493
|
|
1370
|
-
|
1371
|
-
|
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)
|
1372
1500
|
values = other.values
|
1373
|
-
|
1374
|
-
|
1375
|
-
v1
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
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 || (!v1 || v1.empty?) && (!v2 || v2.empty?)
|
1379
1509
|
end
|
1380
1510
|
end
|
1511
|
+
end
|
1381
1512
|
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
DEFAULT_VALUES = {
|
1388
|
-
create_with: FROZEN_EMPTY_HASH,
|
1389
|
-
where: Relation::WhereClause.empty,
|
1390
|
-
having: Relation::WhereClause.empty,
|
1391
|
-
from: Relation::FromClause.empty
|
1392
|
-
}
|
1393
|
-
|
1394
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1395
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1396
|
-
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
|
1397
1518
|
end
|
1398
1519
|
end
|