activerecord 7.0.8.7 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +781 -1777
- data/MIT-LICENSE +1 -1
- data/README.rdoc +30 -30
- 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 +31 -23
- 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 +40 -9
- 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 +35 -21
- 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 +4 -3
- 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 +153 -33
- data/lib/active_record/attributes.rb +96 -71
- data/lib/active_record/autosave_association.rb +81 -39
- data/lib/active_record/base.rb +11 -7
- 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 +343 -91
- 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 +229 -64
- 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 +142 -12
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +539 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +289 -128
- 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 +60 -55
- 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 +108 -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 +153 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +371 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +374 -203
- 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 +57 -45
- 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 +51 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +298 -113
- 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 +101 -105
- data/lib/active_record/core.rb +273 -178
- data/lib/active_record/counter_cache.rb +69 -35
- data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -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 +56 -27
- 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 +46 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +48 -13
- data/lib/active_record/encryption/encryptor.rb +35 -19
- 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 +130 -28
- data/lib/active_record/errors.rb +154 -34
- 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 +48 -10
- data/lib/active_record/fixtures.rb +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- 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 +236 -118
- 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 +96 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +35 -10
- data/lib/active_record/railtie.rb +131 -87
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +147 -155
- 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 +270 -108
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +97 -21
- 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 +20 -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 +3 -2
- data/lib/active_record/relation/query_methods.rb +585 -109
- 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 +15 -21
- data/lib/active_record/relation.rb +592 -92
- 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 +90 -23
- 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 +33 -11
- 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 +23 -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 +108 -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 +3 -1
- 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/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- 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/delete_statement.rb +4 -2
- 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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -3
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -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 +114 -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 -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,104 @@ 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
|
+
args = process_with_args(args)
|
|
495
|
+
self.with_values |= args
|
|
496
|
+
self
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
500
|
+
#
|
|
501
|
+
# 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')])
|
|
502
|
+
# # => ActiveRecord::Relation
|
|
503
|
+
# # WITH post_and_replies AS (
|
|
504
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
|
505
|
+
# # UNION ALL
|
|
506
|
+
# # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
|
|
507
|
+
# # )
|
|
508
|
+
# # SELECT * FROM posts
|
|
509
|
+
#
|
|
510
|
+
# See `#with` for more information.
|
|
511
|
+
def with_recursive(*args)
|
|
512
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
513
|
+
spawn.with_recursive!(*args)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Like #with_recursive but modifies the relation in place.
|
|
517
|
+
def with_recursive!(*args) # :nodoc:
|
|
518
|
+
args = process_with_args(args)
|
|
519
|
+
self.with_values |= args
|
|
520
|
+
@with_is_recursive = true
|
|
521
|
+
self
|
|
522
|
+
end
|
|
523
|
+
|
|
330
524
|
# Allows you to change a previously set select statement.
|
|
331
525
|
#
|
|
332
526
|
# Post.select(:title, :body)
|
|
@@ -339,6 +533,7 @@ module ActiveRecord
|
|
|
339
533
|
# Note that we're unscoping the entire select statement.
|
|
340
534
|
def reselect(*args)
|
|
341
535
|
check_if_method_has_arguments!(__callee__, args)
|
|
536
|
+
args = process_select_args(args)
|
|
342
537
|
spawn.reselect!(*args)
|
|
343
538
|
end
|
|
344
539
|
|
|
@@ -378,6 +573,27 @@ module ActiveRecord
|
|
|
378
573
|
self
|
|
379
574
|
end
|
|
380
575
|
|
|
576
|
+
# Allows you to change a previously set group statement.
|
|
577
|
+
#
|
|
578
|
+
# Post.group(:title, :body)
|
|
579
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`, `posts`.`body`
|
|
580
|
+
#
|
|
581
|
+
# Post.group(:title, :body).regroup(:title)
|
|
582
|
+
# # SELECT `posts`.`*` FROM `posts` GROUP BY `posts`.`title`
|
|
583
|
+
#
|
|
584
|
+
# This is short-hand for <tt>unscope(:group).group(fields)</tt>.
|
|
585
|
+
# Note that we're unscoping the entire group statement.
|
|
586
|
+
def regroup(*args)
|
|
587
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
588
|
+
spawn.regroup!(*args)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Same as #regroup but operates on relation in-place instead of copying.
|
|
592
|
+
def regroup!(*args) # :nodoc:
|
|
593
|
+
self.group_values = args
|
|
594
|
+
self
|
|
595
|
+
end
|
|
596
|
+
|
|
381
597
|
# Applies an <code>ORDER BY</code> clause to a query.
|
|
382
598
|
#
|
|
383
599
|
# #order accepts arguments in one of several formats.
|
|
@@ -426,7 +642,7 @@ module ActiveRecord
|
|
|
426
642
|
# User.order(Arel.sql('end_date - start_date'))
|
|
427
643
|
# # SELECT "users".* FROM "users" ORDER BY end_date - start_date
|
|
428
644
|
#
|
|
429
|
-
# Custom query syntax, like JSON columns for
|
|
645
|
+
# Custom query syntax, like JSON columns for PostgreSQL, is supported in this way.
|
|
430
646
|
#
|
|
431
647
|
# User.order(Arel.sql("payload->>'kind'"))
|
|
432
648
|
# # SELECT "users".* FROM "users" ORDER BY payload->>'kind'
|
|
@@ -444,7 +660,8 @@ module ActiveRecord
|
|
|
444
660
|
self
|
|
445
661
|
end
|
|
446
662
|
|
|
447
|
-
#
|
|
663
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
|
664
|
+
# ordered and filtered by a specific set of +values+.
|
|
448
665
|
#
|
|
449
666
|
# User.in_order_of(:id, [1, 5, 3])
|
|
450
667
|
# # SELECT "users".* FROM "users"
|
|
@@ -455,15 +672,41 @@ module ActiveRecord
|
|
|
455
672
|
# # WHEN "users"."id" = 3 THEN 3
|
|
456
673
|
# # END ASC
|
|
457
674
|
#
|
|
675
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
|
676
|
+
# on the database adapter and the column definition.
|
|
677
|
+
#
|
|
678
|
+
# class Conversation < ActiveRecord::Base
|
|
679
|
+
# enum :status, [ :active, :archived ]
|
|
680
|
+
# end
|
|
681
|
+
#
|
|
682
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
|
683
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
684
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
|
685
|
+
# # ORDER BY CASE
|
|
686
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
|
687
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
|
688
|
+
# # END ASC
|
|
689
|
+
#
|
|
690
|
+
# +values+ can also include +nil+.
|
|
691
|
+
#
|
|
692
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
|
693
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
694
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
|
695
|
+
# # ORDER BY CASE
|
|
696
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
|
697
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
|
698
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
|
699
|
+
# # END ASC
|
|
700
|
+
#
|
|
458
701
|
def in_order_of(column, values)
|
|
459
|
-
klass.disallow_raw_sql!([column], permit:
|
|
702
|
+
klass.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
|
460
703
|
return spawn.none! if values.empty?
|
|
461
704
|
|
|
462
705
|
references = column_references([column])
|
|
463
706
|
self.references_values |= references unless references.empty?
|
|
464
707
|
|
|
465
708
|
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
|
466
|
-
arel_column = column.is_a?(
|
|
709
|
+
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
|
467
710
|
|
|
468
711
|
where_clause =
|
|
469
712
|
if values.include?(nil)
|
|
@@ -485,7 +728,7 @@ module ActiveRecord
|
|
|
485
728
|
#
|
|
486
729
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
|
487
730
|
#
|
|
488
|
-
# generates a query with
|
|
731
|
+
# generates a query with <tt>ORDER BY id ASC, name ASC</tt>.
|
|
489
732
|
def reorder(*args)
|
|
490
733
|
check_if_method_has_arguments!(__callee__, args) do
|
|
491
734
|
sanitize_order_arguments(args)
|
|
@@ -504,7 +747,8 @@ module ActiveRecord
|
|
|
504
747
|
|
|
505
748
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
|
506
749
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
|
507
|
-
:includes, :
|
|
750
|
+
:includes, :eager_load, :preload, :from, :readonly,
|
|
751
|
+
:having, :optimizer_hints, :with])
|
|
508
752
|
|
|
509
753
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
|
510
754
|
# This is useful when passing around chains of relations and would like to
|
|
@@ -554,7 +798,7 @@ module ActiveRecord
|
|
|
554
798
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
|
555
799
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
|
556
800
|
end
|
|
557
|
-
|
|
801
|
+
assert_modifiable!
|
|
558
802
|
@values.delete(scope)
|
|
559
803
|
when Hash
|
|
560
804
|
scope.each do |key, target_value|
|
|
@@ -614,7 +858,7 @@ module ActiveRecord
|
|
|
614
858
|
# Performs LEFT OUTER JOINs on +args+:
|
|
615
859
|
#
|
|
616
860
|
# User.left_outer_joins(:posts)
|
|
617
|
-
#
|
|
861
|
+
# # SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
|
618
862
|
#
|
|
619
863
|
def left_outer_joins(*args)
|
|
620
864
|
check_if_method_has_arguments!(__callee__, args)
|
|
@@ -634,7 +878,7 @@ module ActiveRecord
|
|
|
634
878
|
# SQL is given as an illustration; the actual query generated may be different depending
|
|
635
879
|
# on the database adapter.
|
|
636
880
|
#
|
|
637
|
-
# ===
|
|
881
|
+
# === \String
|
|
638
882
|
#
|
|
639
883
|
# A single string, without additional arguments, is passed to the query
|
|
640
884
|
# constructor as an SQL fragment, and used in the where clause of the query.
|
|
@@ -646,7 +890,7 @@ module ActiveRecord
|
|
|
646
890
|
# to injection attacks if not done properly. As an alternative, it is recommended
|
|
647
891
|
# to use one of the following methods.
|
|
648
892
|
#
|
|
649
|
-
# ===
|
|
893
|
+
# === \Array
|
|
650
894
|
#
|
|
651
895
|
# If an array is passed, then the first element of the array is treated as a template, and
|
|
652
896
|
# the remaining elements are inserted into the template to generate the condition.
|
|
@@ -686,7 +930,7 @@ module ActiveRecord
|
|
|
686
930
|
# dependencies on the underlying database. If your code is intended for general consumption,
|
|
687
931
|
# test with multiple database backends.
|
|
688
932
|
#
|
|
689
|
-
# ===
|
|
933
|
+
# === \Hash
|
|
690
934
|
#
|
|
691
935
|
# #where will also accept a hash condition, in which the keys are fields and the values
|
|
692
936
|
# are values to be searched for.
|
|
@@ -720,6 +964,12 @@ module ActiveRecord
|
|
|
720
964
|
# PriceEstimate.where(estimate_of: treasure)
|
|
721
965
|
# PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
|
|
722
966
|
#
|
|
967
|
+
# Hash conditions may also be specified in a tuple-like syntax. Hash keys may be
|
|
968
|
+
# an array of columns with an array of tuples as values.
|
|
969
|
+
#
|
|
970
|
+
# Article.where([:author_id, :id] => [[15, 1], [15, 2]])
|
|
971
|
+
# # SELECT * FROM articles WHERE author_id = 15 AND id = 1 OR author_id = 15 AND id = 2
|
|
972
|
+
#
|
|
723
973
|
# === Joins
|
|
724
974
|
#
|
|
725
975
|
# If the relation is the result of a join, you may create a condition which uses any of the
|
|
@@ -732,7 +982,7 @@ module ActiveRecord
|
|
|
732
982
|
# User.joins(:posts).where("posts.published" => true)
|
|
733
983
|
# User.joins(:posts).where(posts: { published: true })
|
|
734
984
|
#
|
|
735
|
-
# ===
|
|
985
|
+
# === No Argument
|
|
736
986
|
#
|
|
737
987
|
# If no argument is passed, #where returns a new instance of WhereChain, that
|
|
738
988
|
# can be chained with WhereChain#not, WhereChain#missing, or WhereChain#associated.
|
|
@@ -756,7 +1006,7 @@ module ActiveRecord
|
|
|
756
1006
|
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
757
1007
|
# # WHERE "authors"."id" IS NULL
|
|
758
1008
|
#
|
|
759
|
-
# ===
|
|
1009
|
+
# === Blank Condition
|
|
760
1010
|
#
|
|
761
1011
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
|
762
1012
|
# the current relation.
|
|
@@ -789,6 +1039,8 @@ module ActiveRecord
|
|
|
789
1039
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
|
790
1040
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
|
791
1041
|
def rewhere(conditions)
|
|
1042
|
+
return unscope(:where) if conditions.nil?
|
|
1043
|
+
|
|
792
1044
|
scope = spawn
|
|
793
1045
|
where_clause = scope.build_where_clause(conditions)
|
|
794
1046
|
|
|
@@ -894,7 +1146,11 @@ module ActiveRecord
|
|
|
894
1146
|
#
|
|
895
1147
|
def or(other)
|
|
896
1148
|
if other.is_a?(Relation)
|
|
897
|
-
|
|
1149
|
+
if @none
|
|
1150
|
+
other.spawn
|
|
1151
|
+
else
|
|
1152
|
+
spawn.or!(other)
|
|
1153
|
+
end
|
|
898
1154
|
else
|
|
899
1155
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
|
900
1156
|
end
|
|
@@ -907,7 +1163,7 @@ module ActiveRecord
|
|
|
907
1163
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
908
1164
|
end
|
|
909
1165
|
|
|
910
|
-
self.where_clause =
|
|
1166
|
+
self.where_clause = where_clause.or(other.where_clause)
|
|
911
1167
|
self.having_clause = having_clause.or(other.having_clause)
|
|
912
1168
|
self.references_values |= other.references_values
|
|
913
1169
|
|
|
@@ -1007,15 +1263,29 @@ module ActiveRecord
|
|
|
1007
1263
|
end
|
|
1008
1264
|
|
|
1009
1265
|
def none! # :nodoc:
|
|
1010
|
-
|
|
1266
|
+
unless @none
|
|
1267
|
+
where!("1=0")
|
|
1268
|
+
@none = true
|
|
1269
|
+
end
|
|
1270
|
+
self
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
def null_relation? # :nodoc:
|
|
1274
|
+
@none
|
|
1011
1275
|
end
|
|
1012
1276
|
|
|
1013
|
-
#
|
|
1014
|
-
#
|
|
1277
|
+
# Mark a relation as readonly. Attempting to update a record will result in
|
|
1278
|
+
# an error.
|
|
1015
1279
|
#
|
|
1016
1280
|
# users = User.readonly
|
|
1017
1281
|
# users.first.save
|
|
1018
|
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1282
|
+
# # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1283
|
+
#
|
|
1284
|
+
# To make a readonly relation writable, pass +false+.
|
|
1285
|
+
#
|
|
1286
|
+
# users.readonly(false)
|
|
1287
|
+
# users.first.save
|
|
1288
|
+
# # => true
|
|
1019
1289
|
def readonly(value = true)
|
|
1020
1290
|
spawn.readonly!(value)
|
|
1021
1291
|
end
|
|
@@ -1030,7 +1300,7 @@ module ActiveRecord
|
|
|
1030
1300
|
#
|
|
1031
1301
|
# user = User.strict_loading.first
|
|
1032
1302
|
# user.comments.to_a
|
|
1033
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
1303
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
1034
1304
|
def strict_loading(value = true)
|
|
1035
1305
|
spawn.strict_loading!(value)
|
|
1036
1306
|
end
|
|
@@ -1132,7 +1402,7 @@ module ActiveRecord
|
|
|
1132
1402
|
#
|
|
1133
1403
|
# The object returned is a relation, which can be further extended.
|
|
1134
1404
|
#
|
|
1135
|
-
# === Using a
|
|
1405
|
+
# === Using a \Module
|
|
1136
1406
|
#
|
|
1137
1407
|
# module Pagination
|
|
1138
1408
|
# def page(number)
|
|
@@ -1147,7 +1417,7 @@ module ActiveRecord
|
|
|
1147
1417
|
#
|
|
1148
1418
|
# scope = Model.all.extending(Pagination, SomethingElse)
|
|
1149
1419
|
#
|
|
1150
|
-
# === Using a
|
|
1420
|
+
# === Using a Block
|
|
1151
1421
|
#
|
|
1152
1422
|
# scope = Model.all.extending do
|
|
1153
1423
|
# def page(number)
|
|
@@ -1264,6 +1534,9 @@ module ActiveRecord
|
|
|
1264
1534
|
# Post.excluding(post_one, post_two)
|
|
1265
1535
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
|
1266
1536
|
#
|
|
1537
|
+
# Post.excluding(Post.drafts)
|
|
1538
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
|
1539
|
+
#
|
|
1267
1540
|
# This can also be called on associations. As with the above example, either
|
|
1268
1541
|
# a single record of collection thereof may be specified:
|
|
1269
1542
|
#
|
|
@@ -1279,14 +1552,15 @@ module ActiveRecord
|
|
|
1279
1552
|
# is passed in) are not instances of the same model that the relation is
|
|
1280
1553
|
# scoping.
|
|
1281
1554
|
def excluding(*records)
|
|
1555
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
|
1282
1556
|
records.flatten!(1)
|
|
1283
1557
|
records.compact!
|
|
1284
1558
|
|
|
1285
|
-
unless records.all?(klass)
|
|
1559
|
+
unless records.all?(klass) && relations.all? { |relation| relation.klass == klass }
|
|
1286
1560
|
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to ##{__callee__}."
|
|
1287
1561
|
end
|
|
1288
1562
|
|
|
1289
|
-
spawn.excluding!(records)
|
|
1563
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
|
1290
1564
|
end
|
|
1291
1565
|
alias :without :excluding
|
|
1292
1566
|
|
|
@@ -1298,7 +1572,7 @@ module ActiveRecord
|
|
|
1298
1572
|
|
|
1299
1573
|
# Returns the Arel object associated with the relation.
|
|
1300
1574
|
def arel(aliases = nil) # :nodoc:
|
|
1301
|
-
@arel ||= build_arel(aliases)
|
|
1575
|
+
@arel ||= with_connection { |c| build_arel(c, aliases) }
|
|
1302
1576
|
end
|
|
1303
1577
|
|
|
1304
1578
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
|
@@ -1319,13 +1593,29 @@ module ActiveRecord
|
|
|
1319
1593
|
def build_where_clause(opts, rest = []) # :nodoc:
|
|
1320
1594
|
opts = sanitize_forbidden_attributes(opts)
|
|
1321
1595
|
|
|
1596
|
+
if opts.is_a?(Array)
|
|
1597
|
+
opts, *rest = opts
|
|
1598
|
+
end
|
|
1599
|
+
|
|
1322
1600
|
case opts
|
|
1323
|
-
when String
|
|
1324
|
-
|
|
1601
|
+
when String
|
|
1602
|
+
if rest.empty?
|
|
1603
|
+
parts = [Arel.sql(opts)]
|
|
1604
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
|
1605
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
|
1606
|
+
elsif opts.include?("?")
|
|
1607
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
|
1608
|
+
else
|
|
1609
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
|
1610
|
+
end
|
|
1325
1611
|
when Hash
|
|
1326
1612
|
opts = opts.transform_keys do |key|
|
|
1327
|
-
|
|
1328
|
-
|
|
1613
|
+
if key.is_a?(Array)
|
|
1614
|
+
key.map { |k| klass.attribute_aliases[k.to_s] || k.to_s }
|
|
1615
|
+
else
|
|
1616
|
+
key = key.to_s
|
|
1617
|
+
klass.attribute_aliases[key] || key
|
|
1618
|
+
end
|
|
1329
1619
|
end
|
|
1330
1620
|
references = PredicateBuilder.references(opts)
|
|
1331
1621
|
self.references_values |= references unless references.empty?
|
|
@@ -1343,7 +1633,76 @@ module ActiveRecord
|
|
|
1343
1633
|
end
|
|
1344
1634
|
alias :build_having_clause :build_where_clause
|
|
1345
1635
|
|
|
1636
|
+
def async!
|
|
1637
|
+
@async = true
|
|
1638
|
+
self
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
protected
|
|
1642
|
+
def arel_columns(columns)
|
|
1643
|
+
columns.flat_map do |field|
|
|
1644
|
+
case field
|
|
1645
|
+
when Symbol
|
|
1646
|
+
arel_column(field.to_s) do |attr_name|
|
|
1647
|
+
adapter_class.quote_table_name(attr_name)
|
|
1648
|
+
end
|
|
1649
|
+
when String
|
|
1650
|
+
arel_column(field, &:itself)
|
|
1651
|
+
when Proc
|
|
1652
|
+
field.call
|
|
1653
|
+
when Hash
|
|
1654
|
+
arel_columns_from_hash(field)
|
|
1655
|
+
else
|
|
1656
|
+
field
|
|
1657
|
+
end
|
|
1658
|
+
end
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1346
1661
|
private
|
|
1662
|
+
def async
|
|
1663
|
+
spawn.async!
|
|
1664
|
+
end
|
|
1665
|
+
|
|
1666
|
+
def build_named_bound_sql_literal(statement, values)
|
|
1667
|
+
bound_values = values.transform_values do |value|
|
|
1668
|
+
if ActiveRecord::Relation === value
|
|
1669
|
+
Arel.sql(value.to_sql)
|
|
1670
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1671
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1672
|
+
values.empty? ? nil : values
|
|
1673
|
+
else
|
|
1674
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1675
|
+
value
|
|
1676
|
+
end
|
|
1677
|
+
end
|
|
1678
|
+
|
|
1679
|
+
begin
|
|
1680
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
|
1681
|
+
rescue Arel::BindError => error
|
|
1682
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1683
|
+
end
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
def build_bound_sql_literal(statement, values)
|
|
1687
|
+
bound_values = values.map do |value|
|
|
1688
|
+
if ActiveRecord::Relation === value
|
|
1689
|
+
Arel.sql(value.to_sql)
|
|
1690
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1691
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1692
|
+
values.empty? ? nil : values
|
|
1693
|
+
else
|
|
1694
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1695
|
+
value
|
|
1696
|
+
end
|
|
1697
|
+
end
|
|
1698
|
+
|
|
1699
|
+
begin
|
|
1700
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
|
1701
|
+
rescue Arel::BindError => error
|
|
1702
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1703
|
+
end
|
|
1704
|
+
end
|
|
1705
|
+
|
|
1347
1706
|
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1348
1707
|
each_join_dependencies do |join|
|
|
1349
1708
|
return join.base_klass if table_name == join.table_name
|
|
@@ -1358,22 +1717,21 @@ module ActiveRecord
|
|
|
1358
1717
|
end
|
|
1359
1718
|
|
|
1360
1719
|
def build_join_dependencies
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1720
|
+
joins = joins_values | left_outer_joins_values
|
|
1721
|
+
joins |= eager_load_values unless eager_load_values.empty?
|
|
1722
|
+
joins |= includes_values unless includes_values.empty?
|
|
1364
1723
|
|
|
1365
1724
|
join_dependencies = []
|
|
1366
1725
|
join_dependencies.unshift construct_join_dependency(
|
|
1367
|
-
|
|
1726
|
+
select_named_joins(joins, join_dependencies), nil
|
|
1368
1727
|
)
|
|
1369
1728
|
end
|
|
1370
1729
|
|
|
1371
|
-
def
|
|
1372
|
-
raise
|
|
1373
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1730
|
+
def assert_modifiable!
|
|
1731
|
+
raise UnmodifiableRelation if @loaded || @arel
|
|
1374
1732
|
end
|
|
1375
1733
|
|
|
1376
|
-
def build_arel(aliases = nil)
|
|
1734
|
+
def build_arel(connection, aliases = nil)
|
|
1377
1735
|
arel = Arel::SelectManager.new(table)
|
|
1378
1736
|
|
|
1379
1737
|
build_joins(arel.join_sources, aliases)
|
|
@@ -1385,6 +1743,7 @@ module ActiveRecord
|
|
|
1385
1743
|
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
|
1386
1744
|
|
|
1387
1745
|
build_order(arel)
|
|
1746
|
+
build_with(arel)
|
|
1388
1747
|
build_select(arel)
|
|
1389
1748
|
|
|
1390
1749
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
@@ -1420,6 +1779,18 @@ module ActiveRecord
|
|
|
1420
1779
|
end
|
|
1421
1780
|
end
|
|
1422
1781
|
|
|
1782
|
+
def select_named_joins(join_names, stashed_joins = nil, &block)
|
|
1783
|
+
cte_joins, associations = join_names.partition do |join_name|
|
|
1784
|
+
Symbol === join_name && with_values.any? { _1.key?(join_name) }
|
|
1785
|
+
end
|
|
1786
|
+
|
|
1787
|
+
cte_joins.each do |cte_name|
|
|
1788
|
+
block&.call(CTEJoin.new(cte_name))
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1791
|
+
select_association_list(associations, stashed_joins, &block)
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1423
1794
|
def select_association_list(associations, stashed_joins = nil)
|
|
1424
1795
|
result = []
|
|
1425
1796
|
associations.each do |association|
|
|
@@ -1435,20 +1806,21 @@ module ActiveRecord
|
|
|
1435
1806
|
result
|
|
1436
1807
|
end
|
|
1437
1808
|
|
|
1438
|
-
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
|
1439
|
-
end
|
|
1440
|
-
|
|
1441
1809
|
def build_join_buckets
|
|
1442
1810
|
buckets = Hash.new { |h, k| h[k] = [] }
|
|
1443
1811
|
|
|
1444
1812
|
unless left_outer_joins_values.empty?
|
|
1445
1813
|
stashed_left_joins = []
|
|
1446
|
-
left_joins =
|
|
1447
|
-
|
|
1814
|
+
left_joins = select_named_joins(left_outer_joins_values, stashed_left_joins) do |left_join|
|
|
1815
|
+
if left_join.is_a?(CTEJoin)
|
|
1816
|
+
buckets[:join_node] << build_with_join_node(left_join.name, Arel::Nodes::OuterJoin)
|
|
1817
|
+
else
|
|
1818
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
|
1819
|
+
end
|
|
1448
1820
|
end
|
|
1449
1821
|
|
|
1450
1822
|
if joins_values.empty?
|
|
1451
|
-
buckets[:
|
|
1823
|
+
buckets[:named_join] = left_joins
|
|
1452
1824
|
buckets[:stashed_join] = stashed_left_joins
|
|
1453
1825
|
return buckets, Arel::Nodes::OuterJoin
|
|
1454
1826
|
else
|
|
@@ -1474,9 +1846,11 @@ module ActiveRecord
|
|
|
1474
1846
|
end
|
|
1475
1847
|
end
|
|
1476
1848
|
|
|
1477
|
-
buckets[:
|
|
1849
|
+
buckets[:named_join] = select_named_joins(joins, buckets[:stashed_join]) do |join|
|
|
1478
1850
|
if join.is_a?(Arel::Nodes::Join)
|
|
1479
1851
|
buckets[:join_node] << join
|
|
1852
|
+
elsif join.is_a?(CTEJoin)
|
|
1853
|
+
buckets[:join_node] << build_with_join_node(join.name)
|
|
1480
1854
|
else
|
|
1481
1855
|
raise "unknown class: %s" % join.class.name
|
|
1482
1856
|
end
|
|
@@ -1493,16 +1867,16 @@ module ActiveRecord
|
|
|
1493
1867
|
|
|
1494
1868
|
buckets, join_type = build_join_buckets
|
|
1495
1869
|
|
|
1496
|
-
|
|
1497
|
-
stashed_joins
|
|
1498
|
-
leading_joins
|
|
1499
|
-
join_nodes
|
|
1870
|
+
named_joins = buckets[:named_join]
|
|
1871
|
+
stashed_joins = buckets[:stashed_join]
|
|
1872
|
+
leading_joins = buckets[:leading_join]
|
|
1873
|
+
join_nodes = buckets[:join_node]
|
|
1500
1874
|
|
|
1501
1875
|
join_sources.concat(leading_joins) unless leading_joins.empty?
|
|
1502
1876
|
|
|
1503
|
-
unless
|
|
1877
|
+
unless named_joins.empty? && stashed_joins.empty?
|
|
1504
1878
|
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
|
1505
|
-
join_dependency = construct_join_dependency(
|
|
1879
|
+
join_dependency = construct_join_dependency(named_joins, join_type)
|
|
1506
1880
|
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
|
1507
1881
|
end
|
|
1508
1882
|
|
|
@@ -1520,31 +1894,63 @@ module ActiveRecord
|
|
|
1520
1894
|
end
|
|
1521
1895
|
end
|
|
1522
1896
|
|
|
1523
|
-
def
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1897
|
+
def build_with(arel)
|
|
1898
|
+
return if with_values.empty?
|
|
1899
|
+
|
|
1900
|
+
with_statements = with_values.map do |with_value|
|
|
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
|
|
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)
|
|
1536
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.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
|
|
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,48 @@ 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
|
+
|
|
2211
|
+
def process_with_args(args)
|
|
2212
|
+
args.flat_map do |arg|
|
|
2213
|
+
raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
|
|
2214
|
+
arg.map { |k, v| { k => v } }
|
|
2215
|
+
end
|
|
2216
|
+
end
|
|
2217
|
+
|
|
1742
2218
|
STRUCTURAL_VALUE_METHODS = (
|
|
1743
2219
|
Relation::VALUE_METHODS -
|
|
1744
2220
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|