activerecord 7.0.8.1 → 7.2.2.1
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 +642 -1925
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -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 +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +59 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -3,14 +3,13 @@
|
|
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 +where+ does not have any parameter.
|
12
|
+
# +WhereChain+ objects act as placeholder for queries in which +where+ does not have any parameter.
|
14
13
|
# In this case, +where+ can be chained to return a new relation.
|
15
14
|
class WhereChain
|
16
15
|
def initialize(scope) # :nodoc:
|
@@ -39,7 +38,7 @@ 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')
|
43
42
|
#
|
44
43
|
# If there is a non-nil condition on a nullable column in the hash condition, the records that have
|
45
44
|
# nil values on the nullable column won't be returned.
|
@@ -73,10 +72,26 @@ module ActiveRecord
|
|
73
72
|
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
74
73
|
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
75
74
|
# # WHERE "authors"."id" IS NOT NULL AND "comments"."id" IS NOT NULL
|
75
|
+
#
|
76
|
+
# You can define join type in the scope and +associated+ will not use `JOIN` by default.
|
77
|
+
#
|
78
|
+
# Post.left_joins(:author).where.associated(:author)
|
79
|
+
# # SELECT "posts".* FROM "posts"
|
80
|
+
# # LEFT OUTER JOIN "authors" "authors"."id" = "posts"."author_id"
|
81
|
+
# # WHERE "authors"."id" IS NOT NULL
|
82
|
+
#
|
83
|
+
# Post.left_joins(:comments).where.associated(:author)
|
84
|
+
# # SELECT "posts".* FROM "posts"
|
85
|
+
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
86
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
87
|
+
# # WHERE "author"."id" IS NOT NULL
|
76
88
|
def associated(*associations)
|
77
89
|
associations.each do |association|
|
78
90
|
reflection = scope_association_reflection(association)
|
79
|
-
@scope.
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
92
|
+
@scope.joins!(association)
|
93
|
+
end
|
94
|
+
|
80
95
|
if reflection.options[:class_name]
|
81
96
|
self.not(association => { reflection.association_primary_key => nil })
|
82
97
|
else
|
@@ -129,6 +144,15 @@ module ActiveRecord
|
|
129
144
|
end
|
130
145
|
end
|
131
146
|
|
147
|
+
# A wrapper to distinguish CTE joins from other nodes.
|
148
|
+
class CTEJoin # :nodoc:
|
149
|
+
attr_reader :name
|
150
|
+
|
151
|
+
def initialize(name)
|
152
|
+
@name = name
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
132
156
|
FROZEN_EMPTY_ARRAY = [].freeze
|
133
157
|
FROZEN_EMPTY_HASH = {}.freeze
|
134
158
|
|
@@ -149,7 +173,7 @@ module ActiveRecord
|
|
149
173
|
end # end
|
150
174
|
|
151
175
|
def #{method_name}=(value) # def includes_values=(value)
|
152
|
-
|
176
|
+
assert_modifiable! # assert_modifiable!
|
153
177
|
@values[:#{name}] = value # @values[:includes] = value
|
154
178
|
end # end
|
155
179
|
CODE
|
@@ -157,45 +181,69 @@ module ActiveRecord
|
|
157
181
|
|
158
182
|
alias extensions extending_values
|
159
183
|
|
160
|
-
# Specify
|
161
|
-
#
|
184
|
+
# Specify associations +args+ to be eager loaded to prevent N + 1 queries.
|
185
|
+
# A separate query is performed for each association, unless a join is
|
186
|
+
# required by conditions.
|
162
187
|
#
|
163
|
-
#
|
188
|
+
# For example:
|
189
|
+
#
|
190
|
+
# users = User.includes(:address).limit(5)
|
164
191
|
# users.each do |user|
|
165
192
|
# user.address.city
|
166
193
|
# end
|
167
194
|
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
# performance improvement over a simple join.
|
195
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
196
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
171
197
|
#
|
172
|
-
#
|
198
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
199
|
+
# are loaded with a single query.
|
173
200
|
#
|
174
|
-
#
|
201
|
+
# Loading the associations in a separate query will often result in a
|
202
|
+
# performance improvement over a simple join, as a join can result in many
|
203
|
+
# rows that contain redundant data and it performs poorly at scale.
|
175
204
|
#
|
176
|
-
#
|
205
|
+
# You can also specify multiple associations. Each association will result
|
206
|
+
# in an additional query:
|
207
|
+
#
|
208
|
+
# User.includes(:address, :friends).to_a
|
209
|
+
# # SELECT "users".* FROM "users"
|
210
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
211
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
177
212
|
#
|
178
|
-
#
|
213
|
+
# Loading nested associations is possible using a Hash:
|
214
|
+
#
|
215
|
+
# User.includes(:address, friends: [:address, :followers])
|
179
216
|
#
|
180
217
|
# === Conditions
|
181
218
|
#
|
182
219
|
# If you want to add string conditions to your included models, you'll have
|
183
220
|
# to explicitly reference them. For example:
|
184
221
|
#
|
185
|
-
# User.includes(:posts).where('posts.name = ?', 'example')
|
222
|
+
# User.includes(:posts).where('posts.name = ?', 'example').to_a
|
186
223
|
#
|
187
224
|
# Will throw an error, but this will work:
|
188
225
|
#
|
189
|
-
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
226
|
+
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts).to_a
|
227
|
+
# # SELECT "users"."id" AS t0_r0, ... FROM "users"
|
228
|
+
# # LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
229
|
+
# # WHERE "posts"."name" = ? [["name", "example"]]
|
230
|
+
#
|
231
|
+
# As the <tt>LEFT OUTER JOIN</tt> already contains the posts, the second query for
|
232
|
+
# the posts is no longer performed.
|
190
233
|
#
|
191
234
|
# Note that #includes works with association names while #references needs
|
192
235
|
# the actual table name.
|
193
236
|
#
|
194
|
-
# If you pass the conditions via
|
237
|
+
# If you pass the conditions via a Hash, you don't need to call #references
|
195
238
|
# explicitly, as #where references the tables for you. For example, this
|
196
239
|
# will work correctly:
|
197
240
|
#
|
198
241
|
# User.includes(:posts).where(posts: { name: 'example' })
|
242
|
+
#
|
243
|
+
# NOTE: Conditions affect both sides of an association. For example, the
|
244
|
+
# above code will return only users that have a post named "example",
|
245
|
+
# <em>and will only include posts named "example"</em>, even when a
|
246
|
+
# matching user has other additional posts.
|
199
247
|
def includes(*args)
|
200
248
|
check_if_method_has_arguments!(__callee__, args)
|
201
249
|
spawn.includes!(*args)
|
@@ -206,12 +254,32 @@ module ActiveRecord
|
|
206
254
|
self
|
207
255
|
end
|
208
256
|
|
209
|
-
#
|
257
|
+
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
258
|
+
# Performs a single query joining all specified associations. For example:
|
259
|
+
#
|
260
|
+
# users = User.eager_load(:address).limit(5)
|
261
|
+
# users.each do |user|
|
262
|
+
# user.address.city
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
266
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
267
|
+
# # LIMIT 5
|
268
|
+
#
|
269
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
270
|
+
# are loaded with a single joined query.
|
210
271
|
#
|
211
|
-
#
|
212
|
-
#
|
213
|
-
#
|
214
|
-
#
|
272
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
273
|
+
# similar to #includes:
|
274
|
+
#
|
275
|
+
# User.eager_load(:address, friends: [:address, :followers])
|
276
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... FROM "users"
|
277
|
+
# # LEFT OUTER JOIN "addresses" ON "addresses"."id" = "users"."address_id"
|
278
|
+
# # LEFT OUTER JOIN "friends" ON "friends"."user_id" = "users"."id"
|
279
|
+
# # ...
|
280
|
+
#
|
281
|
+
# NOTE: Loading the associations in a join can result in many rows that
|
282
|
+
# contain redundant data and it performs poorly at scale.
|
215
283
|
def eager_load(*args)
|
216
284
|
check_if_method_has_arguments!(__callee__, args)
|
217
285
|
spawn.eager_load!(*args)
|
@@ -222,10 +290,28 @@ module ActiveRecord
|
|
222
290
|
self
|
223
291
|
end
|
224
292
|
|
225
|
-
#
|
293
|
+
# Specify associations +args+ to be eager loaded using separate queries.
|
294
|
+
# A separate query is performed for each association.
|
295
|
+
#
|
296
|
+
# users = User.preload(:address).limit(5)
|
297
|
+
# users.each do |user|
|
298
|
+
# user.address.city
|
299
|
+
# end
|
300
|
+
#
|
301
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
302
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
226
303
|
#
|
227
|
-
#
|
228
|
-
#
|
304
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
305
|
+
# are loaded with a separate query.
|
306
|
+
#
|
307
|
+
# Loading multiple and nested associations is possible using Hashes and Arrays,
|
308
|
+
# similar to #includes:
|
309
|
+
#
|
310
|
+
# User.preload(:address, friends: [:address, :followers])
|
311
|
+
# # SELECT "users".* FROM "users"
|
312
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
313
|
+
# # SELECT "friends".* FROM "friends" WHERE "friends"."user_id" IN (1,2,3,4,5)
|
314
|
+
# # SELECT ...
|
229
315
|
def preload(*args)
|
230
316
|
check_if_method_has_arguments!(__callee__, args)
|
231
317
|
spawn.preload!(*args)
|
@@ -250,7 +336,7 @@ module ActiveRecord
|
|
250
336
|
end
|
251
337
|
|
252
338
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
253
|
-
# and should therefore be
|
339
|
+
# and should therefore be +JOIN+ed in any query rather than loaded separately.
|
254
340
|
# This method only works in conjunction with #includes.
|
255
341
|
# See #includes for more details.
|
256
342
|
#
|
@@ -294,6 +380,14 @@ module ActiveRecord
|
|
294
380
|
# Model.select(:field, :other_field, :and_one_more)
|
295
381
|
# # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
|
296
382
|
#
|
383
|
+
# The argument also can be a hash of fields and aliases.
|
384
|
+
#
|
385
|
+
# Model.select(models: { field: :alias, other_field: :other_alias })
|
386
|
+
# # => [#<Model id: nil, alias: "value", other_alias: "value">]
|
387
|
+
#
|
388
|
+
# Model.select(models: [:field, :other_field])
|
389
|
+
# # => [#<Model id: nil, field: "value", other_field: "value">]
|
390
|
+
#
|
297
391
|
# You can also use one or more strings, which will be used unchanged as SELECT fields.
|
298
392
|
#
|
299
393
|
# Model.select('field AS field_one', 'other_field AS field_two')
|
@@ -308,7 +402,7 @@ module ActiveRecord
|
|
308
402
|
# except +id+ will throw ActiveModel::MissingAttributeError:
|
309
403
|
#
|
310
404
|
# Model.select(:field).first.other_field
|
311
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
405
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
|
312
406
|
def select(*fields)
|
313
407
|
if block_given?
|
314
408
|
if fields.any?
|
@@ -319,6 +413,8 @@ module ActiveRecord
|
|
319
413
|
end
|
320
414
|
|
321
415
|
check_if_method_has_arguments!(__callee__, fields, "Call `select' with at least one field.")
|
416
|
+
|
417
|
+
fields = process_select_args(fields)
|
322
418
|
spawn._select!(*fields)
|
323
419
|
end
|
324
420
|
|
@@ -327,6 +423,102 @@ module ActiveRecord
|
|
327
423
|
self
|
328
424
|
end
|
329
425
|
|
426
|
+
# Add a Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
427
|
+
#
|
428
|
+
# Note: CTE's are only supported in MySQL for versions 8.0 and above. You will not be able to
|
429
|
+
# use CTE's with MySQL 5.7.
|
430
|
+
#
|
431
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0))
|
432
|
+
# # => ActiveRecord::Relation
|
433
|
+
# # WITH posts_with_tags AS (
|
434
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
435
|
+
# # )
|
436
|
+
# # SELECT * FROM posts
|
437
|
+
#
|
438
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
439
|
+
#
|
440
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
441
|
+
# # => ActiveRecord::Relation
|
442
|
+
# # WITH posts_with_tags_or_comments AS (
|
443
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
444
|
+
# # UNION ALL
|
445
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
446
|
+
# # )
|
447
|
+
# # SELECT * FROM posts
|
448
|
+
#
|
449
|
+
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
450
|
+
#
|
451
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
452
|
+
# # => ActiveRecord::Relation
|
453
|
+
# # WITH posts_with_tags AS (
|
454
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
455
|
+
# # )
|
456
|
+
# # SELECT * FROM posts_with_tags AS posts
|
457
|
+
#
|
458
|
+
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).joins("JOIN posts_with_tags ON posts_with_tags.id = posts.id")
|
459
|
+
# # => ActiveRecord::Relation
|
460
|
+
# # WITH posts_with_tags AS (
|
461
|
+
# # SELECT * FROM posts WHERE (tags_count > 0)
|
462
|
+
# # )
|
463
|
+
# # SELECT * FROM posts JOIN posts_with_tags ON posts_with_tags.id = posts.id
|
464
|
+
#
|
465
|
+
# It is recommended to pass a query as ActiveRecord::Relation. If that is not possible
|
466
|
+
# and you have verified it is safe for the database, you can pass it as SQL literal
|
467
|
+
# using +Arel+.
|
468
|
+
#
|
469
|
+
# Post.with(popular_posts: Arel.sql("... complex sql to calculate posts popularity ..."))
|
470
|
+
#
|
471
|
+
# Great caution should be taken to avoid SQL injection vulnerabilities. This method should not
|
472
|
+
# be used with unsafe values that include unsanitized input.
|
473
|
+
#
|
474
|
+
# To add multiple CTEs just pass multiple key-value pairs
|
475
|
+
#
|
476
|
+
# Post.with(
|
477
|
+
# posts_with_comments: Post.where("comments_count > ?", 0),
|
478
|
+
# posts_with_tags: Post.where("tags_count > ?", 0)
|
479
|
+
# )
|
480
|
+
#
|
481
|
+
# or chain multiple +.with+ calls
|
482
|
+
#
|
483
|
+
# Post
|
484
|
+
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
485
|
+
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
486
|
+
def with(*args)
|
487
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
488
|
+
check_if_method_has_arguments!(__callee__, args)
|
489
|
+
spawn.with!(*args)
|
490
|
+
end
|
491
|
+
|
492
|
+
# Like #with, but modifies relation in place.
|
493
|
+
def with!(*args) # :nodoc:
|
494
|
+
self.with_values += args
|
495
|
+
self
|
496
|
+
end
|
497
|
+
|
498
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
499
|
+
#
|
500
|
+
# Post.with_recursive(post_and_replies: [Post.where(id: 42), Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id')])
|
501
|
+
# # => ActiveRecord::Relation
|
502
|
+
# # WITH post_and_replies AS (
|
503
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
504
|
+
# # UNION ALL
|
505
|
+
# # (SELECT * FROM posts JOIN posts_and_replies ON posts.in_reply_to_id = posts_and_replies.id)
|
506
|
+
# # )
|
507
|
+
# # SELECT * FROM posts
|
508
|
+
#
|
509
|
+
# See `#with` for more information.
|
510
|
+
def with_recursive(*args)
|
511
|
+
check_if_method_has_arguments!(__callee__, args)
|
512
|
+
spawn.with_recursive!(*args)
|
513
|
+
end
|
514
|
+
|
515
|
+
# Like #with_recursive but modifies the relation in place.
|
516
|
+
def with_recursive!(*args) # :nodoc:
|
517
|
+
self.with_values += args
|
518
|
+
@with_is_recursive = true
|
519
|
+
self
|
520
|
+
end
|
521
|
+
|
330
522
|
# Allows you to change a previously set select statement.
|
331
523
|
#
|
332
524
|
# Post.select(:title, :body)
|
@@ -339,6 +531,7 @@ module ActiveRecord
|
|
339
531
|
# Note that we're unscoping the entire select statement.
|
340
532
|
def reselect(*args)
|
341
533
|
check_if_method_has_arguments!(__callee__, args)
|
534
|
+
args = process_select_args(args)
|
342
535
|
spawn.reselect!(*args)
|
343
536
|
end
|
344
537
|
|
@@ -378,6 +571,27 @@ module ActiveRecord
|
|
378
571
|
self
|
379
572
|
end
|
380
573
|
|
574
|
+
# Allows you to change a previously set group statement.
|
575
|
+
#
|
576
|
+
# Post.group(:title, :body)
|
577
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
578
|
+
#
|
579
|
+
# Post.group(:title, :body).regroup(:title)
|
580
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
581
|
+
#
|
582
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
583
|
+
# Note that we're unscoping the entire group statement.
|
584
|
+
def regroup(*args)
|
585
|
+
check_if_method_has_arguments!(__callee__, args)
|
586
|
+
spawn.regroup!(*args)
|
587
|
+
end
|
588
|
+
|
589
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
590
|
+
def regroup!(*args) # :nodoc:
|
591
|
+
self.group_values = args
|
592
|
+
self
|
593
|
+
end
|
594
|
+
|
381
595
|
# Applies an <code>ORDER BY</code> clause to a query.
|
382
596
|
#
|
383
597
|
# #order accepts arguments in one of several formats.
|
@@ -426,7 +640,7 @@ module ActiveRecord
|
|
426
640
|
# User.order(Arel.sql('end_date - start_date'))
|
427
641
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
428
642
|
#
|
429
|
-
# Custom query syntax, like JSON columns for
|
643
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
430
644
|
#
|
431
645
|
# User.order(Arel.sql("payload->>'kind'"))
|
432
646
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
@@ -444,7 +658,8 @@ module ActiveRecord
|
|
444
658
|
self
|
445
659
|
end
|
446
660
|
|
447
|
-
#
|
661
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
662
|
+
# ordered and filtered by a specific set of +values+.
|
448
663
|
#
|
449
664
|
# User.in_order_of(:id, [1, 5, 3])
|
450
665
|
# # SELECT "users".* FROM "users"
|
@@ -455,15 +670,41 @@ module ActiveRecord
|
|
455
670
|
# # WHEN "users"."id" = 3 THEN 3
|
456
671
|
# # END ASC
|
457
672
|
#
|
673
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
674
|
+
# on the database adapter and the column definition.
|
675
|
+
#
|
676
|
+
# class Conversation < ActiveRecord::Base
|
677
|
+
# enum :status, [ :active, :archived ]
|
678
|
+
# end
|
679
|
+
#
|
680
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
681
|
+
# # SELECT "conversations".* FROM "conversations"
|
682
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
683
|
+
# # ORDER BY CASE
|
684
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
685
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
686
|
+
# # END ASC
|
687
|
+
#
|
688
|
+
# +values+ can also include +nil+.
|
689
|
+
#
|
690
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
691
|
+
# # SELECT "conversations".* FROM "conversations"
|
692
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
693
|
+
# # ORDER BY CASE
|
694
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
695
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
696
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
697
|
+
# # END ASC
|
698
|
+
#
|
458
699
|
def in_order_of(column, values)
|
459
|
-
klass.disallow_raw_sql!([column], permit:
|
700
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
460
701
|
return spawn.none! if values.empty?
|
461
702
|
|
462
703
|
references = column_references([column])
|
463
704
|
self.references_values |= references unless references.empty?
|
464
705
|
|
465
706
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
466
|
-
arel_column = column.is_a?(
|
707
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
467
708
|
|
468
709
|
where_clause =
|
469
710
|
if values.include?(nil)
|
@@ -485,7 +726,7 @@ module ActiveRecord
|
|
485
726
|
#
|
486
727
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
487
728
|
#
|
488
|
-
# generates a query with
|
729
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
489
730
|
def reorder(*args)
|
490
731
|
check_if_method_has_arguments!(__callee__, args) do
|
491
732
|
sanitize_order_arguments(args)
|
@@ -504,7 +745,8 @@ module ActiveRecord
|
|
504
745
|
|
505
746
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
506
747
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
507
|
-
:includes, :
|
748
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
749
|
+
:having, :optimizer_hints, :with])
|
508
750
|
|
509
751
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
510
752
|
# This is useful when passing around chains of relations and would like to
|
@@ -554,7 +796,7 @@ module ActiveRecord
|
|
554
796
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
555
797
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
556
798
|
end
|
557
|
-
|
799
|
+
assert_modifiable!
|
558
800
|
@values.delete(scope)
|
559
801
|
when Hash
|
560
802
|
scope.each do |key, target_value|
|
@@ -614,7 +856,7 @@ module ActiveRecord
|
|
614
856
|
# Performs LEFT OUTER JOINs on +args+:
|
615
857
|
#
|
616
858
|
# User.left_outer_joins(:posts)
|
617
|
-
#
|
859
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
618
860
|
#
|
619
861
|
def left_outer_joins(*args)
|
620
862
|
check_if_method_has_arguments!(__callee__, args)
|
@@ -634,7 +876,7 @@ module ActiveRecord
|
|
634
876
|
# SQL is given as an illustration; the actual query generated may be different depending
|
635
877
|
# on the database adapter.
|
636
878
|
#
|
637
|
-
# ===
|
879
|
+
# === \String
|
638
880
|
#
|
639
881
|
# A single string, without additional arguments, is passed to the query
|
640
882
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
@@ -646,7 +888,7 @@ module ActiveRecord
|
|
646
888
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
647
889
|
# to use one of the following methods.
|
648
890
|
#
|
649
|
-
# ===
|
891
|
+
# === \Array
|
650
892
|
#
|
651
893
|
# If an array is passed, then the first element of the array is treated as a template, and
|
652
894
|
# the remaining elements are inserted into the template to generate the condition.
|
@@ -686,7 +928,7 @@ module ActiveRecord
|
|
686
928
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
687
929
|
# test with multiple database backends.
|
688
930
|
#
|
689
|
-
# ===
|
931
|
+
# === \Hash
|
690
932
|
#
|
691
933
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
692
934
|
# are values to be searched for.
|
@@ -720,6 +962,12 @@ module ActiveRecord
|
|
720
962
|
# PriceEstimate.where(estimate_of: treasure)
|
721
963
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
722
964
|
#
|
965
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
966
|
+
# an array of columns with an array of tuples as values.
|
967
|
+
#
|
968
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
969
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
970
|
+
#
|
723
971
|
# === Joins
|
724
972
|
#
|
725
973
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
@@ -732,7 +980,7 @@ module ActiveRecord
|
|
732
980
|
# User.joins(:posts).where("posts.published" => true)
|
733
981
|
# User.joins(:posts).where(posts: { published: true })
|
734
982
|
#
|
735
|
-
# ===
|
983
|
+
# === No Argument
|
736
984
|
#
|
737
985
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
738
986
|
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
@@ -756,7 +1004,7 @@ module ActiveRecord
|
|
756
1004
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
757
1005
|
# # WHERE "authors"."id" IS NULL
|
758
1006
|
#
|
759
|
-
# ===
|
1007
|
+
# === Blank Condition
|
760
1008
|
#
|
761
1009
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
762
1010
|
# the current relation.
|
@@ -789,6 +1037,8 @@ module ActiveRecord
|
|
789
1037
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
790
1038
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
791
1039
|
def rewhere(conditions)
|
1040
|
+
return unscope(:where) if conditions.nil?
|
1041
|
+
|
792
1042
|
scope = spawn
|
793
1043
|
where_clause = scope.build_where_clause(conditions)
|
794
1044
|
|
@@ -894,7 +1144,11 @@ module ActiveRecord
|
|
894
1144
|
#
|
895
1145
|
def or(other)
|
896
1146
|
if other.is_a?(Relation)
|
897
|
-
|
1147
|
+
if @none
|
1148
|
+
other.spawn
|
1149
|
+
else
|
1150
|
+
spawn.or!(other)
|
1151
|
+
end
|
898
1152
|
else
|
899
1153
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
900
1154
|
end
|
@@ -907,7 +1161,7 @@ module ActiveRecord
|
|
907
1161
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
908
1162
|
end
|
909
1163
|
|
910
|
-
self.where_clause =
|
1164
|
+
self.where_clause = where_clause.or(other.where_clause)
|
911
1165
|
self.having_clause = having_clause.or(other.having_clause)
|
912
1166
|
self.references_values |= other.references_values
|
913
1167
|
|
@@ -1007,15 +1261,29 @@ module ActiveRecord
|
|
1007
1261
|
end
|
1008
1262
|
|
1009
1263
|
def none! # :nodoc:
|
1010
|
-
|
1264
|
+
unless @none
|
1265
|
+
where!("1=0")
|
1266
|
+
@none = true
|
1267
|
+
end
|
1268
|
+
self
|
1011
1269
|
end
|
1012
1270
|
|
1013
|
-
|
1014
|
-
|
1271
|
+
def null_relation? # :nodoc:
|
1272
|
+
@none
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
1276
|
+
# an error.
|
1015
1277
|
#
|
1016
1278
|
# users = User.readonly
|
1017
1279
|
# users.first.save
|
1018
1280
|
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1281
|
+
#
|
1282
|
+
# To make a readonly relation writable, pass +false+.
|
1283
|
+
#
|
1284
|
+
# users.readonly(false)
|
1285
|
+
# users.first.save
|
1286
|
+
# => true
|
1019
1287
|
def readonly(value = true)
|
1020
1288
|
spawn.readonly!(value)
|
1021
1289
|
end
|
@@ -1132,7 +1400,7 @@ module ActiveRecord
|
|
1132
1400
|
#
|
1133
1401
|
# The object returned is a relation, which can be further extended.
|
1134
1402
|
#
|
1135
|
-
# === Using a
|
1403
|
+
# === Using a \Module
|
1136
1404
|
#
|
1137
1405
|
# module Pagination
|
1138
1406
|
# def page(number)
|
@@ -1147,7 +1415,7 @@ module ActiveRecord
|
|
1147
1415
|
#
|
1148
1416
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
1149
1417
|
#
|
1150
|
-
# === Using a
|
1418
|
+
# === Using a Block
|
1151
1419
|
#
|
1152
1420
|
# scope = Model.all.extending do
|
1153
1421
|
# def page(number)
|
@@ -1264,6 +1532,9 @@ module ActiveRecord
|
|
1264
1532
|
# Post.excluding(post_one, post_two)
|
1265
1533
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
1266
1534
|
#
|
1535
|
+
# Post.excluding(Post.drafts)
|
1536
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
1537
|
+
#
|
1267
1538
|
# This can also be called on associations. As with the above example, either
|
1268
1539
|
# a single record of collection thereof may be specified:
|
1269
1540
|
#
|
@@ -1279,14 +1550,15 @@ module ActiveRecord
|
|
1279
1550
|
# is passed in) are not instances of the same model that the relation is
|
1280
1551
|
# scoping.
|
1281
1552
|
def excluding(*records)
|
1553
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
1282
1554
|
records.flatten!(1)
|
1283
1555
|
records.compact!
|
1284
1556
|
|
1285
|
-
unless records.all?(klass)
|
1557
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
1286
1558
|
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
1287
1559
|
end
|
1288
1560
|
|
1289
|
-
spawn.excluding!(records)
|
1561
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
1290
1562
|
end
|
1291
1563
|
alias :without :excluding
|
1292
1564
|
|
@@ -1298,7 +1570,7 @@ module ActiveRecord
|
|
1298
1570
|
|
1299
1571
|
# Returns the Arel object associated with the relation.
|
1300
1572
|
def arel(aliases = nil) # :nodoc:
|
1301
|
-
@arel ||= build_arel(aliases)
|
1573
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
1302
1574
|
end
|
1303
1575
|
|
1304
1576
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
@@ -1319,13 +1591,29 @@ module ActiveRecord
|
|
1319
1591
|
def build_where_clause(opts, rest = []) # :nodoc:
|
1320
1592
|
opts = sanitize_forbidden_attributes(opts)
|
1321
1593
|
|
1594
|
+
if opts.is_a?(Array)
|
1595
|
+
opts, *rest = opts
|
1596
|
+
end
|
1597
|
+
|
1322
1598
|
case opts
|
1323
|
-
when String
|
1324
|
-
|
1599
|
+
when String
|
1600
|
+
if rest.empty?
|
1601
|
+
parts = [Arel.sql(opts)]
|
1602
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
1603
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
1604
|
+
elsif opts.include?("?")
|
1605
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
1606
|
+
else
|
1607
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1608
|
+
end
|
1325
1609
|
when Hash
|
1326
1610
|
opts = opts.transform_keys do |key|
|
1327
|
-
|
1328
|
-
|
1611
|
+
if key.is_a?(Array)
|
1612
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
1613
|
+
else
|
1614
|
+
key = key.to_s
|
1615
|
+
klass.attribute_aliases[key] || key
|
1616
|
+
end
|
1329
1617
|
end
|
1330
1618
|
references = PredicateBuilder.references(opts)
|
1331
1619
|
self.references_values |= references unless references.empty?
|
@@ -1343,7 +1631,76 @@ module ActiveRecord
|
|
1343
1631
|
end
|
1344
1632
|
alias :build_having_clause :build_where_clause
|
1345
1633
|
|
1634
|
+
def async!
|
1635
|
+
@async = true
|
1636
|
+
self
|
1637
|
+
end
|
1638
|
+
|
1639
|
+
protected
|
1640
|
+
def arel_columns(columns)
|
1641
|
+
columns.flat_map do |field|
|
1642
|
+
case field
|
1643
|
+
when Symbol
|
1644
|
+
arel_column(field.to_s) do |attr_name|
|
1645
|
+
adapter_class.quote_table_name(attr_name)
|
1646
|
+
end
|
1647
|
+
when String
|
1648
|
+
arel_column(field, &:itself)
|
1649
|
+
when Proc
|
1650
|
+
field.call
|
1651
|
+
when Hash
|
1652
|
+
arel_columns_from_hash(field)
|
1653
|
+
else
|
1654
|
+
field
|
1655
|
+
end
|
1656
|
+
end
|
1657
|
+
end
|
1658
|
+
|
1346
1659
|
private
|
1660
|
+
def async
|
1661
|
+
spawn.async!
|
1662
|
+
end
|
1663
|
+
|
1664
|
+
def build_named_bound_sql_literal(statement, values)
|
1665
|
+
bound_values = values.transform_values do |value|
|
1666
|
+
if ActiveRecord::Relation === value
|
1667
|
+
Arel.sql(value.to_sql)
|
1668
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1669
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1670
|
+
values.empty? ? nil : values
|
1671
|
+
else
|
1672
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1673
|
+
value
|
1674
|
+
end
|
1675
|
+
end
|
1676
|
+
|
1677
|
+
begin
|
1678
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
1679
|
+
rescue Arel::BindError => error
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1681
|
+
end
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
def build_bound_sql_literal(statement, values)
|
1685
|
+
bound_values = values.map do |value|
|
1686
|
+
if ActiveRecord::Relation === value
|
1687
|
+
Arel.sql(value.to_sql)
|
1688
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
1689
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
1690
|
+
values.empty? ? nil : values
|
1691
|
+
else
|
1692
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
1693
|
+
value
|
1694
|
+
end
|
1695
|
+
end
|
1696
|
+
|
1697
|
+
begin
|
1698
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
1699
|
+
rescue Arel::BindError => error
|
1700
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
|
1347
1704
|
def lookup_table_klass_from_join_dependencies(table_name)
|
1348
1705
|
each_join_dependencies do |join|
|
1349
1706
|
return join.base_klass if table_name == join.table_name
|
@@ -1358,22 +1715,21 @@ module ActiveRecord
|
|
1358
1715
|
end
|
1359
1716
|
|
1360
1717
|
def build_join_dependencies
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1718
|
+
joins = joins_values | left_outer_joins_values
|
1719
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
1720
|
+
joins |= includes_values unless includes_values.empty?
|
1364
1721
|
|
1365
1722
|
join_dependencies = []
|
1366
1723
|
join_dependencies.unshift construct_join_dependency(
|
1367
|
-
|
1724
|
+
select_named_joins(joins, join_dependencies), nil
|
1368
1725
|
)
|
1369
1726
|
end
|
1370
1727
|
|
1371
|
-
def
|
1372
|
-
raise
|
1373
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
1728
|
+
def assert_modifiable!
|
1729
|
+
raise UnmodifiableRelation if @loaded || @arel
|
1374
1730
|
end
|
1375
1731
|
|
1376
|
-
def build_arel(aliases = nil)
|
1732
|
+
def build_arel(connection, aliases = nil)
|
1377
1733
|
arel = Arel::SelectManager.new(table)
|
1378
1734
|
|
1379
1735
|
build_joins(arel.join_sources, aliases)
|
@@ -1385,6 +1741,7 @@ module ActiveRecord
|
|
1385
1741
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1386
1742
|
|
1387
1743
|
build_order(arel)
|
1744
|
+
build_with(arel)
|
1388
1745
|
build_select(arel)
|
1389
1746
|
|
1390
1747
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
@@ -1420,6 +1777,18 @@ module ActiveRecord
|
|
1420
1777
|
end
|
1421
1778
|
end
|
1422
1779
|
|
1780
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
1781
|
+
cte_joins, associations = join_names.partition do |join_name|
|
1782
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
cte_joins.each do |cte_name|
|
1786
|
+
block&.call(CTEJoin.new(cte_name))
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
select_association_list(associations, stashed_joins, &block)
|
1790
|
+
end
|
1791
|
+
|
1423
1792
|
def select_association_list(associations, stashed_joins = nil)
|
1424
1793
|
result = []
|
1425
1794
|
associations.each do |association|
|
@@ -1435,20 +1804,21 @@ module ActiveRecord
|
|
1435
1804
|
result
|
1436
1805
|
end
|
1437
1806
|
|
1438
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1439
|
-
end
|
1440
|
-
|
1441
1807
|
def build_join_buckets
|
1442
1808
|
buckets = Hash.new { |h, k| h[k] = [] }
|
1443
1809
|
|
1444
1810
|
unless left_outer_joins_values.empty?
|
1445
1811
|
stashed_left_joins = []
|
1446
|
-
left_joins =
|
1447
|
-
|
1812
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
1813
|
+
if left_join.is_a?(CTEJoin)
|
1814
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
1815
|
+
else
|
1816
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1817
|
+
end
|
1448
1818
|
end
|
1449
1819
|
|
1450
1820
|
if joins_values.empty?
|
1451
|
-
buckets[:
|
1821
|
+
buckets[:named_join] = left_joins
|
1452
1822
|
buckets[:stashed_join] = stashed_left_joins
|
1453
1823
|
return buckets, Arel::Nodes::OuterJoin
|
1454
1824
|
else
|
@@ -1474,9 +1844,11 @@ module ActiveRecord
|
|
1474
1844
|
end
|
1475
1845
|
end
|
1476
1846
|
|
1477
|
-
buckets[:
|
1847
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
1478
1848
|
if join.is_a?(Arel::Nodes::Join)
|
1479
1849
|
buckets[:join_node] << join
|
1850
|
+
elsif join.is_a?(CTEJoin)
|
1851
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
1480
1852
|
else
|
1481
1853
|
raise "unknown class: %s" % join.class.name
|
1482
1854
|
end
|
@@ -1493,16 +1865,16 @@ module ActiveRecord
|
|
1493
1865
|
|
1494
1866
|
buckets, join_type = build_join_buckets
|
1495
1867
|
|
1496
|
-
|
1497
|
-
stashed_joins
|
1498
|
-
leading_joins
|
1499
|
-
join_nodes
|
1868
|
+
named_joins = buckets[:named_join]
|
1869
|
+
stashed_joins = buckets[:stashed_join]
|
1870
|
+
leading_joins = buckets[:leading_join]
|
1871
|
+
join_nodes = buckets[:join_node]
|
1500
1872
|
|
1501
1873
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1502
1874
|
|
1503
|
-
unless
|
1875
|
+
unless named_joins.empty? && stashed_joins.empty?
|
1504
1876
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1505
|
-
join_dependency = construct_join_dependency(
|
1877
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
1506
1878
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1507
1879
|
end
|
1508
1880
|
|
@@ -1520,31 +1892,65 @@ module ActiveRecord
|
|
1520
1892
|
end
|
1521
1893
|
end
|
1522
1894
|
|
1523
|
-
def
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1895
|
+
def build_with(arel)
|
1896
|
+
return if with_values.empty?
|
1897
|
+
|
1898
|
+
with_statements = with_values.map do |with_value|
|
1899
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
1900
|
+
|
1901
|
+
build_with_value_from_hash(with_value)
|
1902
|
+
end
|
1903
|
+
|
1904
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
1905
|
+
end
|
1906
|
+
|
1907
|
+
def build_with_value_from_hash(hash)
|
1908
|
+
hash.map do |name, value|
|
1909
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
1910
|
+
end
|
1911
|
+
end
|
1912
|
+
|
1913
|
+
def build_with_expression_from_value(value, nested = false)
|
1914
|
+
case value
|
1915
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
1916
|
+
when ActiveRecord::Relation
|
1917
|
+
if nested
|
1918
|
+
value.arel.ast
|
1534
1919
|
else
|
1535
|
-
|
1920
|
+
value.arel
|
1536
1921
|
end
|
1922
|
+
when Arel::SelectManager then value
|
1923
|
+
when Array
|
1924
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
1925
|
+
|
1926
|
+
parts = value.map do |query|
|
1927
|
+
build_with_expression_from_value(query, true)
|
1928
|
+
end
|
1929
|
+
|
1930
|
+
parts.reduce do |result, value|
|
1931
|
+
Arel::Nodes::UnionAll.new(result, value)
|
1932
|
+
end
|
1933
|
+
else
|
1934
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
1537
1935
|
end
|
1538
1936
|
end
|
1539
1937
|
|
1938
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
1939
|
+
with_table = Arel::Table.new(name)
|
1940
|
+
|
1941
|
+
table.join(with_table, kind).on(
|
1942
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
1943
|
+
).join_sources.first
|
1944
|
+
end
|
1945
|
+
|
1540
1946
|
def arel_column(field)
|
1541
1947
|
field = klass.attribute_aliases[field] || field
|
1542
1948
|
from = from_clause.name || from_clause.value
|
1543
1949
|
|
1544
1950
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1545
1951
|
table[field]
|
1546
|
-
elsif
|
1547
|
-
table,
|
1952
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
1953
|
+
self.references_values |= [Arel.sql(table, retryable: true)]
|
1548
1954
|
predicate_builder.resolve_arel_attribute(table, column) do
|
1549
1955
|
lookup_table_klass_from_join_dependencies(table)
|
1550
1956
|
end
|
@@ -1555,7 +1961,7 @@ module ActiveRecord
|
|
1555
1961
|
|
1556
1962
|
def table_name_matches?(from)
|
1557
1963
|
table_name = Regexp.escape(table.name)
|
1558
|
-
quoted_table_name = Regexp.escape(
|
1964
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
1559
1965
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1560
1966
|
end
|
1561
1967
|
|
@@ -1611,7 +2017,9 @@ module ActiveRecord
|
|
1611
2017
|
args.each do |arg|
|
1612
2018
|
next unless arg.is_a?(Hash)
|
1613
2019
|
arg.each do |_key, value|
|
1614
|
-
|
2020
|
+
if value.is_a?(Hash)
|
2021
|
+
validate_order_args([value])
|
2022
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
1615
2023
|
raise ArgumentError,
|
1616
2024
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1617
2025
|
end
|
@@ -1619,10 +2027,14 @@ module ActiveRecord
|
|
1619
2027
|
end
|
1620
2028
|
end
|
1621
2029
|
|
2030
|
+
def flattened_args(args)
|
2031
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
2032
|
+
end
|
2033
|
+
|
1622
2034
|
def preprocess_order_args(order_args)
|
1623
2035
|
@klass.disallow_raw_sql!(
|
1624
|
-
order_args
|
1625
|
-
permit:
|
2036
|
+
flattened_args(order_args),
|
2037
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
1626
2038
|
)
|
1627
2039
|
|
1628
2040
|
validate_order_args(order_args)
|
@@ -1636,14 +2048,20 @@ module ActiveRecord
|
|
1636
2048
|
when Symbol
|
1637
2049
|
order_column(arg.to_s).asc
|
1638
2050
|
when Hash
|
1639
|
-
arg.map
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
2051
|
+
arg.map do |key, value|
|
2052
|
+
if value.is_a?(Hash)
|
2053
|
+
value.map do |field, dir|
|
2054
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
2055
|
+
end
|
1643
2056
|
else
|
1644
|
-
|
2057
|
+
case key
|
2058
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
2059
|
+
key.public_send(value.downcase)
|
2060
|
+
else
|
2061
|
+
order_column(key.to_s).public_send(value.downcase)
|
2062
|
+
end
|
1645
2063
|
end
|
1646
|
-
|
2064
|
+
end
|
1647
2065
|
else
|
1648
2066
|
arg
|
1649
2067
|
end
|
@@ -1657,16 +2075,32 @@ module ActiveRecord
|
|
1657
2075
|
end
|
1658
2076
|
|
1659
2077
|
def column_references(order_args)
|
1660
|
-
|
2078
|
+
order_args.flat_map do |arg|
|
1661
2079
|
case arg
|
1662
2080
|
when String, Symbol
|
1663
|
-
arg
|
2081
|
+
extract_table_name_from(arg)
|
1664
2082
|
when Hash
|
1665
|
-
arg
|
2083
|
+
arg
|
2084
|
+
.map do |key, value|
|
2085
|
+
case value
|
2086
|
+
when Hash
|
2087
|
+
key.to_s
|
2088
|
+
else
|
2089
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
2090
|
+
end
|
2091
|
+
end
|
2092
|
+
when Arel::Attribute
|
2093
|
+
arg.relation.name
|
2094
|
+
when Arel::Nodes::Ordering
|
2095
|
+
if arg.expr.is_a?(Arel::Attribute)
|
2096
|
+
arg.expr.relation.name
|
2097
|
+
end
|
1666
2098
|
end
|
1667
|
-
end
|
1668
|
-
|
1669
|
-
|
2099
|
+
end.compact
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
def extract_table_name_from(string)
|
2103
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
1670
2104
|
end
|
1671
2105
|
|
1672
2106
|
def order_column(field)
|
@@ -1674,7 +2108,7 @@ module ActiveRecord
|
|
1674
2108
|
if attr_name == "count" && !group_values.empty?
|
1675
2109
|
table[attr_name]
|
1676
2110
|
else
|
1677
|
-
Arel.sql(
|
2111
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
1678
2112
|
end
|
1679
2113
|
end
|
1680
2114
|
end
|
@@ -1739,6 +2173,41 @@ module ActiveRecord
|
|
1739
2173
|
end
|
1740
2174
|
end
|
1741
2175
|
|
2176
|
+
def process_select_args(fields)
|
2177
|
+
fields.flat_map do |field|
|
2178
|
+
if field.is_a?(Hash)
|
2179
|
+
arel_columns_from_hash(field)
|
2180
|
+
else
|
2181
|
+
field
|
2182
|
+
end
|
2183
|
+
end
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
def arel_columns_from_hash(fields)
|
2187
|
+
fields.flat_map do |key, columns_aliases|
|
2188
|
+
case columns_aliases
|
2189
|
+
when Hash
|
2190
|
+
columns_aliases.map do |column, column_alias|
|
2191
|
+
if values[:joins]&.include?(key)
|
2192
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
2193
|
+
self.references_values |= references unless references.empty?
|
2194
|
+
end
|
2195
|
+
arel_column("#{key}.#{column}") do
|
2196
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
2197
|
+
end.as(column_alias.to_s)
|
2198
|
+
end
|
2199
|
+
when Array
|
2200
|
+
columns_aliases.map do |column|
|
2201
|
+
arel_column("#{key}.#{column}", &:itself)
|
2202
|
+
end
|
2203
|
+
when String, Symbol
|
2204
|
+
arel_column(key.to_s) do
|
2205
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
2206
|
+
end.as(columns_aliases.to_s)
|
2207
|
+
end
|
2208
|
+
end
|
2209
|
+
end
|
2210
|
+
|
1742
2211
|
STRUCTURAL_VALUE_METHODS = (
|
1743
2212
|
Relation::VALUE_METHODS -
|
1744
2213
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|