activerecord 6.0.4.7 → 6.1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1199 -777
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/lib/active_record/aggregations.rb +5 -5
- data/lib/active_record/association_relation.rb +30 -12
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +49 -26
- data/lib/active_record/associations/association_scope.rb +18 -20
- data/lib/active_record/associations/belongs_to_association.rb +23 -10
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -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 +32 -18
- 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 +37 -21
- 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 +14 -8
- 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 +118 -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 +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/attribute_methods.rb +64 -54
- data/lib/active_record/attributes.rb +33 -8
- data/lib/active_record/autosave_association.rb +47 -30
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +24 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -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 +44 -35
- 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 +114 -26
- 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 +92 -33
- data/lib/active_record/connection_adapters/abstract_adapter.rb +52 -76
- 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 +35 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +24 -24
- 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 +7 -4
- 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 +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +14 -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 -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/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 +30 -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 +75 -64
- data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +32 -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 +52 -0
- data/lib/active_record/connection_handling.rb +218 -71
- data/lib/active_record/core.rb +271 -60
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -40
- data/lib/active_record/database_configurations.rb +125 -85
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +69 -34
- 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 +3 -3
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +38 -5
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +18 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +24 -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/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 +72 -18
- data/lib/active_record/migration.rb +114 -84
- data/lib/active_record/model_schema.rb +89 -14
- 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 +279 -101
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +60 -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 +104 -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 +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +61 -38
- data/lib/active_record/relation/query_methods.rb +324 -196
- 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 +111 -61
- data/lib/active_record/relation.rb +100 -81
- 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/default.rb +1 -3
- 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 +8 -3
- 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 +79 -31
- 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/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 +76 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/predications.rb +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 +26 -26
- 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,6 +1035,8 @@ 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.
|
1038
|
+
#
|
1039
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
1003
1040
|
def annotate(*args)
|
1004
1041
|
check_if_method_has_arguments!(:annotate, args)
|
1005
1042
|
spawn.annotate!(*args)
|
@@ -1011,6 +1048,14 @@ module ActiveRecord
|
|
1011
1048
|
self
|
1012
1049
|
end
|
1013
1050
|
|
1051
|
+
# Deduplicate multiple values.
|
1052
|
+
def uniq!(name)
|
1053
|
+
if values = @values[name]
|
1054
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1055
|
+
end
|
1056
|
+
self
|
1057
|
+
end
|
1058
|
+
|
1014
1059
|
# Returns the Arel object associated with the relation.
|
1015
1060
|
def arel(aliases = nil) # :nodoc:
|
1016
1061
|
@arel ||= build_arel(aliases)
|
@@ -1031,54 +1076,106 @@ module ActiveRecord
|
|
1031
1076
|
end
|
1032
1077
|
end
|
1033
1078
|
|
1079
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1080
|
+
opts = sanitize_forbidden_attributes(opts)
|
1081
|
+
|
1082
|
+
case opts
|
1083
|
+
when String, Array
|
1084
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1085
|
+
when Hash
|
1086
|
+
opts = opts.transform_keys do |key|
|
1087
|
+
key = key.to_s
|
1088
|
+
klass.attribute_aliases[key] || key
|
1089
|
+
end
|
1090
|
+
references = PredicateBuilder.references(opts)
|
1091
|
+
self.references_values |= references unless references.empty?
|
1092
|
+
|
1093
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1094
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1095
|
+
end
|
1096
|
+
when Arel::Nodes::Node
|
1097
|
+
parts = [opts]
|
1098
|
+
else
|
1099
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
Relation::WhereClause.new(parts)
|
1103
|
+
end
|
1104
|
+
alias :build_having_clause :build_where_clause
|
1105
|
+
|
1034
1106
|
private
|
1107
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1108
|
+
each_join_dependencies do |join|
|
1109
|
+
return join.base_klass if table_name == join.table_name
|
1110
|
+
end
|
1111
|
+
nil
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1115
|
+
join_dependencies.each do |join_dependency|
|
1116
|
+
join_dependency.each do |join|
|
1117
|
+
yield join
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
def build_join_dependencies
|
1123
|
+
associations = joins_values | left_outer_joins_values
|
1124
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1125
|
+
associations |= includes_values unless includes_values.empty?
|
1126
|
+
|
1127
|
+
join_dependencies = []
|
1128
|
+
join_dependencies.unshift construct_join_dependency(
|
1129
|
+
select_association_list(associations, join_dependencies), nil
|
1130
|
+
)
|
1131
|
+
end
|
1132
|
+
|
1035
1133
|
def assert_mutability!
|
1036
1134
|
raise ImmutableRelation if @loaded
|
1037
1135
|
raise ImmutableRelation if defined?(@arel) && @arel
|
1038
1136
|
end
|
1039
1137
|
|
1040
|
-
def build_arel(aliases)
|
1138
|
+
def build_arel(aliases = nil)
|
1041
1139
|
arel = Arel::SelectManager.new(table)
|
1042
1140
|
|
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
|
1141
|
+
build_joins(arel.join_sources, aliases)
|
1048
1142
|
|
1049
1143
|
arel.where(where_clause.ast) unless where_clause.empty?
|
1050
1144
|
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?
|
1145
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1146
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1147
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1068
1148
|
|
1069
1149
|
build_order(arel)
|
1070
|
-
|
1071
1150
|
build_select(arel)
|
1072
1151
|
|
1073
1152
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1074
1153
|
arel.distinct(distinct_value)
|
1075
1154
|
arel.from(build_from) unless from_clause.empty?
|
1076
1155
|
arel.lock(lock_value) if lock_value
|
1077
|
-
|
1156
|
+
|
1157
|
+
unless annotate_values.empty?
|
1158
|
+
annotates = annotate_values
|
1159
|
+
annotates = annotates.uniq if annotates.size > 1
|
1160
|
+
unless annotates == annotate_values
|
1161
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1162
|
+
Duplicated query annotations are no longer shown in queries in Rails 7.0.
|
1163
|
+
To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1164
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1165
|
+
MSG
|
1166
|
+
annotates = annotate_values
|
1167
|
+
end
|
1168
|
+
arel.comment(*annotates)
|
1169
|
+
end
|
1078
1170
|
|
1079
1171
|
arel
|
1080
1172
|
end
|
1081
1173
|
|
1174
|
+
def build_cast_value(name, value)
|
1175
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1176
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1177
|
+
end
|
1178
|
+
|
1082
1179
|
def build_from
|
1083
1180
|
opts = from_clause.value
|
1084
1181
|
name = from_clause.name
|
@@ -1103,47 +1200,41 @@ module ActiveRecord
|
|
1103
1200
|
when ActiveRecord::Associations::JoinDependency
|
1104
1201
|
stashed_joins&.<< association
|
1105
1202
|
else
|
1106
|
-
yield if block_given?
|
1203
|
+
yield association if block_given?
|
1107
1204
|
end
|
1108
1205
|
end
|
1109
1206
|
result
|
1110
1207
|
end
|
1111
1208
|
|
1112
|
-
def valid_association_list(associations, stashed_joins)
|
1113
|
-
select_association_list(associations, stashed_joins) do
|
1114
|
-
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1115
|
-
end
|
1116
|
-
end
|
1117
|
-
|
1118
|
-
def build_left_outer_joins(manager, outer_joins, aliases)
|
1119
|
-
buckets = Hash.new { |h, k| h[k] = [] }
|
1120
|
-
buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
|
1121
|
-
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
1122
|
-
end
|
1123
|
-
|
1124
1209
|
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1125
1210
|
end
|
1126
1211
|
|
1127
|
-
def
|
1212
|
+
def build_join_buckets
|
1128
1213
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1129
1214
|
|
1130
1215
|
unless left_outer_joins_values.empty?
|
1131
1216
|
stashed_left_joins = []
|
1132
|
-
left_joins =
|
1133
|
-
|
1217
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1218
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
if joins_values.empty?
|
1222
|
+
buckets[:association_join] = left_joins
|
1223
|
+
buckets[:stashed_join] = stashed_left_joins
|
1224
|
+
return buckets, Arel::Nodes::OuterJoin
|
1225
|
+
else
|
1226
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1227
|
+
end
|
1134
1228
|
end
|
1135
1229
|
|
1230
|
+
joins = joins_values.dup
|
1136
1231
|
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1137
1232
|
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1138
1233
|
end
|
1139
1234
|
|
1140
|
-
joins.
|
1141
|
-
if join.is_a?(String)
|
1142
|
-
|
1143
|
-
else
|
1144
|
-
join
|
1145
|
-
end
|
1146
|
-
end.delete_if(&:blank?).uniq!
|
1235
|
+
joins.each_with_index do |join, i|
|
1236
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1237
|
+
end
|
1147
1238
|
|
1148
1239
|
while joins.first.is_a?(Arel::Nodes::Join)
|
1149
1240
|
join_node = joins.shift
|
@@ -1154,13 +1245,8 @@ module ActiveRecord
|
|
1154
1245
|
end
|
1155
1246
|
end
|
1156
1247
|
|
1157
|
-
joins
|
1158
|
-
|
1159
|
-
when Hash, Symbol, Array
|
1160
|
-
buckets[:association_join] << join
|
1161
|
-
when ActiveRecord::Associations::JoinDependency
|
1162
|
-
buckets[:stashed_join] << join
|
1163
|
-
when Arel::Nodes::Join
|
1248
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1249
|
+
if join.is_a?(Arel::Nodes::Join)
|
1164
1250
|
buckets[:join_node] << join
|
1165
1251
|
else
|
1166
1252
|
raise "unknown class: %s" % join.class.name
|
@@ -1170,32 +1256,36 @@ module ActiveRecord
|
|
1170
1256
|
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1171
1257
|
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1172
1258
|
|
1173
|
-
|
1259
|
+
return buckets, Arel::Nodes::InnerJoin
|
1174
1260
|
end
|
1175
1261
|
|
1176
|
-
def
|
1262
|
+
def build_joins(join_sources, aliases = nil)
|
1263
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1264
|
+
|
1265
|
+
buckets, join_type = build_join_buckets
|
1266
|
+
|
1177
1267
|
association_joins = buckets[:association_join]
|
1178
1268
|
stashed_joins = buckets[:stashed_join]
|
1179
1269
|
leading_joins = buckets[:leading_join]
|
1180
1270
|
join_nodes = buckets[:join_node]
|
1181
1271
|
|
1182
|
-
join_sources = manager.join_sources
|
1183
1272
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1184
1273
|
|
1185
1274
|
unless association_joins.empty? && stashed_joins.empty?
|
1186
1275
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1187
1276
|
join_dependency = construct_join_dependency(association_joins, join_type)
|
1188
|
-
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
1277
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1189
1278
|
end
|
1190
1279
|
|
1191
1280
|
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1281
|
+
join_sources
|
1192
1282
|
end
|
1193
1283
|
|
1194
1284
|
def build_select(arel)
|
1195
1285
|
if select_values.any?
|
1196
|
-
arel.project(*arel_columns(select_values
|
1286
|
+
arel.project(*arel_columns(select_values))
|
1197
1287
|
elsif klass.ignored_columns.any?
|
1198
|
-
arel.project(*klass.column_names.map { |field|
|
1288
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1199
1289
|
else
|
1200
1290
|
arel.project(table[Arel.star])
|
1201
1291
|
end
|
@@ -1223,7 +1313,12 @@ module ActiveRecord
|
|
1223
1313
|
from = from_clause.name || from_clause.value
|
1224
1314
|
|
1225
1315
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1226
|
-
|
1316
|
+
table[field]
|
1317
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1318
|
+
table, column = field.split(".")
|
1319
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1320
|
+
lookup_table_klass_from_join_dependencies(table)
|
1321
|
+
end
|
1227
1322
|
else
|
1228
1323
|
yield field
|
1229
1324
|
end
|
@@ -1237,7 +1332,7 @@ module ActiveRecord
|
|
1237
1332
|
|
1238
1333
|
def reverse_sql_order(order_query)
|
1239
1334
|
if order_query.empty?
|
1240
|
-
return [
|
1335
|
+
return [table[primary_key].desc] if primary_key
|
1241
1336
|
raise IrreversibleOrderError,
|
1242
1337
|
"Relation has no current order and table has no primary key to be used as default order"
|
1243
1338
|
end
|
@@ -1248,6 +1343,8 @@ module ActiveRecord
|
|
1248
1343
|
o.desc
|
1249
1344
|
when Arel::Nodes::Ordering
|
1250
1345
|
o.reverse
|
1346
|
+
when Arel::Nodes::NodeExpression
|
1347
|
+
o.desc
|
1251
1348
|
when String
|
1252
1349
|
if does_not_support_reverse?(o)
|
1253
1350
|
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
@@ -1274,9 +1371,7 @@ module ActiveRecord
|
|
1274
1371
|
end
|
1275
1372
|
|
1276
1373
|
def build_order(arel)
|
1277
|
-
orders = order_values.
|
1278
|
-
orders.reject!(&:blank?)
|
1279
|
-
|
1374
|
+
orders = order_values.compact_blank
|
1280
1375
|
arel.order(*orders) unless orders.empty?
|
1281
1376
|
end
|
1282
1377
|
|
@@ -1296,12 +1391,6 @@ module ActiveRecord
|
|
1296
1391
|
end
|
1297
1392
|
|
1298
1393
|
def preprocess_order_args(order_args)
|
1299
|
-
order_args.reject!(&:blank?)
|
1300
|
-
order_args.map! do |arg|
|
1301
|
-
klass.sanitize_sql_for_order(arg)
|
1302
|
-
end
|
1303
|
-
order_args.flatten!
|
1304
|
-
|
1305
1394
|
@klass.disallow_raw_sql!(
|
1306
1395
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1307
1396
|
permit: connection.column_name_with_order_matcher
|
@@ -1309,9 +1398,8 @@ module ActiveRecord
|
|
1309
1398
|
|
1310
1399
|
validate_order_args(order_args)
|
1311
1400
|
|
1312
|
-
references = order_args
|
1313
|
-
|
1314
|
-
references!(references) if references.any?
|
1401
|
+
references = column_references(order_args)
|
1402
|
+
self.references_values |= references unless references.empty?
|
1315
1403
|
|
1316
1404
|
# if a symbol is given we prepend the quoted table name
|
1317
1405
|
order_args.map! do |arg|
|
@@ -1322,9 +1410,9 @@ module ActiveRecord
|
|
1322
1410
|
arg.map { |field, dir|
|
1323
1411
|
case field
|
1324
1412
|
when Arel::Nodes::SqlLiteral
|
1325
|
-
field.
|
1413
|
+
field.public_send(dir.downcase)
|
1326
1414
|
else
|
1327
|
-
order_column(field.to_s).
|
1415
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1328
1416
|
end
|
1329
1417
|
}
|
1330
1418
|
else
|
@@ -1333,16 +1421,54 @@ module ActiveRecord
|
|
1333
1421
|
end.flatten!
|
1334
1422
|
end
|
1335
1423
|
|
1424
|
+
def sanitize_order_arguments(order_args)
|
1425
|
+
order_args.map! do |arg|
|
1426
|
+
klass.sanitize_sql_for_order(arg)
|
1427
|
+
end
|
1428
|
+
order_args.flatten!
|
1429
|
+
order_args.compact_blank!
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
def column_references(order_args)
|
1433
|
+
references = order_args.grep(String)
|
1434
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1435
|
+
references
|
1436
|
+
end
|
1437
|
+
|
1336
1438
|
def order_column(field)
|
1337
1439
|
arel_column(field) do |attr_name|
|
1338
1440
|
if attr_name == "count" && !group_values.empty?
|
1339
|
-
|
1441
|
+
table[attr_name]
|
1340
1442
|
else
|
1341
1443
|
Arel.sql(connection.quote_table_name(attr_name))
|
1342
1444
|
end
|
1343
1445
|
end
|
1344
1446
|
end
|
1345
1447
|
|
1448
|
+
def resolve_arel_attributes(attrs)
|
1449
|
+
attrs.flat_map do |attr|
|
1450
|
+
case attr
|
1451
|
+
when Arel::Predications
|
1452
|
+
attr
|
1453
|
+
when Hash
|
1454
|
+
attr.flat_map do |table, columns|
|
1455
|
+
table = table.to_s
|
1456
|
+
Array(columns).map do |column|
|
1457
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
else
|
1461
|
+
attr = attr.to_s
|
1462
|
+
if attr.include?(".")
|
1463
|
+
table, column = attr.split(".", 2)
|
1464
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1465
|
+
else
|
1466
|
+
attr
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
end
|
1470
|
+
end
|
1471
|
+
|
1346
1472
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1347
1473
|
# blank-like object were initially passed into the query method, then this
|
1348
1474
|
# method will not raise an error.
|
@@ -1359,38 +1485,40 @@ module ActiveRecord
|
|
1359
1485
|
# check_if_method_has_arguments!("references", args)
|
1360
1486
|
# ...
|
1361
1487
|
# end
|
1362
|
-
def check_if_method_has_arguments!(method_name, args)
|
1488
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1363
1489
|
if args.blank?
|
1364
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1490
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1491
|
+
elsif block_given?
|
1492
|
+
yield args
|
1493
|
+
else
|
1494
|
+
args.flatten!
|
1495
|
+
args.compact_blank!
|
1365
1496
|
end
|
1366
1497
|
end
|
1367
1498
|
|
1368
|
-
|
1369
|
-
|
1499
|
+
STRUCTURAL_VALUE_METHODS = (
|
1500
|
+
Relation::VALUE_METHODS -
|
1501
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1502
|
+
).freeze # :nodoc:
|
1503
|
+
|
1504
|
+
def structurally_incompatible_values_for(other)
|
1370
1505
|
values = other.values
|
1371
|
-
|
1372
|
-
|
1373
|
-
v1
|
1374
|
-
|
1375
|
-
|
1506
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1507
|
+
v1, v2 = @values[method], values[method]
|
1508
|
+
if v1.is_a?(Array)
|
1509
|
+
next true unless v2.is_a?(Array)
|
1510
|
+
v1 = v1.uniq
|
1511
|
+
v2 = v2.uniq
|
1512
|
+
end
|
1376
1513
|
v1 == v2
|
1377
1514
|
end
|
1378
1515
|
end
|
1516
|
+
end
|
1379
1517
|
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
DEFAULT_VALUES = {
|
1386
|
-
create_with: FROZEN_EMPTY_HASH,
|
1387
|
-
where: Relation::WhereClause.empty,
|
1388
|
-
having: Relation::WhereClause.empty,
|
1389
|
-
from: Relation::FromClause.empty
|
1390
|
-
}
|
1391
|
-
|
1392
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1393
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1394
|
-
end
|
1518
|
+
class Relation # :nodoc:
|
1519
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1520
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1521
|
+
class WhereClauseFactory # :nodoc:
|
1522
|
+
end
|
1395
1523
|
end
|
1396
1524
|
end
|