activerecord 7.0.0 → 7.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1701 -1039
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +362 -236
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +142 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -29
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +108 -10
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +274 -117
- data/lib/active_record/model_schema.rb +86 -54
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +128 -62
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +145 -146
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +208 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +430 -77
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,17 +3,16 @@
|
|
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_model/forbidden_attributes_protection"
|
7
6
|
require "active_support/core_ext/array/wrap"
|
8
7
|
|
9
8
|
module ActiveRecord
|
10
9
|
module QueryMethods
|
11
10
|
include ActiveModel::ForbiddenAttributesProtection
|
12
11
|
|
13
|
-
# WhereChain objects act as placeholder for queries in which
|
14
|
-
# In this case,
|
12
|
+
# +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
|
13
|
+
# In this case, +where+ can be chained to return a new relation.
|
15
14
|
class WhereChain
|
16
|
-
def initialize(scope)
|
15
|
+
def initialize(scope) # :nodoc:
|
17
16
|
@scope = scope
|
18
17
|
end
|
19
18
|
|
@@ -39,7 +38,14 @@ module ActiveRecord
|
|
39
38
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
40
39
|
#
|
41
40
|
# User.where.not(name: "Jon", role: "admin")
|
42
|
-
# # SELECT * FROM users WHERE NOT (name
|
41
|
+
# # SELECT * FROM users WHERE NOT (name = 'Jon' AND role = 'admin')
|
42
|
+
#
|
43
|
+
# If there is a non-nil condition on a nullable column in the hash condition, the records that have
|
44
|
+
# nil values on the nullable column won't be returned.
|
45
|
+
# User.create!(nullable_country: nil)
|
46
|
+
# User.where.not(nullable_country: "UK")
|
47
|
+
# # SELECT * FROM users WHERE NOT (nullable_country = 'UK')
|
48
|
+
# # => []
|
43
49
|
def not(opts, *rest)
|
44
50
|
where_clause = @scope.send(:build_where_clause, opts, rest)
|
45
51
|
|
@@ -68,9 +74,13 @@ module ActiveRecord
|
|
68
74
|
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
69
75
|
def associated(*associations)
|
70
76
|
associations.each do |association|
|
71
|
-
reflection =
|
77
|
+
reflection = scope_association_reflection(association)
|
72
78
|
@scope.joins!(association)
|
73
|
-
|
79
|
+
if reflection.options[:class_name]
|
80
|
+
self.not(association => { reflection.association_primary_key => nil })
|
81
|
+
else
|
82
|
+
self.not(reflection.table_name => { reflection.association_primary_key => nil })
|
83
|
+
end
|
74
84
|
end
|
75
85
|
|
76
86
|
@scope
|
@@ -96,13 +106,35 @@ module ActiveRecord
|
|
96
106
|
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
97
107
|
def missing(*associations)
|
98
108
|
associations.each do |association|
|
99
|
-
reflection =
|
109
|
+
reflection = scope_association_reflection(association)
|
100
110
|
@scope.left_outer_joins!(association)
|
101
|
-
|
111
|
+
if reflection.options[:class_name]
|
112
|
+
@scope.where!(association => { reflection.association_primary_key => nil })
|
113
|
+
else
|
114
|
+
@scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
|
115
|
+
end
|
102
116
|
end
|
103
117
|
|
104
118
|
@scope
|
105
119
|
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def scope_association_reflection(association)
|
123
|
+
reflection = @scope.klass._reflect_on_association(association)
|
124
|
+
unless reflection
|
125
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
|
126
|
+
end
|
127
|
+
reflection
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
132
|
+
class CTEJoin # :nodoc:
|
133
|
+
attr_reader :name
|
134
|
+
|
135
|
+
def initialize(name)
|
136
|
+
@name = name
|
137
|
+
end
|
106
138
|
end
|
107
139
|
|
108
140
|
FROZEN_EMPTY_ARRAY = [].freeze
|
@@ -133,45 +165,69 @@ module ActiveRecord
|
|
133
165
|
|
134
166
|
alias extensions extending_values
|
135
167
|
|
136
|
-
# Specify
|
137
|
-
#
|
168
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
169
|
+
# A separate query is performed for each association, unless a join is
|
170
|
+
# required by conditions.
|
171
|
+
#
|
172
|
+
# For example:
|
138
173
|
#
|
139
|
-
# users = User.includes(:address)
|
174
|
+
# users = User.includes(:address).limit(5)
|
140
175
|
# users.each do |user|
|
141
176
|
# user.address.city
|
142
177
|
# end
|
143
178
|
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
179
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
180
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
181
|
+
#
|
182
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
183
|
+
# are loaded with a single query.
|
147
184
|
#
|
148
|
-
#
|
185
|
+
# Loading the associations in a separate query will often result in a
|
186
|
+
# performance improvement over a simple join, as a join can result in many
|
187
|
+
# rows that contain redundant data and it performs poorly at scale.
|
149
188
|
#
|
150
|
-
#
|
189
|
+
# You can also specify multiple associations. Each association will result
|
190
|
+
# in an additional query:
|
151
191
|
#
|
152
|
-
#
|
192
|
+
# User.includes(:address, :friends).to_a
|
193
|
+
# # SELECT "users".* FROM "users"
|
194
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
195
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
153
196
|
#
|
154
|
-
#
|
197
|
+
# Loading nested associations is possible using a Hash:
|
155
198
|
#
|
156
|
-
#
|
199
|
+
# User.includes(:address, friends: [:address, :followers])
|
200
|
+
#
|
201
|
+
# === Conditions
|
157
202
|
#
|
158
203
|
# If you want to add string conditions to your included models, you'll have
|
159
204
|
# to explicitly reference them. For example:
|
160
205
|
#
|
161
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
206
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
162
207
|
#
|
163
208
|
# Will throw an error, but this will work:
|
164
209
|
#
|
165
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
210
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
211
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
212
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
213
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
214
|
+
#
|
215
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
216
|
+
# the posts is no longer performed.
|
166
217
|
#
|
167
218
|
# Note that #includes works with association names while #references needs
|
168
219
|
# the actual table name.
|
169
220
|
#
|
170
|
-
# If you pass the conditions via
|
221
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
171
222
|
# explicitly, as #where references the tables for you. For example, this
|
172
223
|
# will work correctly:
|
173
224
|
#
|
174
225
|
# User.includes(:posts).where(posts: { name: 'example' })
|
226
|
+
#
|
227
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
228
|
+
# above code will return only users that have a post named "example",
|
229
|
+
# <em>and will only include posts named "example"</em>, even when a
|
230
|
+
# matching user has other additional posts.
|
175
231
|
def includes(*args)
|
176
232
|
check_if_method_has_arguments!(__callee__, args)
|
177
233
|
spawn.includes!(*args)
|
@@ -182,12 +238,32 @@ module ActiveRecord
|
|
182
238
|
self
|
183
239
|
end
|
184
240
|
|
185
|
-
#
|
241
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
242
|
+
# Performs a single query joining all specified associations. For example:
|
243
|
+
#
|
244
|
+
# users = User.eager_load(:address).limit(5)
|
245
|
+
# users.each do |user|
|
246
|
+
# user.address.city
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
250
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
251
|
+
# # LIMIT 5
|
252
|
+
#
|
253
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
254
|
+
# are loaded with a single joined query.
|
186
255
|
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
256
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
257
|
+
# similar to #includes:
|
258
|
+
#
|
259
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
260
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
261
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
262
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
263
|
+
# # ...
|
264
|
+
#
|
265
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
266
|
+
# contain redundant data and it performs poorly at scale.
|
191
267
|
def eager_load(*args)
|
192
268
|
check_if_method_has_arguments!(__callee__, args)
|
193
269
|
spawn.eager_load!(*args)
|
@@ -198,10 +274,28 @@ module ActiveRecord
|
|
198
274
|
self
|
199
275
|
end
|
200
276
|
|
201
|
-
#
|
277
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
278
|
+
# A separate query is performed for each association.
|
279
|
+
#
|
280
|
+
# users = User.preload(:address).limit(5)
|
281
|
+
# users.each do |user|
|
282
|
+
# user.address.city
|
283
|
+
# end
|
284
|
+
#
|
285
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
286
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
287
|
+
#
|
288
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
289
|
+
# are loaded with a separate query.
|
202
290
|
#
|
203
|
-
#
|
204
|
-
#
|
291
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
292
|
+
# similar to #includes:
|
293
|
+
#
|
294
|
+
# User.preload(:address, friends: [:address, :followers])
|
295
|
+
# # SELECT "users".* FROM "users"
|
296
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
297
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
298
|
+
# # SELECT ...
|
205
299
|
def preload(*args)
|
206
300
|
check_if_method_has_arguments!(__callee__, args)
|
207
301
|
spawn.preload!(*args)
|
@@ -226,7 +320,7 @@ module ActiveRecord
|
|
226
320
|
end
|
227
321
|
|
228
322
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
229
|
-
# and should therefore be
|
323
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
230
324
|
# This method only works in conjunction with #includes.
|
231
325
|
# See #includes for more details.
|
232
326
|
#
|
@@ -270,10 +364,18 @@ module ActiveRecord
|
|
270
364
|
# Model.select(:field, :other_field, :and_one_more)
|
271
365
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
272
366
|
#
|
367
|
+
# The argument also can be a hash of fields and aliases.
|
368
|
+
#
|
369
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
370
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
371
|
+
#
|
372
|
+
# Model.select(models: [:field, :other_field])
|
373
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
374
|
+
#
|
273
375
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
274
376
|
#
|
275
377
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
276
|
-
# # => [#<Model id: nil,
|
378
|
+
# # => [#<Model id: nil, field_one: "value", field_two: "value">]
|
277
379
|
#
|
278
380
|
# If an alias was specified, it will be accessible from the resulting objects:
|
279
381
|
#
|
@@ -284,7 +386,7 @@ module ActiveRecord
|
|
284
386
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
285
387
|
#
|
286
388
|
# Model.select(:field).first.other_field
|
287
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
389
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
288
390
|
def select(*fields)
|
289
391
|
if block_given?
|
290
392
|
if fields.any?
|
@@ -295,6 +397,8 @@ module ActiveRecord
|
|
295
397
|
end
|
296
398
|
|
297
399
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
400
|
+
|
401
|
+
fields = process_select_args(fields)
|
298
402
|
spawn._select!(*fields)
|
299
403
|
end
|
300
404
|
|
@@ -303,6 +407,66 @@ module ActiveRecord
|
|
303
407
|
self
|
304
408
|
end
|
305
409
|
|
410
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
411
|
+
#
|
412
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
413
|
+
# use CTE's with MySQL 5.7.
|
414
|
+
#
|
415
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
416
|
+
# # => ActiveRecord::Relation
|
417
|
+
# # WITH posts_with_tags AS (
|
418
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
419
|
+
# # )
|
420
|
+
# # SELECT * FROM posts
|
421
|
+
#
|
422
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
423
|
+
#
|
424
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
425
|
+
# # => ActiveRecord::Relation
|
426
|
+
# # WITH posts_with_tags AS (
|
427
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
428
|
+
# # )
|
429
|
+
# # SELECT * FROM posts_with_tags AS posts
|
430
|
+
#
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
432
|
+
# # => ActiveRecord::Relation
|
433
|
+
# # WITH posts_with_tags AS (
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
435
|
+
# # )
|
436
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
437
|
+
#
|
438
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
439
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
440
|
+
# using +Arel+.
|
441
|
+
#
|
442
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
443
|
+
#
|
444
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
445
|
+
# be used with unsafe values that include unsanitized input.
|
446
|
+
#
|
447
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
448
|
+
#
|
449
|
+
# Post.with(
|
450
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
451
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
452
|
+
# )
|
453
|
+
#
|
454
|
+
# or chain multiple +.with+ calls
|
455
|
+
#
|
456
|
+
# Post
|
457
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
458
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
459
|
+
def with(*args)
|
460
|
+
check_if_method_has_arguments!(__callee__, args)
|
461
|
+
spawn.with!(*args)
|
462
|
+
end
|
463
|
+
|
464
|
+
# Like #with, but modifies relation in place.
|
465
|
+
def with!(*args) # :nodoc:
|
466
|
+
self.with_values += args
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
306
470
|
# Allows you to change a previously set select statement.
|
307
471
|
#
|
308
472
|
# Post.select(:title, :body)
|
@@ -315,6 +479,7 @@ module ActiveRecord
|
|
315
479
|
# Note that we're unscoping the entire select statement.
|
316
480
|
def reselect(*args)
|
317
481
|
check_if_method_has_arguments!(__callee__, args)
|
482
|
+
args = process_select_args(args)
|
318
483
|
spawn.reselect!(*args)
|
319
484
|
end
|
320
485
|
|
@@ -354,6 +519,27 @@ module ActiveRecord
|
|
354
519
|
self
|
355
520
|
end
|
356
521
|
|
522
|
+
# Allows you to change a previously set group statement.
|
523
|
+
#
|
524
|
+
# Post.group(:title, :body)
|
525
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
526
|
+
#
|
527
|
+
# Post.group(:title, :body).regroup(:title)
|
528
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
529
|
+
#
|
530
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
531
|
+
# Note that we're unscoping the entire group statement.
|
532
|
+
def regroup(*args)
|
533
|
+
check_if_method_has_arguments!(__callee__, args)
|
534
|
+
spawn.regroup!(*args)
|
535
|
+
end
|
536
|
+
|
537
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
538
|
+
def regroup!(*args) # :nodoc:
|
539
|
+
self.group_values = args
|
540
|
+
self
|
541
|
+
end
|
542
|
+
|
357
543
|
# Applies an <code>ORDER BY</code> clause to a query.
|
358
544
|
#
|
359
545
|
# #order accepts arguments in one of several formats.
|
@@ -402,7 +588,7 @@ module ActiveRecord
|
|
402
588
|
# User.order(Arel.sql('end_date - start_date'))
|
403
589
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
404
590
|
#
|
405
|
-
# Custom query syntax, like JSON columns for
|
591
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
406
592
|
#
|
407
593
|
# User.order(Arel.sql("payload->>'kind'"))
|
408
594
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
@@ -420,22 +606,37 @@ module ActiveRecord
|
|
420
606
|
self
|
421
607
|
end
|
422
608
|
|
423
|
-
# Allows to specify an order by a specific set of values.
|
424
|
-
# adapter this will either use a CASE statement or a built-in function.
|
609
|
+
# Allows to specify an order by a specific set of values.
|
425
610
|
#
|
426
611
|
# User.in_order_of(:id, [1, 5, 3])
|
427
|
-
# # SELECT "users".* FROM "users"
|
612
|
+
# # SELECT "users".* FROM "users"
|
613
|
+
# # WHERE "users"."id" IN (1, 5, 3)
|
614
|
+
# # ORDER BY CASE
|
615
|
+
# # WHEN "users"."id" = 1 THEN 1
|
616
|
+
# # WHEN "users"."id" = 5 THEN 2
|
617
|
+
# # WHEN "users"."id" = 3 THEN 3
|
618
|
+
# # END ASC
|
428
619
|
#
|
429
620
|
def in_order_of(column, values)
|
430
621
|
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
|
622
|
+
return spawn.none! if values.empty?
|
431
623
|
|
432
624
|
references = column_references([column])
|
433
625
|
self.references_values |= references unless references.empty?
|
434
626
|
|
435
627
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
436
|
-
|
628
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
629
|
+
|
630
|
+
where_clause =
|
631
|
+
if values.include?(nil)
|
632
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
633
|
+
else
|
634
|
+
arel_column.in(values)
|
635
|
+
end
|
437
636
|
|
438
|
-
spawn
|
637
|
+
spawn
|
638
|
+
.order!(build_case_for_value_position(arel_column, values))
|
639
|
+
.where!(where_clause)
|
439
640
|
end
|
440
641
|
|
441
642
|
# Replaces any existing order defined on the relation with the specified order.
|
@@ -446,7 +647,7 @@ module ActiveRecord
|
|
446
647
|
#
|
447
648
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
448
649
|
#
|
449
|
-
# generates a query with
|
650
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
450
651
|
def reorder(*args)
|
451
652
|
check_if_method_has_arguments!(__callee__, args) do
|
452
653
|
sanitize_order_arguments(args)
|
@@ -465,7 +666,8 @@ module ActiveRecord
|
|
465
666
|
|
466
667
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
467
668
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
468
|
-
:includes, :
|
669
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
670
|
+
:having, :optimizer_hints])
|
469
671
|
|
470
672
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
471
673
|
# This is useful when passing around chains of relations and would like to
|
@@ -575,7 +777,7 @@ module ActiveRecord
|
|
575
777
|
# Performs LEFT OUTER JOINs on +args+:
|
576
778
|
#
|
577
779
|
# User.left_outer_joins(:posts)
|
578
|
-
#
|
780
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
579
781
|
#
|
580
782
|
def left_outer_joins(*args)
|
581
783
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -595,7 +797,7 @@ module ActiveRecord
|
|
595
797
|
# SQL is given as an illustration; the actual query generated may be different depending
|
596
798
|
# on the database adapter.
|
597
799
|
#
|
598
|
-
# ===
|
800
|
+
# === \String
|
599
801
|
#
|
600
802
|
# A single string, without additional arguments, is passed to the query
|
601
803
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -607,7 +809,7 @@ module ActiveRecord
|
|
607
809
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
608
810
|
# to use one of the following methods.
|
609
811
|
#
|
610
|
-
# ===
|
812
|
+
# === \Array
|
611
813
|
#
|
612
814
|
# If an array is passed, then the first element of the array is treated as a template, and
|
613
815
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -647,7 +849,7 @@ module ActiveRecord
|
|
647
849
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
648
850
|
# test with multiple database backends.
|
649
851
|
#
|
650
|
-
# ===
|
852
|
+
# === \Hash
|
651
853
|
#
|
652
854
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
653
855
|
# are values to be searched for.
|
@@ -681,6 +883,12 @@ module ActiveRecord
|
|
681
883
|
# PriceEstimate.where(estimate_of: treasure)
|
682
884
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
683
885
|
#
|
886
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
887
|
+
# an array of columns with an array of tuples as values.
|
888
|
+
#
|
889
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
890
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
891
|
+
#
|
684
892
|
# === Joins
|
685
893
|
#
|
686
894
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -693,17 +901,31 @@ module ActiveRecord
|
|
693
901
|
# User.joins(:posts).where("posts.published" => true)
|
694
902
|
# User.joins(:posts).where(posts: { published: true })
|
695
903
|
#
|
696
|
-
# ===
|
904
|
+
# === No Argument
|
697
905
|
#
|
698
906
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
699
|
-
# can be chained with #not
|
907
|
+
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
908
|
+
#
|
909
|
+
# Chaining with WhereChain#not:
|
700
910
|
#
|
701
911
|
# User.where.not(name: "Jon")
|
702
912
|
# # SELECT * FROM users WHERE name != 'Jon'
|
703
913
|
#
|
704
|
-
#
|
914
|
+
# Chaining with WhereChain#associated:
|
705
915
|
#
|
706
|
-
#
|
916
|
+
# Post.where.associated(:author)
|
917
|
+
# # SELECT "posts".* FROM "posts"
|
918
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
919
|
+
# # WHERE "authors"."id" IS NOT NULL
|
920
|
+
#
|
921
|
+
# Chaining with WhereChain#missing:
|
922
|
+
#
|
923
|
+
# Post.where.missing(:author)
|
924
|
+
# # SELECT "posts".* FROM "posts"
|
925
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
926
|
+
# # WHERE "authors"."id" IS NULL
|
927
|
+
#
|
928
|
+
# === Blank Condition
|
707
929
|
#
|
708
930
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
709
931
|
# the current relation.
|
@@ -736,6 +958,8 @@ module ActiveRecord
|
|
736
958
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
737
959
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
738
960
|
def rewhere(conditions)
|
961
|
+
return unscope(:where) if conditions.nil?
|
962
|
+
|
739
963
|
scope = spawn
|
740
964
|
where_clause = scope.build_where_clause(conditions)
|
741
965
|
|
@@ -841,7 +1065,11 @@ module ActiveRecord
|
|
841
1065
|
#
|
842
1066
|
def or(other)
|
843
1067
|
if other.is_a?(Relation)
|
844
|
-
|
1068
|
+
if @none
|
1069
|
+
other.spawn
|
1070
|
+
else
|
1071
|
+
spawn.or!(other)
|
1072
|
+
end
|
845
1073
|
else
|
846
1074
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
847
1075
|
end
|
@@ -954,15 +1182,29 @@ module ActiveRecord
|
|
954
1182
|
end
|
955
1183
|
|
956
1184
|
def none! # :nodoc:
|
957
|
-
|
1185
|
+
unless @none
|
1186
|
+
where!("1=0")
|
1187
|
+
@none = true
|
1188
|
+
end
|
1189
|
+
self
|
958
1190
|
end
|
959
1191
|
|
960
|
-
|
961
|
-
|
1192
|
+
def null_relation? # :nodoc:
|
1193
|
+
@none
|
1194
|
+
end
|
1195
|
+
|
1196
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
1197
|
+
# an error.
|
962
1198
|
#
|
963
1199
|
# users = User.readonly
|
964
1200
|
# users.first.save
|
965
1201
|
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1202
|
+
#
|
1203
|
+
# To make a readonly relation writable, pass +false+.
|
1204
|
+
#
|
1205
|
+
# users.readonly(false)
|
1206
|
+
# users.first.save
|
1207
|
+
# => true
|
966
1208
|
def readonly(value = true)
|
967
1209
|
spawn.readonly!(value)
|
968
1210
|
end
|
@@ -1079,7 +1321,7 @@ module ActiveRecord
|
|
1079
1321
|
#
|
1080
1322
|
# The object returned is a relation, which can be further extended.
|
1081
1323
|
#
|
1082
|
-
# === Using a
|
1324
|
+
# === Using a \Module
|
1083
1325
|
#
|
1084
1326
|
# module Pagination
|
1085
1327
|
# def page(number)
|
@@ -1094,7 +1336,7 @@ module ActiveRecord
|
|
1094
1336
|
#
|
1095
1337
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
1096
1338
|
#
|
1097
|
-
# === Using a
|
1339
|
+
# === Using a Block
|
1098
1340
|
#
|
1099
1341
|
# scope = Model.all.extending do
|
1100
1342
|
# def page(number)
|
@@ -1181,6 +1423,8 @@ module ActiveRecord
|
|
1181
1423
|
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1182
1424
|
#
|
1183
1425
|
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1426
|
+
#
|
1427
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
1184
1428
|
def annotate(*args)
|
1185
1429
|
check_if_method_has_arguments!(__callee__, args)
|
1186
1430
|
spawn.annotate!(*args)
|
@@ -1269,8 +1513,12 @@ module ActiveRecord
|
|
1269
1513
|
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1270
1514
|
when Hash
|
1271
1515
|
opts = opts.transform_keys do |key|
|
1272
|
-
|
1273
|
-
|
1516
|
+
if key.is_a?(Array)
|
1517
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
1518
|
+
else
|
1519
|
+
key = key.to_s
|
1520
|
+
klass.attribute_aliases[key] || key
|
1521
|
+
end
|
1274
1522
|
end
|
1275
1523
|
references = PredicateBuilder.references(opts)
|
1276
1524
|
self.references_values |= references unless references.empty?
|
@@ -1288,7 +1536,16 @@ module ActiveRecord
|
|
1288
1536
|
end
|
1289
1537
|
alias :build_having_clause :build_where_clause
|
1290
1538
|
|
1539
|
+
def async!
|
1540
|
+
@async = true
|
1541
|
+
self
|
1542
|
+
end
|
1543
|
+
|
1291
1544
|
private
|
1545
|
+
def async
|
1546
|
+
spawn.async!
|
1547
|
+
end
|
1548
|
+
|
1292
1549
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1293
1550
|
each_join_dependencies do |join|
|
1294
1551
|
return join.base_klass if table_name == join.table_name
|
@@ -1303,13 +1560,13 @@ module ActiveRecord
|
|
1303
1560
|
end
|
1304
1561
|
|
1305
1562
|
def build_join_dependencies
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1563
|
+
joins = joins_values | left_outer_joins_values
|
1564
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
1565
|
+
joins |= includes_values unless includes_values.empty?
|
1309
1566
|
|
1310
1567
|
join_dependencies = []
|
1311
1568
|
join_dependencies.unshift construct_join_dependency(
|
1312
|
-
|
1569
|
+
select_named_joins(joins, join_dependencies), nil
|
1313
1570
|
)
|
1314
1571
|
end
|
1315
1572
|
|
@@ -1330,6 +1587,7 @@ module ActiveRecord
|
|
1330
1587
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1331
1588
|
|
1332
1589
|
build_order(arel)
|
1590
|
+
build_with(arel)
|
1333
1591
|
build_select(arel)
|
1334
1592
|
|
1335
1593
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1365,6 +1623,18 @@ module ActiveRecord
|
|
1365
1623
|
end
|
1366
1624
|
end
|
1367
1625
|
|
1626
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
1627
|
+
cte_joins, associations = join_names.partition do |join_name|
|
1628
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
1629
|
+
end
|
1630
|
+
|
1631
|
+
cte_joins.each do |cte_name|
|
1632
|
+
block&.call(CTEJoin.new(cte_name))
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
select_association_list(associations, stashed_joins, &block)
|
1636
|
+
end
|
1637
|
+
|
1368
1638
|
def select_association_list(associations, stashed_joins = nil)
|
1369
1639
|
result = []
|
1370
1640
|
associations.each do |association|
|
@@ -1380,20 +1650,21 @@ module ActiveRecord
|
|
1380
1650
|
result
|
1381
1651
|
end
|
1382
1652
|
|
1383
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1384
|
-
end
|
1385
|
-
|
1386
1653
|
def build_join_buckets
|
1387
1654
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1388
1655
|
|
1389
1656
|
unless left_outer_joins_values.empty?
|
1390
1657
|
stashed_left_joins = []
|
1391
|
-
left_joins =
|
1392
|
-
|
1658
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
1659
|
+
if left_join.is_a?(CTEJoin)
|
1660
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
1661
|
+
else
|
1662
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1663
|
+
end
|
1393
1664
|
end
|
1394
1665
|
|
1395
1666
|
if joins_values.empty?
|
1396
|
-
buckets[:
|
1667
|
+
buckets[:named_join] = left_joins
|
1397
1668
|
buckets[:stashed_join] = stashed_left_joins
|
1398
1669
|
return buckets, Arel::Nodes::OuterJoin
|
1399
1670
|
else
|
@@ -1419,9 +1690,11 @@ module ActiveRecord
|
|
1419
1690
|
end
|
1420
1691
|
end
|
1421
1692
|
|
1422
|
-
buckets[:
|
1693
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1423
1694
|
if join.is_a?(Arel::Nodes::Join)
|
1424
1695
|
buckets[:join_node] << join
|
1696
|
+
elsif join.is_a?(CTEJoin)
|
1697
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1425
1698
|
else
|
1426
1699
|
raise "unknown class: %s" % join.class.name
|
1427
1700
|
end
|
@@ -1438,16 +1711,16 @@ module ActiveRecord
|
|
1438
1711
|
|
1439
1712
|
buckets, join_type = build_join_buckets
|
1440
1713
|
|
1441
|
-
|
1442
|
-
stashed_joins
|
1443
|
-
leading_joins
|
1444
|
-
join_nodes
|
1714
|
+
named_joins = buckets[:named_join]
|
1715
|
+
stashed_joins = buckets[:stashed_join]
|
1716
|
+
leading_joins = buckets[:leading_join]
|
1717
|
+
join_nodes = buckets[:join_node]
|
1445
1718
|
|
1446
1719
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1447
1720
|
|
1448
|
-
unless
|
1721
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1449
1722
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1450
|
-
join_dependency = construct_join_dependency(
|
1723
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1451
1724
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1452
1725
|
end
|
1453
1726
|
|
@@ -1465,6 +1738,40 @@ module ActiveRecord
|
|
1465
1738
|
end
|
1466
1739
|
end
|
1467
1740
|
|
1741
|
+
def build_with(arel)
|
1742
|
+
return if with_values.empty?
|
1743
|
+
|
1744
|
+
with_statements = with_values.map do |with_value|
|
1745
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
1746
|
+
|
1747
|
+
build_with_value_from_hash(with_value)
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
arel.with(with_statements)
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
def build_with_value_from_hash(hash)
|
1754
|
+
hash.map do |name, value|
|
1755
|
+
expression =
|
1756
|
+
case value
|
1757
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1758
|
+
when ActiveRecord::Relation then value.arel
|
1759
|
+
when Arel::SelectManager then value
|
1760
|
+
else
|
1761
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1762
|
+
end
|
1763
|
+
Arel::Nodes::TableAlias.new(expression, name)
|
1764
|
+
end
|
1765
|
+
end
|
1766
|
+
|
1767
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
1768
|
+
with_table = Arel::Table.new(name)
|
1769
|
+
|
1770
|
+
table.join(with_table, kind).on(
|
1771
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
1772
|
+
).join_sources.first
|
1773
|
+
end
|
1774
|
+
|
1468
1775
|
def arel_columns(columns)
|
1469
1776
|
columns.flat_map do |field|
|
1470
1777
|
case field
|
@@ -1583,7 +1890,7 @@ module ActiveRecord
|
|
1583
1890
|
when Hash
|
1584
1891
|
arg.map { |field, dir|
|
1585
1892
|
case field
|
1586
|
-
when Arel::Nodes::SqlLiteral
|
1893
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
1587
1894
|
field.public_send(dir.downcase)
|
1588
1895
|
else
|
1589
1896
|
order_column(field.to_s).public_send(dir.downcase)
|
@@ -1607,7 +1914,9 @@ module ActiveRecord
|
|
1607
1914
|
when String, Symbol
|
1608
1915
|
arg
|
1609
1916
|
when Hash
|
1610
|
-
arg.keys
|
1917
|
+
arg.keys.map do |key|
|
1918
|
+
key if key.is_a?(String) || key.is_a?(Symbol)
|
1919
|
+
end
|
1611
1920
|
end
|
1612
1921
|
end
|
1613
1922
|
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
@@ -1624,6 +1933,15 @@ module ActiveRecord
|
|
1624
1933
|
end
|
1625
1934
|
end
|
1626
1935
|
|
1936
|
+
def build_case_for_value_position(column, values)
|
1937
|
+
node = Arel::Nodes::Case.new
|
1938
|
+
values.each.with_index(1) do |value, order|
|
1939
|
+
node.when(column.eq(value)).then(order)
|
1940
|
+
end
|
1941
|
+
|
1942
|
+
Arel::Nodes::Ascending.new(node)
|
1943
|
+
end
|
1944
|
+
|
1627
1945
|
def resolve_arel_attributes(attrs)
|
1628
1946
|
attrs.flat_map do |attr|
|
1629
1947
|
case attr
|
@@ -1675,6 +1993,41 @@ module ActiveRecord
|
|
1675
1993
|
end
|
1676
1994
|
end
|
1677
1995
|
|
1996
|
+
def process_select_args(fields)
|
1997
|
+
fields.flat_map do |field|
|
1998
|
+
if field.is_a?(Hash)
|
1999
|
+
transform_select_hash_values(field)
|
2000
|
+
else
|
2001
|
+
field
|
2002
|
+
end
|
2003
|
+
end
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
def transform_select_hash_values(fields)
|
2007
|
+
fields.flat_map do |key, columns_aliases|
|
2008
|
+
case columns_aliases
|
2009
|
+
when Hash
|
2010
|
+
columns_aliases.map do |column, column_alias|
|
2011
|
+
if values[:joins]&.include?(key)
|
2012
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2013
|
+
self.references_values |= references unless references.empty?
|
2014
|
+
end
|
2015
|
+
arel_column("#{key}.#{column}") do
|
2016
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2017
|
+
end.as(column_alias.to_s)
|
2018
|
+
end
|
2019
|
+
when Array
|
2020
|
+
columns_aliases.map do |column|
|
2021
|
+
arel_column("#{key}.#{column}", &:itself)
|
2022
|
+
end
|
2023
|
+
when String, Symbol
|
2024
|
+
arel_column(key.to_s) do
|
2025
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2026
|
+
end.as(columns_aliases.to_s)
|
2027
|
+
end
|
2028
|
+
end
|
2029
|
+
end
|
2030
|
+
|
1678
2031
|
STRUCTURAL_VALUE_METHODS = (
|
1679
2032
|
Relation::VALUE_METHODS -
|
1680
2033
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|