activerecord 7.0.0 → 7.1.2
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 +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]
|