activerecord 7.0.8 → 7.2.0
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 +530 -2004
- 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 +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +5 -5
- 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 +328 -471
- data/lib/active_record/attribute_assignment.rb +1 -13
- 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 +7 -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 +58 -45
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +10 -24
- 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 +317 -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 +188 -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 +306 -128
- 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 +274 -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 +368 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
- 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 +217 -174
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
- 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 +44 -20
- data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
- data/lib/active_record/encryption/encryptor.rb +17 -2
- 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/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 +1 -0
- data/lib/active_record/enum.rb +122 -29
- 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 +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 +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 +88 -103
- data/lib/active_record/nested_attributes.rb +35 -9
- 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 +19 -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 +135 -86
- 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 +259 -68
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +196 -61
- data/lib/active_record/relation/calculations.rb +249 -92
- 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 +548 -94
- 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 +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/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 +180 -119
- 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 +60 -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/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 +56 -14
- 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.
|
|
187
|
+
#
|
|
188
|
+
# For example:
|
|
162
189
|
#
|
|
163
|
-
# users = User.includes(:address)
|
|
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
|
-
#
|
|
195
|
+
# # SELECT "users".* FROM "users" LIMIT 5
|
|
196
|
+
# # SELECT "addresses".* FROM "addresses" WHERE "addresses"."id" IN (1,2,3,4,5)
|
|
197
|
+
#
|
|
198
|
+
# Instead of loading the 5 addresses with 5 separate queries, all addresses
|
|
199
|
+
# are loaded with a single query.
|
|
171
200
|
#
|
|
172
|
-
#
|
|
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.
|
|
173
204
|
#
|
|
174
|
-
#
|
|
205
|
+
# You can also specify multiple associations. Each association will result
|
|
206
|
+
# in an additional query:
|
|
175
207
|
#
|
|
176
|
-
#
|
|
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:
|
|
210
259
|
#
|
|
211
|
-
# User.eager_load(:
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
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.
|
|
271
|
+
#
|
|
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
|
|
1269
|
+
end
|
|
1270
|
+
|
|
1271
|
+
def null_relation? # :nodoc:
|
|
1272
|
+
@none
|
|
1011
1273
|
end
|
|
1012
1274
|
|
|
1013
|
-
#
|
|
1014
|
-
#
|
|
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,56 @@ 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
|
+
|
|
1346
1639
|
private
|
|
1640
|
+
def async
|
|
1641
|
+
spawn.async!
|
|
1642
|
+
end
|
|
1643
|
+
|
|
1644
|
+
def build_named_bound_sql_literal(statement, values)
|
|
1645
|
+
bound_values = values.transform_values do |value|
|
|
1646
|
+
if ActiveRecord::Relation === value
|
|
1647
|
+
Arel.sql(value.to_sql)
|
|
1648
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1649
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1650
|
+
values.empty? ? nil : values
|
|
1651
|
+
else
|
|
1652
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1653
|
+
value
|
|
1654
|
+
end
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
begin
|
|
1658
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
|
1659
|
+
rescue Arel::BindError => error
|
|
1660
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1661
|
+
end
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
def build_bound_sql_literal(statement, values)
|
|
1665
|
+
bound_values = values.map 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})", bound_values, nil)
|
|
1679
|
+
rescue Arel::BindError => error
|
|
1680
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1681
|
+
end
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1347
1684
|
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1348
1685
|
each_join_dependencies do |join|
|
|
1349
1686
|
return join.base_klass if table_name == join.table_name
|
|
@@ -1358,22 +1695,21 @@ module ActiveRecord
|
|
|
1358
1695
|
end
|
|
1359
1696
|
|
|
1360
1697
|
def build_join_dependencies
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1698
|
+
joins = joins_values | left_outer_joins_values
|
|
1699
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
|
1700
|
+
joins |= includes_values unless includes_values.empty?
|
|
1364
1701
|
|
|
1365
1702
|
join_dependencies = []
|
|
1366
1703
|
join_dependencies.unshift construct_join_dependency(
|
|
1367
|
-
|
|
1704
|
+
select_named_joins(joins, join_dependencies), nil
|
|
1368
1705
|
)
|
|
1369
1706
|
end
|
|
1370
1707
|
|
|
1371
|
-
def
|
|
1372
|
-
raise
|
|
1373
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1708
|
+
def assert_modifiable!
|
|
1709
|
+
raise UnmodifiableRelation if @loaded || @arel
|
|
1374
1710
|
end
|
|
1375
1711
|
|
|
1376
|
-
def build_arel(aliases = nil)
|
|
1712
|
+
def build_arel(connection, aliases = nil)
|
|
1377
1713
|
arel = Arel::SelectManager.new(table)
|
|
1378
1714
|
|
|
1379
1715
|
build_joins(arel.join_sources, aliases)
|
|
@@ -1385,6 +1721,7 @@ module ActiveRecord
|
|
|
1385
1721
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
|
1386
1722
|
|
|
1387
1723
|
build_order(arel)
|
|
1724
|
+
build_with(arel)
|
|
1388
1725
|
build_select(arel)
|
|
1389
1726
|
|
|
1390
1727
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
@@ -1420,6 +1757,18 @@ module ActiveRecord
|
|
|
1420
1757
|
end
|
|
1421
1758
|
end
|
|
1422
1759
|
|
|
1760
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
|
1761
|
+
cte_joins, associations = join_names.partition do |join_name|
|
|
1762
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
cte_joins.each do |cte_name|
|
|
1766
|
+
block&.call(CTEJoin.new(cte_name))
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
select_association_list(associations, stashed_joins, &block)
|
|
1770
|
+
end
|
|
1771
|
+
|
|
1423
1772
|
def select_association_list(associations, stashed_joins = nil)
|
|
1424
1773
|
result = []
|
|
1425
1774
|
associations.each do |association|
|
|
@@ -1435,20 +1784,21 @@ module ActiveRecord
|
|
|
1435
1784
|
result
|
|
1436
1785
|
end
|
|
1437
1786
|
|
|
1438
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
|
1439
|
-
end
|
|
1440
|
-
|
|
1441
1787
|
def build_join_buckets
|
|
1442
1788
|
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1443
1789
|
|
|
1444
1790
|
unless left_outer_joins_values.empty?
|
|
1445
1791
|
stashed_left_joins = []
|
|
1446
|
-
left_joins =
|
|
1447
|
-
|
|
1792
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
|
1793
|
+
if left_join.is_a?(CTEJoin)
|
|
1794
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
|
1795
|
+
else
|
|
1796
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1797
|
+
end
|
|
1448
1798
|
end
|
|
1449
1799
|
|
|
1450
1800
|
if joins_values.empty?
|
|
1451
|
-
buckets[:
|
|
1801
|
+
buckets[:named_join] = left_joins
|
|
1452
1802
|
buckets[:stashed_join] = stashed_left_joins
|
|
1453
1803
|
return buckets, Arel::Nodes::OuterJoin
|
|
1454
1804
|
else
|
|
@@ -1474,9 +1824,11 @@ module ActiveRecord
|
|
|
1474
1824
|
end
|
|
1475
1825
|
end
|
|
1476
1826
|
|
|
1477
|
-
buckets[:
|
|
1827
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
|
1478
1828
|
if join.is_a?(Arel::Nodes::Join)
|
|
1479
1829
|
buckets[:join_node] << join
|
|
1830
|
+
elsif join.is_a?(CTEJoin)
|
|
1831
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
|
1480
1832
|
else
|
|
1481
1833
|
raise "unknown class: %s" % join.class.name
|
|
1482
1834
|
end
|
|
@@ -1493,16 +1845,16 @@ module ActiveRecord
|
|
|
1493
1845
|
|
|
1494
1846
|
buckets, join_type = build_join_buckets
|
|
1495
1847
|
|
|
1496
|
-
|
|
1497
|
-
stashed_joins
|
|
1498
|
-
leading_joins
|
|
1499
|
-
join_nodes
|
|
1848
|
+
named_joins = buckets[:named_join]
|
|
1849
|
+
stashed_joins = buckets[:stashed_join]
|
|
1850
|
+
leading_joins = buckets[:leading_join]
|
|
1851
|
+
join_nodes = buckets[:join_node]
|
|
1500
1852
|
|
|
1501
1853
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
|
1502
1854
|
|
|
1503
|
-
unless
|
|
1855
|
+
unless named_joins.empty? && stashed_joins.empty?
|
|
1504
1856
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
|
1505
|
-
join_dependency = construct_join_dependency(
|
|
1857
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
|
1506
1858
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
|
1507
1859
|
end
|
|
1508
1860
|
|
|
@@ -1520,17 +1872,56 @@ module ActiveRecord
|
|
|
1520
1872
|
end
|
|
1521
1873
|
end
|
|
1522
1874
|
|
|
1875
|
+
def build_with(arel)
|
|
1876
|
+
return if with_values.empty?
|
|
1877
|
+
|
|
1878
|
+
with_statements = with_values.map do |with_value|
|
|
1879
|
+
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
|
1880
|
+
|
|
1881
|
+
build_with_value_from_hash(with_value)
|
|
1882
|
+
end
|
|
1883
|
+
|
|
1884
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
|
1885
|
+
end
|
|
1886
|
+
|
|
1887
|
+
def build_with_value_from_hash(hash)
|
|
1888
|
+
hash.map do |name, value|
|
|
1889
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
|
1890
|
+
end
|
|
1891
|
+
end
|
|
1892
|
+
|
|
1893
|
+
def build_with_expression_from_value(value)
|
|
1894
|
+
case value
|
|
1895
|
+
when Arel::Nodes::SqlLiteral then Arel::Nodes::Grouping.new(value)
|
|
1896
|
+
when ActiveRecord::Relation then value.arel
|
|
1897
|
+
when Arel::SelectManager then value
|
|
1898
|
+
when Array then value.map { |q| build_with_expression_from_value(q) }.reduce { |result, value| result.union(:all, value) }
|
|
1899
|
+
else
|
|
1900
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
|
1901
|
+
end
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
def build_with_join_node(name, kind = Arel::Nodes::InnerJoin)
|
|
1905
|
+
with_table = Arel::Table.new(name)
|
|
1906
|
+
|
|
1907
|
+
table.join(with_table, kind).on(
|
|
1908
|
+
with_table[klass.model_name.to_s.foreign_key].eq(table[klass.primary_key])
|
|
1909
|
+
).join_sources.first
|
|
1910
|
+
end
|
|
1911
|
+
|
|
1523
1912
|
def arel_columns(columns)
|
|
1524
1913
|
columns.flat_map do |field|
|
|
1525
1914
|
case field
|
|
1526
1915
|
when Symbol
|
|
1527
1916
|
arel_column(field.to_s) do |attr_name|
|
|
1528
|
-
|
|
1917
|
+
adapter_class.quote_table_name(attr_name)
|
|
1529
1918
|
end
|
|
1530
1919
|
when String
|
|
1531
1920
|
arel_column(field, &:itself)
|
|
1532
1921
|
when Proc
|
|
1533
1922
|
field.call
|
|
1923
|
+
when Hash
|
|
1924
|
+
arel_columns_from_hash(field)
|
|
1534
1925
|
else
|
|
1535
1926
|
field
|
|
1536
1927
|
end
|
|
@@ -1555,7 +1946,7 @@ module ActiveRecord
|
|
|
1555
1946
|
|
|
1556
1947
|
def table_name_matches?(from)
|
|
1557
1948
|
table_name = Regexp.escape(table.name)
|
|
1558
|
-
quoted_table_name = Regexp.escape(
|
|
1949
|
+
quoted_table_name = Regexp.escape(adapter_class.quote_table_name(table.name))
|
|
1559
1950
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
1560
1951
|
end
|
|
1561
1952
|
|
|
@@ -1611,7 +2002,9 @@ module ActiveRecord
|
|
|
1611
2002
|
args.each do |arg|
|
|
1612
2003
|
next unless arg.is_a?(Hash)
|
|
1613
2004
|
arg.each do |_key, value|
|
|
1614
|
-
|
|
2005
|
+
if value.is_a?(Hash)
|
|
2006
|
+
validate_order_args([value])
|
|
2007
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
|
1615
2008
|
raise ArgumentError,
|
|
1616
2009
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
|
1617
2010
|
end
|
|
@@ -1619,10 +2012,14 @@ module ActiveRecord
|
|
|
1619
2012
|
end
|
|
1620
2013
|
end
|
|
1621
2014
|
|
|
2015
|
+
def flattened_args(args)
|
|
2016
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
|
2017
|
+
end
|
|
2018
|
+
|
|
1622
2019
|
def preprocess_order_args(order_args)
|
|
1623
2020
|
@klass.disallow_raw_sql!(
|
|
1624
|
-
order_args
|
|
1625
|
-
permit:
|
|
2021
|
+
flattened_args(order_args),
|
|
2022
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
|
1626
2023
|
)
|
|
1627
2024
|
|
|
1628
2025
|
validate_order_args(order_args)
|
|
@@ -1636,14 +2033,20 @@ module ActiveRecord
|
|
|
1636
2033
|
when Symbol
|
|
1637
2034
|
order_column(arg.to_s).asc
|
|
1638
2035
|
when Hash
|
|
1639
|
-
arg.map
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
2036
|
+
arg.map do |key, value|
|
|
2037
|
+
if value.is_a?(Hash)
|
|
2038
|
+
value.map do |field, dir|
|
|
2039
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
|
2040
|
+
end
|
|
1643
2041
|
else
|
|
1644
|
-
|
|
2042
|
+
case key
|
|
2043
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
|
2044
|
+
key.public_send(value.downcase)
|
|
2045
|
+
else
|
|
2046
|
+
order_column(key.to_s).public_send(value.downcase)
|
|
2047
|
+
end
|
|
1645
2048
|
end
|
|
1646
|
-
|
|
2049
|
+
end
|
|
1647
2050
|
else
|
|
1648
2051
|
arg
|
|
1649
2052
|
end
|
|
@@ -1657,16 +2060,32 @@ module ActiveRecord
|
|
|
1657
2060
|
end
|
|
1658
2061
|
|
|
1659
2062
|
def column_references(order_args)
|
|
1660
|
-
|
|
2063
|
+
order_args.flat_map do |arg|
|
|
1661
2064
|
case arg
|
|
1662
2065
|
when String, Symbol
|
|
1663
|
-
arg
|
|
2066
|
+
extract_table_name_from(arg)
|
|
1664
2067
|
when Hash
|
|
1665
|
-
arg
|
|
2068
|
+
arg
|
|
2069
|
+
.map do |key, value|
|
|
2070
|
+
case value
|
|
2071
|
+
when Hash
|
|
2072
|
+
key.to_s
|
|
2073
|
+
else
|
|
2074
|
+
extract_table_name_from(key) if key.is_a?(String) || key.is_a?(Symbol)
|
|
2075
|
+
end
|
|
2076
|
+
end
|
|
2077
|
+
when Arel::Attribute
|
|
2078
|
+
arg.relation.name
|
|
2079
|
+
when Arel::Nodes::Ordering
|
|
2080
|
+
if arg.expr.is_a?(Arel::Attribute)
|
|
2081
|
+
arg.expr.relation.name
|
|
2082
|
+
end
|
|
1666
2083
|
end
|
|
1667
|
-
end
|
|
1668
|
-
|
|
1669
|
-
|
|
2084
|
+
end.compact
|
|
2085
|
+
end
|
|
2086
|
+
|
|
2087
|
+
def extract_table_name_from(string)
|
|
2088
|
+
string.match(/^\W?(\w+)\W?\./) && $1
|
|
1670
2089
|
end
|
|
1671
2090
|
|
|
1672
2091
|
def order_column(field)
|
|
@@ -1674,7 +2093,7 @@ module ActiveRecord
|
|
|
1674
2093
|
if attr_name == "count" && !group_values.empty?
|
|
1675
2094
|
table[attr_name]
|
|
1676
2095
|
else
|
|
1677
|
-
Arel.sql(
|
|
2096
|
+
Arel.sql(adapter_class.quote_table_name(attr_name), retryable: true)
|
|
1678
2097
|
end
|
|
1679
2098
|
end
|
|
1680
2099
|
end
|
|
@@ -1739,6 +2158,41 @@ module ActiveRecord
|
|
|
1739
2158
|
end
|
|
1740
2159
|
end
|
|
1741
2160
|
|
|
2161
|
+
def process_select_args(fields)
|
|
2162
|
+
fields.flat_map do |field|
|
|
2163
|
+
if field.is_a?(Hash)
|
|
2164
|
+
arel_columns_from_hash(field)
|
|
2165
|
+
else
|
|
2166
|
+
field
|
|
2167
|
+
end
|
|
2168
|
+
end
|
|
2169
|
+
end
|
|
2170
|
+
|
|
2171
|
+
def arel_columns_from_hash(fields)
|
|
2172
|
+
fields.flat_map do |key, columns_aliases|
|
|
2173
|
+
case columns_aliases
|
|
2174
|
+
when Hash
|
|
2175
|
+
columns_aliases.map do |column, column_alias|
|
|
2176
|
+
if values[:joins]&.include?(key)
|
|
2177
|
+
references = PredicateBuilder.references({ key.to_s => fields[key] })
|
|
2178
|
+
self.references_values |= references unless references.empty?
|
|
2179
|
+
end
|
|
2180
|
+
arel_column("#{key}.#{column}") do
|
|
2181
|
+
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
|
2182
|
+
end.as(column_alias.to_s)
|
|
2183
|
+
end
|
|
2184
|
+
when Array
|
|
2185
|
+
columns_aliases.map do |column|
|
|
2186
|
+
arel_column("#{key}.#{column}", &:itself)
|
|
2187
|
+
end
|
|
2188
|
+
when String, Symbol
|
|
2189
|
+
arel_column(key.to_s) do
|
|
2190
|
+
predicate_builder.resolve_arel_attribute(klass.table_name, key.to_s)
|
|
2191
|
+
end.as(columns_aliases.to_s)
|
|
2192
|
+
end
|
|
2193
|
+
end
|
|
2194
|
+
end
|
|
2195
|
+
|
|
1742
2196
|
STRUCTURAL_VALUE_METHODS = (
|
|
1743
2197
|
Relation::VALUE_METHODS -
|
|
1744
2198
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|