activerecord 7.1.5.2 → 8.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- 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 +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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 +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -72,14 +72,31 @@ module ActiveRecord
|
|
|
72
72
|
# # INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
|
73
73
|
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
|
74
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
|
|
75
88
|
def associated(*associations)
|
|
76
89
|
associations.each do |association|
|
|
77
90
|
reflection = scope_association_reflection(association)
|
|
78
|
-
@scope.
|
|
91
|
+
unless @scope.joins_values.include?(reflection.name) || @scope.left_outer_joins_values.include?(reflection.name)
|
|
92
|
+
@scope.joins!(association)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
association_conditions = Array(reflection.association_primary_key).index_with(nil)
|
|
79
96
|
if reflection.options[:class_name]
|
|
80
|
-
self.not(association =>
|
|
97
|
+
self.not(association => association_conditions)
|
|
81
98
|
else
|
|
82
|
-
self.not(reflection.table_name =>
|
|
99
|
+
self.not(reflection.table_name => association_conditions)
|
|
83
100
|
end
|
|
84
101
|
end
|
|
85
102
|
|
|
@@ -108,10 +125,11 @@ module ActiveRecord
|
|
|
108
125
|
associations.each do |association|
|
|
109
126
|
reflection = scope_association_reflection(association)
|
|
110
127
|
@scope.left_outer_joins!(association)
|
|
128
|
+
association_conditions = Array(reflection.association_primary_key).index_with(nil)
|
|
111
129
|
if reflection.options[:class_name]
|
|
112
|
-
@scope.where!(association =>
|
|
130
|
+
@scope.where!(association => association_conditions)
|
|
113
131
|
else
|
|
114
|
-
@scope.where!(reflection.table_name =>
|
|
132
|
+
@scope.where!(reflection.table_name => association_conditions)
|
|
115
133
|
end
|
|
116
134
|
end
|
|
117
135
|
|
|
@@ -120,9 +138,10 @@ module ActiveRecord
|
|
|
120
138
|
|
|
121
139
|
private
|
|
122
140
|
def scope_association_reflection(association)
|
|
123
|
-
|
|
141
|
+
model = @scope.model
|
|
142
|
+
reflection = model._reflect_on_association(association)
|
|
124
143
|
unless reflection
|
|
125
|
-
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{
|
|
144
|
+
raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{model.name}`.")
|
|
126
145
|
end
|
|
127
146
|
reflection
|
|
128
147
|
end
|
|
@@ -157,7 +176,7 @@ module ActiveRecord
|
|
|
157
176
|
end # end
|
|
158
177
|
|
|
159
178
|
def #{method_name}=(value) # def includes_values=(value)
|
|
160
|
-
|
|
179
|
+
assert_modifiable! # assert_modifiable!
|
|
161
180
|
@values[:#{name}] = value # @values[:includes] = value
|
|
162
181
|
end # end
|
|
163
182
|
CODE
|
|
@@ -238,6 +257,10 @@ module ActiveRecord
|
|
|
238
257
|
self
|
|
239
258
|
end
|
|
240
259
|
|
|
260
|
+
def all # :nodoc:
|
|
261
|
+
spawn
|
|
262
|
+
end
|
|
263
|
+
|
|
241
264
|
# Specify associations +args+ to be eager loaded using a <tt>LEFT OUTER JOIN</tt>.
|
|
242
265
|
# Performs a single query joining all specified associations. For example:
|
|
243
266
|
#
|
|
@@ -419,6 +442,17 @@ module ActiveRecord
|
|
|
419
442
|
# # )
|
|
420
443
|
# # SELECT * FROM posts
|
|
421
444
|
#
|
|
445
|
+
# You can also pass an array of sub-queries to be joined in a +UNION ALL+.
|
|
446
|
+
#
|
|
447
|
+
# Post.with(posts_with_tags_or_comments: [Post.where("tags_count > ?", 0), Post.where("comments_count > ?", 0)])
|
|
448
|
+
# # => ActiveRecord::Relation
|
|
449
|
+
# # WITH posts_with_tags_or_comments AS (
|
|
450
|
+
# # (SELECT * FROM posts WHERE (tags_count > 0))
|
|
451
|
+
# # UNION ALL
|
|
452
|
+
# # (SELECT * FROM posts WHERE (comments_count > 0))
|
|
453
|
+
# # )
|
|
454
|
+
# # SELECT * FROM posts
|
|
455
|
+
#
|
|
422
456
|
# Once you define Common Table Expression you can use custom +FROM+ value or +JOIN+ to reference it.
|
|
423
457
|
#
|
|
424
458
|
# Post.with(posts_with_tags: Post.where("tags_count > ?", 0)).from("posts_with_tags AS posts")
|
|
@@ -457,13 +491,40 @@ module ActiveRecord
|
|
|
457
491
|
# .with(posts_with_comments: Post.where("comments_count > ?", 0))
|
|
458
492
|
# .with(posts_with_tags: Post.where("tags_count > ?", 0))
|
|
459
493
|
def with(*args)
|
|
494
|
+
raise ArgumentError, "ActiveRecord::Relation#with does not accept a block" if block_given?
|
|
460
495
|
check_if_method_has_arguments!(__callee__, args)
|
|
461
496
|
spawn.with!(*args)
|
|
462
497
|
end
|
|
463
498
|
|
|
464
499
|
# Like #with, but modifies relation in place.
|
|
465
500
|
def with!(*args) # :nodoc:
|
|
466
|
-
|
|
501
|
+
args = process_with_args(args)
|
|
502
|
+
self.with_values |= args
|
|
503
|
+
self
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Add a recursive Common Table Expression (CTE) that you can then reference within another SELECT statement.
|
|
507
|
+
#
|
|
508
|
+
# 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')])
|
|
509
|
+
# # => ActiveRecord::Relation
|
|
510
|
+
# # WITH RECURSIVE post_and_replies AS (
|
|
511
|
+
# # (SELECT * FROM posts WHERE id = 42)
|
|
512
|
+
# # UNION ALL
|
|
513
|
+
# # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
|
|
514
|
+
# # )
|
|
515
|
+
# # SELECT * FROM posts
|
|
516
|
+
#
|
|
517
|
+
# See `#with` for more information.
|
|
518
|
+
def with_recursive(*args)
|
|
519
|
+
check_if_method_has_arguments!(__callee__, args)
|
|
520
|
+
spawn.with_recursive!(*args)
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Like #with_recursive but modifies the relation in place.
|
|
524
|
+
def with_recursive!(*args) # :nodoc:
|
|
525
|
+
args = process_with_args(args)
|
|
526
|
+
self.with_values |= args
|
|
527
|
+
@with_is_recursive = true
|
|
467
528
|
self
|
|
468
529
|
end
|
|
469
530
|
|
|
@@ -515,7 +576,7 @@ module ActiveRecord
|
|
|
515
576
|
end
|
|
516
577
|
|
|
517
578
|
def group!(*args) # :nodoc:
|
|
518
|
-
self.group_values
|
|
579
|
+
self.group_values |= args
|
|
519
580
|
self
|
|
520
581
|
end
|
|
521
582
|
|
|
@@ -606,7 +667,8 @@ module ActiveRecord
|
|
|
606
667
|
self
|
|
607
668
|
end
|
|
608
669
|
|
|
609
|
-
#
|
|
670
|
+
# Applies an <tt>ORDER BY</tt> clause based on a given +column+,
|
|
671
|
+
# ordered and filtered by a specific set of +values+.
|
|
610
672
|
#
|
|
611
673
|
# User.in_order_of(:id, [1, 5, 3])
|
|
612
674
|
# # SELECT "users".* FROM "users"
|
|
@@ -617,26 +679,65 @@ module ActiveRecord
|
|
|
617
679
|
# # WHEN "users"."id" = 3 THEN 3
|
|
618
680
|
# # END ASC
|
|
619
681
|
#
|
|
620
|
-
|
|
621
|
-
|
|
682
|
+
# +column+ can point to an enum column; the actual query generated may be different depending
|
|
683
|
+
# on the database adapter and the column definition.
|
|
684
|
+
#
|
|
685
|
+
# class Conversation < ActiveRecord::Base
|
|
686
|
+
# enum :status, [ :active, :archived ]
|
|
687
|
+
# end
|
|
688
|
+
#
|
|
689
|
+
# Conversation.in_order_of(:status, [:archived, :active])
|
|
690
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
691
|
+
# # WHERE "conversations"."status" IN (1, 0)
|
|
692
|
+
# # ORDER BY CASE
|
|
693
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
|
694
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
|
695
|
+
# # END ASC
|
|
696
|
+
#
|
|
697
|
+
# +values+ can also include +nil+.
|
|
698
|
+
#
|
|
699
|
+
# Conversation.in_order_of(:status, [nil, :archived, :active])
|
|
700
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
701
|
+
# # WHERE ("conversations"."status" IN (1, 0) OR "conversations"."status" IS NULL)
|
|
702
|
+
# # ORDER BY CASE
|
|
703
|
+
# # WHEN "conversations"."status" IS NULL THEN 1
|
|
704
|
+
# # WHEN "conversations"."status" = 1 THEN 2
|
|
705
|
+
# # WHEN "conversations"."status" = 0 THEN 3
|
|
706
|
+
# # END ASC
|
|
707
|
+
#
|
|
708
|
+
# +filter+ can be set to +false+ to include all results instead of only the ones specified in +values+.
|
|
709
|
+
#
|
|
710
|
+
# Conversation.in_order_of(:status, [:archived, :active], filter: false)
|
|
711
|
+
# # SELECT "conversations".* FROM "conversations"
|
|
712
|
+
# # ORDER BY CASE
|
|
713
|
+
# # WHEN "conversations"."status" = 1 THEN 1
|
|
714
|
+
# # WHEN "conversations"."status" = 0 THEN 2
|
|
715
|
+
# # ELSE 3
|
|
716
|
+
# # END ASC
|
|
717
|
+
def in_order_of(column, values, filter: true)
|
|
718
|
+
model.disallow_raw_sql!([column], permit: model.adapter_class.column_name_with_order_matcher)
|
|
622
719
|
return spawn.none! if values.empty?
|
|
623
720
|
|
|
624
721
|
references = column_references([column])
|
|
625
722
|
self.references_values |= references unless references.empty?
|
|
626
723
|
|
|
627
|
-
values = values.map { |value| type_caster.type_cast_for_database(column, value) }
|
|
724
|
+
values = values.map { |value| model.type_caster.type_cast_for_database(column, value) }
|
|
628
725
|
arel_column = column.is_a?(Arel::Nodes::SqlLiteral) ? column : order_column(column.to_s)
|
|
629
726
|
|
|
630
|
-
|
|
631
|
-
if values.include?(nil)
|
|
632
|
-
arel_column.in(values.compact).or(arel_column.eq(nil))
|
|
633
|
-
else
|
|
634
|
-
arel_column.in(values)
|
|
635
|
-
end
|
|
727
|
+
scope = spawn.order!(build_case_for_value_position(arel_column, values, filter: filter))
|
|
636
728
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
729
|
+
if filter
|
|
730
|
+
where_clause =
|
|
731
|
+
if values.include?(nil)
|
|
732
|
+
arel_column.in(values.compact).or(arel_column.eq(nil))
|
|
733
|
+
else
|
|
734
|
+
arel_column.in(values)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
scope = scope.where!(where_clause)
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
scope
|
|
640
741
|
end
|
|
641
742
|
|
|
642
743
|
# Replaces any existing order defined on the relation with the specified order.
|
|
@@ -667,7 +768,7 @@ module ActiveRecord
|
|
|
667
768
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
|
668
769
|
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
|
669
770
|
:includes, :eager_load, :preload, :from, :readonly,
|
|
670
|
-
:having, :optimizer_hints])
|
|
771
|
+
:having, :optimizer_hints, :with])
|
|
671
772
|
|
|
672
773
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
|
673
774
|
# This is useful when passing around chains of relations and would like to
|
|
@@ -708,7 +809,7 @@ module ActiveRecord
|
|
|
708
809
|
end
|
|
709
810
|
|
|
710
811
|
def unscope!(*args) # :nodoc:
|
|
711
|
-
self.unscope_values
|
|
812
|
+
self.unscope_values |= args
|
|
712
813
|
|
|
713
814
|
args.each do |scope|
|
|
714
815
|
case scope
|
|
@@ -717,7 +818,7 @@ module ActiveRecord
|
|
|
717
818
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
|
718
819
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
|
719
820
|
end
|
|
720
|
-
|
|
821
|
+
assert_modifiable!
|
|
721
822
|
@values.delete(scope)
|
|
722
823
|
when Hash
|
|
723
824
|
scope.each do |key, target_value|
|
|
@@ -1082,7 +1183,7 @@ module ActiveRecord
|
|
|
1082
1183
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
|
1083
1184
|
end
|
|
1084
1185
|
|
|
1085
|
-
self.where_clause =
|
|
1186
|
+
self.where_clause = where_clause.or(other.where_clause)
|
|
1086
1187
|
self.having_clause = having_clause.or(other.having_clause)
|
|
1087
1188
|
self.references_values |= other.references_values
|
|
1088
1189
|
|
|
@@ -1112,6 +1213,7 @@ module ActiveRecord
|
|
|
1112
1213
|
end
|
|
1113
1214
|
|
|
1114
1215
|
def limit!(value) # :nodoc:
|
|
1216
|
+
value = Integer(value) unless value.nil?
|
|
1115
1217
|
self.limit_value = value
|
|
1116
1218
|
self
|
|
1117
1219
|
end
|
|
@@ -1198,13 +1300,13 @@ module ActiveRecord
|
|
|
1198
1300
|
#
|
|
1199
1301
|
# users = User.readonly
|
|
1200
1302
|
# users.first.save
|
|
1201
|
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1303
|
+
# # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
|
1202
1304
|
#
|
|
1203
1305
|
# To make a readonly relation writable, pass +false+.
|
|
1204
1306
|
#
|
|
1205
1307
|
# users.readonly(false)
|
|
1206
1308
|
# users.first.save
|
|
1207
|
-
# => true
|
|
1309
|
+
# # => true
|
|
1208
1310
|
def readonly(value = true)
|
|
1209
1311
|
spawn.readonly!(value)
|
|
1210
1312
|
end
|
|
@@ -1219,7 +1321,7 @@ module ActiveRecord
|
|
|
1219
1321
|
#
|
|
1220
1322
|
# user = User.strict_loading.first
|
|
1221
1323
|
# user.comments.to_a
|
|
1222
|
-
# => ActiveRecord::StrictLoadingViolationError
|
|
1324
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
|
1223
1325
|
def strict_loading(value = true)
|
|
1224
1326
|
spawn.strict_loading!(value)
|
|
1225
1327
|
end
|
|
@@ -1364,7 +1466,7 @@ module ActiveRecord
|
|
|
1364
1466
|
modules << Module.new(&block) if block
|
|
1365
1467
|
modules.flatten!
|
|
1366
1468
|
|
|
1367
|
-
self.extending_values
|
|
1469
|
+
self.extending_values |= modules
|
|
1368
1470
|
extend(*extending_values) if extending_values.any?
|
|
1369
1471
|
|
|
1370
1472
|
self
|
|
@@ -1432,7 +1534,7 @@ module ActiveRecord
|
|
|
1432
1534
|
|
|
1433
1535
|
# Like #annotate, but modifies relation in place.
|
|
1434
1536
|
def annotate!(*args) # :nodoc:
|
|
1435
|
-
self.annotate_values
|
|
1537
|
+
self.annotate_values |= args
|
|
1436
1538
|
self
|
|
1437
1539
|
end
|
|
1438
1540
|
|
|
@@ -1453,6 +1555,9 @@ module ActiveRecord
|
|
|
1453
1555
|
# Post.excluding(post_one, post_two)
|
|
1454
1556
|
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
|
1455
1557
|
#
|
|
1558
|
+
# Post.excluding(Post.drafts)
|
|
1559
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (3, 4, 5)
|
|
1560
|
+
#
|
|
1456
1561
|
# This can also be called on associations. As with the above example, either
|
|
1457
1562
|
# a single record of collection thereof may be specified:
|
|
1458
1563
|
#
|
|
@@ -1468,14 +1573,15 @@ module ActiveRecord
|
|
|
1468
1573
|
# is passed in) are not instances of the same model that the relation is
|
|
1469
1574
|
# scoping.
|
|
1470
1575
|
def excluding(*records)
|
|
1576
|
+
relations = records.extract! { |element| element.is_a?(Relation) }
|
|
1471
1577
|
records.flatten!(1)
|
|
1472
1578
|
records.compact!
|
|
1473
1579
|
|
|
1474
|
-
unless records.all?(
|
|
1475
|
-
raise ArgumentError, "You must only pass a single or collection of #{
|
|
1580
|
+
unless records.all?(model) && relations.all? { |relation| relation.model == model }
|
|
1581
|
+
raise ArgumentError, "You must only pass a single or collection of #{model.name} objects to ##{__callee__}."
|
|
1476
1582
|
end
|
|
1477
1583
|
|
|
1478
|
-
spawn.excluding!(records)
|
|
1584
|
+
spawn.excluding!(records + relations.flat_map(&:ids))
|
|
1479
1585
|
end
|
|
1480
1586
|
alias :without :excluding
|
|
1481
1587
|
|
|
@@ -1492,7 +1598,7 @@ module ActiveRecord
|
|
|
1492
1598
|
|
|
1493
1599
|
def construct_join_dependency(associations, join_type) # :nodoc:
|
|
1494
1600
|
ActiveRecord::Associations::JoinDependency.new(
|
|
1495
|
-
|
|
1601
|
+
model, table, associations, join_type
|
|
1496
1602
|
)
|
|
1497
1603
|
end
|
|
1498
1604
|
|
|
@@ -1508,16 +1614,28 @@ module ActiveRecord
|
|
|
1508
1614
|
def build_where_clause(opts, rest = []) # :nodoc:
|
|
1509
1615
|
opts = sanitize_forbidden_attributes(opts)
|
|
1510
1616
|
|
|
1617
|
+
if opts.is_a?(Array)
|
|
1618
|
+
opts, *rest = opts
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1511
1621
|
case opts
|
|
1512
|
-
when String
|
|
1513
|
-
|
|
1622
|
+
when String
|
|
1623
|
+
if rest.empty?
|
|
1624
|
+
parts = [Arel.sql(opts)]
|
|
1625
|
+
elsif rest.first.is_a?(Hash) && /:\w+/.match?(opts)
|
|
1626
|
+
parts = [build_named_bound_sql_literal(opts, rest.first)]
|
|
1627
|
+
elsif opts.include?("?")
|
|
1628
|
+
parts = [build_bound_sql_literal(opts, rest)]
|
|
1629
|
+
else
|
|
1630
|
+
parts = [Arel.sql(model.sanitize_sql([opts, *rest]))]
|
|
1631
|
+
end
|
|
1514
1632
|
when Hash
|
|
1515
1633
|
opts = opts.transform_keys do |key|
|
|
1516
1634
|
if key.is_a?(Array)
|
|
1517
|
-
key.map { |k|
|
|
1635
|
+
key.map { |k| model.attribute_aliases[k.to_s] || k.to_s }
|
|
1518
1636
|
else
|
|
1519
1637
|
key = key.to_s
|
|
1520
|
-
|
|
1638
|
+
model.attribute_aliases[key] || key
|
|
1521
1639
|
end
|
|
1522
1640
|
end
|
|
1523
1641
|
references = PredicateBuilder.references(opts)
|
|
@@ -1536,16 +1654,71 @@ module ActiveRecord
|
|
|
1536
1654
|
end
|
|
1537
1655
|
alias :build_having_clause :build_where_clause
|
|
1538
1656
|
|
|
1539
|
-
def async!
|
|
1657
|
+
def async! # :nodoc:
|
|
1540
1658
|
@async = true
|
|
1541
1659
|
self
|
|
1542
1660
|
end
|
|
1543
1661
|
|
|
1662
|
+
def arel_columns(columns) # :nodoc:
|
|
1663
|
+
columns.flat_map do |field|
|
|
1664
|
+
case field
|
|
1665
|
+
when Symbol, String
|
|
1666
|
+
arel_column(field)
|
|
1667
|
+
when Proc
|
|
1668
|
+
field.call
|
|
1669
|
+
when Hash
|
|
1670
|
+
arel_columns_from_hash(field)
|
|
1671
|
+
else
|
|
1672
|
+
field
|
|
1673
|
+
end
|
|
1674
|
+
end
|
|
1675
|
+
end
|
|
1676
|
+
|
|
1544
1677
|
private
|
|
1545
1678
|
def async
|
|
1546
1679
|
spawn.async!
|
|
1547
1680
|
end
|
|
1548
1681
|
|
|
1682
|
+
def build_named_bound_sql_literal(statement, values)
|
|
1683
|
+
bound_values = values.transform_values do |value|
|
|
1684
|
+
if ActiveRecord::Relation === value
|
|
1685
|
+
Arel.sql(value.to_sql)
|
|
1686
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1687
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1688
|
+
values.empty? ? nil : values
|
|
1689
|
+
else
|
|
1690
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1691
|
+
value
|
|
1692
|
+
end
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
begin
|
|
1696
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", nil, bound_values)
|
|
1697
|
+
rescue Arel::BindError => error
|
|
1698
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1699
|
+
end
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
def build_bound_sql_literal(statement, values)
|
|
1703
|
+
bound_values = values.map do |value|
|
|
1704
|
+
if ActiveRecord::Relation === value
|
|
1705
|
+
Arel.sql(value.to_sql)
|
|
1706
|
+
elsif value.respond_to?(:map) && !value.acts_like?(:string)
|
|
1707
|
+
values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
|
|
1708
|
+
values.empty? ? nil : values
|
|
1709
|
+
else
|
|
1710
|
+
value = value.id_for_database if value.respond_to?(:id_for_database)
|
|
1711
|
+
value
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
begin
|
|
1716
|
+
Arel::Nodes::BoundSqlLiteral.new("(#{statement})", bound_values, nil)
|
|
1717
|
+
rescue Arel::BindError => error
|
|
1718
|
+
raise ActiveRecord::PreparedStatementInvalid, error.message
|
|
1719
|
+
end
|
|
1720
|
+
end
|
|
1721
|
+
|
|
1549
1722
|
def lookup_table_klass_from_join_dependencies(table_name)
|
|
1550
1723
|
each_join_dependencies do |join|
|
|
1551
1724
|
return join.base_klass if table_name == join.table_name
|
|
@@ -1570,37 +1743,31 @@ module ActiveRecord
|
|
|
1570
1743
|
)
|
|
1571
1744
|
end
|
|
1572
1745
|
|
|
1573
|
-
def
|
|
1574
|
-
raise
|
|
1575
|
-
raise ImmutableRelation if defined?(@arel) && @arel
|
|
1746
|
+
def assert_modifiable!
|
|
1747
|
+
raise UnmodifiableRelation if @loaded || @arel
|
|
1576
1748
|
end
|
|
1577
1749
|
|
|
1578
|
-
def build_arel(aliases
|
|
1750
|
+
def build_arel(aliases)
|
|
1579
1751
|
arel = Arel::SelectManager.new(table)
|
|
1580
1752
|
|
|
1581
1753
|
build_joins(arel.join_sources, aliases)
|
|
1582
1754
|
|
|
1583
1755
|
arel.where(where_clause.ast) unless where_clause.empty?
|
|
1584
1756
|
arel.having(having_clause.ast) unless having_clause.empty?
|
|
1585
|
-
arel.take(build_cast_value("LIMIT",
|
|
1757
|
+
arel.take(build_cast_value("LIMIT", limit_value)) if limit_value
|
|
1586
1758
|
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
|
1587
|
-
arel.group(*arel_columns(group_values
|
|
1759
|
+
arel.group(*arel_columns(group_values)) unless group_values.empty?
|
|
1588
1760
|
|
|
1589
1761
|
build_order(arel)
|
|
1590
1762
|
build_with(arel)
|
|
1591
1763
|
build_select(arel)
|
|
1592
1764
|
|
|
1593
1765
|
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
|
1766
|
+
arel.comment(*annotate_values) unless annotate_values.empty?
|
|
1594
1767
|
arel.distinct(distinct_value)
|
|
1595
1768
|
arel.from(build_from) unless from_clause.empty?
|
|
1596
1769
|
arel.lock(lock_value) if lock_value
|
|
1597
1770
|
|
|
1598
|
-
unless annotate_values.empty?
|
|
1599
|
-
annotates = annotate_values
|
|
1600
|
-
annotates = annotates.uniq if annotates.size > 1
|
|
1601
|
-
arel.comment(*annotates)
|
|
1602
|
-
end
|
|
1603
|
-
|
|
1604
1771
|
arel
|
|
1605
1772
|
end
|
|
1606
1773
|
|
|
@@ -1674,7 +1841,7 @@ module ActiveRecord
|
|
|
1674
1841
|
|
|
1675
1842
|
joins = joins_values.dup
|
|
1676
1843
|
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
|
1677
|
-
stashed_eager_load = joins.pop if joins.last.base_klass ==
|
|
1844
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == model
|
|
1678
1845
|
end
|
|
1679
1846
|
|
|
1680
1847
|
joins.each_with_index do |join, i|
|
|
@@ -1731,8 +1898,8 @@ module ActiveRecord
|
|
|
1731
1898
|
def build_select(arel)
|
|
1732
1899
|
if select_values.any?
|
|
1733
1900
|
arel.project(*arel_columns(select_values))
|
|
1734
|
-
elsif
|
|
1735
|
-
arel.project(*
|
|
1901
|
+
elsif model.ignored_columns.any? || model.enumerate_columns_in_select_statements
|
|
1902
|
+
arel.project(*model.column_names.map { |field| table[field] })
|
|
1736
1903
|
else
|
|
1737
1904
|
arel.project(table[Arel.star])
|
|
1738
1905
|
end
|
|
@@ -1742,25 +1909,41 @@ module ActiveRecord
|
|
|
1742
1909
|
return if with_values.empty?
|
|
1743
1910
|
|
|
1744
1911
|
with_statements = with_values.map do |with_value|
|
|
1745
|
-
raise ArgumentError, "Unsupported argument type: #{with_value} #{with_value.class}" unless with_value.is_a?(Hash)
|
|
1746
|
-
|
|
1747
1912
|
build_with_value_from_hash(with_value)
|
|
1748
1913
|
end
|
|
1749
1914
|
|
|
1750
|
-
arel.with(with_statements)
|
|
1915
|
+
@with_is_recursive ? arel.with(:recursive, with_statements) : arel.with(with_statements)
|
|
1751
1916
|
end
|
|
1752
1917
|
|
|
1753
1918
|
def build_with_value_from_hash(hash)
|
|
1754
1919
|
hash.map do |name, value|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1920
|
+
Arel::Nodes::TableAlias.new(build_with_expression_from_value(value), name)
|
|
1921
|
+
end
|
|
1922
|
+
end
|
|
1923
|
+
|
|
1924
|
+
def build_with_expression_from_value(value, nested = false)
|
|
1925
|
+
case value
|
|
1926
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::BoundSqlLiteral
|
|
1927
|
+
Arel::Nodes::Grouping.new(value)
|
|
1928
|
+
when ActiveRecord::Relation
|
|
1929
|
+
if nested
|
|
1930
|
+
value.arel.ast
|
|
1931
|
+
else
|
|
1932
|
+
value.arel
|
|
1933
|
+
end
|
|
1934
|
+
when Arel::SelectManager then value
|
|
1935
|
+
when Array
|
|
1936
|
+
return build_with_expression_from_value(value.first, false) if value.size == 1
|
|
1937
|
+
|
|
1938
|
+
parts = value.map do |query|
|
|
1939
|
+
build_with_expression_from_value(query, true)
|
|
1940
|
+
end
|
|
1941
|
+
|
|
1942
|
+
parts.reduce do |result, value|
|
|
1943
|
+
Arel::Nodes::UnionAll.new(result, value)
|
|
1944
|
+
end
|
|
1945
|
+
else
|
|
1946
|
+
raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
|
|
1764
1947
|
end
|
|
1765
1948
|
end
|
|
1766
1949
|
|
|
@@ -1768,54 +1951,76 @@ module ActiveRecord
|
|
|
1768
1951
|
with_table = Arel::Table.new(name)
|
|
1769
1952
|
|
|
1770
1953
|
table.join(with_table, kind).on(
|
|
1771
|
-
with_table[
|
|
1954
|
+
with_table[model.model_name.to_s.foreign_key].eq(table[model.primary_key])
|
|
1772
1955
|
).join_sources.first
|
|
1773
1956
|
end
|
|
1774
1957
|
|
|
1775
|
-
def
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1958
|
+
def arel_columns_from_hash(fields)
|
|
1959
|
+
fields.flat_map do |table_name, columns|
|
|
1960
|
+
table_name = table_name.name if table_name.is_a?(Symbol)
|
|
1961
|
+
case columns
|
|
1962
|
+
when Symbol, String
|
|
1963
|
+
arel_column_with_table(table_name, columns)
|
|
1964
|
+
when Array
|
|
1965
|
+
columns.map do |column|
|
|
1966
|
+
arel_column_with_table(table_name, column)
|
|
1781
1967
|
end
|
|
1782
|
-
when String
|
|
1783
|
-
arel_column(field, &:itself)
|
|
1784
|
-
when Proc
|
|
1785
|
-
field.call
|
|
1786
1968
|
else
|
|
1787
|
-
|
|
1969
|
+
raise TypeError, "Expected Symbol, String or Array, got: #{columns.class}"
|
|
1788
1970
|
end
|
|
1789
1971
|
end
|
|
1790
1972
|
end
|
|
1791
1973
|
|
|
1974
|
+
def arel_column_with_table(table_name, column_name)
|
|
1975
|
+
self.references_values |= [Arel.sql(table_name, retryable: true)]
|
|
1976
|
+
|
|
1977
|
+
if column_name.is_a?(Symbol) || !column_name.match?(/\W/)
|
|
1978
|
+
predicate_builder.resolve_arel_attribute(table_name, column_name) do
|
|
1979
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
|
1980
|
+
end
|
|
1981
|
+
else
|
|
1982
|
+
Arel.sql("#{model.adapter_class.quote_table_name(table_name)}.#{column_name}")
|
|
1983
|
+
end
|
|
1984
|
+
end
|
|
1985
|
+
|
|
1792
1986
|
def arel_column(field)
|
|
1793
|
-
field =
|
|
1987
|
+
field = field.name if is_symbol = field.is_a?(Symbol)
|
|
1988
|
+
|
|
1989
|
+
field = model.attribute_aliases[field] || field
|
|
1794
1990
|
from = from_clause.name || from_clause.value
|
|
1795
1991
|
|
|
1796
|
-
if
|
|
1992
|
+
if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
|
1797
1993
|
table[field]
|
|
1798
|
-
elsif
|
|
1799
|
-
table, column
|
|
1800
|
-
|
|
1801
|
-
lookup_table_klass_from_join_dependencies(table)
|
|
1802
|
-
end
|
|
1803
|
-
else
|
|
1994
|
+
elsif /\A(?<table>(?:\w+\.)?\w+)\.(?<column>\w+)\z/ =~ field
|
|
1995
|
+
arel_column_with_table(table, column)
|
|
1996
|
+
elsif block_given?
|
|
1804
1997
|
yield field
|
|
1998
|
+
elsif Arel.arel_node?(field)
|
|
1999
|
+
field
|
|
2000
|
+
elsif is_symbol
|
|
2001
|
+
Arel.sql(model.adapter_class.quote_table_name(field), retryable: true)
|
|
2002
|
+
else
|
|
2003
|
+
Arel.sql(field)
|
|
1805
2004
|
end
|
|
1806
2005
|
end
|
|
1807
2006
|
|
|
1808
2007
|
def table_name_matches?(from)
|
|
1809
2008
|
table_name = Regexp.escape(table.name)
|
|
1810
|
-
quoted_table_name = Regexp.escape(
|
|
2009
|
+
quoted_table_name = Regexp.escape(model.adapter_class.quote_table_name(table.name))
|
|
1811
2010
|
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
|
1812
2011
|
end
|
|
1813
2012
|
|
|
1814
2013
|
def reverse_sql_order(order_query)
|
|
1815
2014
|
if order_query.empty?
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2015
|
+
if !_reverse_order_columns.empty?
|
|
2016
|
+
return _reverse_order_columns.map { |column| table[column].desc }
|
|
2017
|
+
end
|
|
2018
|
+
|
|
2019
|
+
raise IrreversibleOrderError, <<~MSG.squish
|
|
2020
|
+
Relation has no order values, and #{model} has no order columns to use as a default.
|
|
2021
|
+
Set at least one of `implicit_order_column`, or `primary_key` on the model when no
|
|
2022
|
+
`order `is specified on the relation.
|
|
2023
|
+
MSG
|
|
1819
2024
|
end
|
|
1820
2025
|
|
|
1821
2026
|
order_query.flat_map do |o|
|
|
@@ -1840,6 +2045,13 @@ module ActiveRecord
|
|
|
1840
2045
|
end
|
|
1841
2046
|
end
|
|
1842
2047
|
|
|
2048
|
+
def _reverse_order_columns
|
|
2049
|
+
roc = []
|
|
2050
|
+
roc << model.implicit_order_column if model.implicit_order_column
|
|
2051
|
+
roc << model.primary_key if model.primary_key
|
|
2052
|
+
roc.flatten.uniq.compact
|
|
2053
|
+
end
|
|
2054
|
+
|
|
1843
2055
|
def does_not_support_reverse?(order)
|
|
1844
2056
|
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
|
1845
2057
|
# override methods like #count.
|
|
@@ -1863,7 +2075,9 @@ module ActiveRecord
|
|
|
1863
2075
|
args.each do |arg|
|
|
1864
2076
|
next unless arg.is_a?(Hash)
|
|
1865
2077
|
arg.each do |_key, value|
|
|
1866
|
-
|
|
2078
|
+
if value.is_a?(Hash)
|
|
2079
|
+
validate_order_args([value])
|
|
2080
|
+
elsif VALID_DIRECTIONS.exclude?(value)
|
|
1867
2081
|
raise ArgumentError,
|
|
1868
2082
|
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
|
1869
2083
|
end
|
|
@@ -1871,10 +2085,14 @@ module ActiveRecord
|
|
|
1871
2085
|
end
|
|
1872
2086
|
end
|
|
1873
2087
|
|
|
2088
|
+
def flattened_args(args)
|
|
2089
|
+
args.flat_map { |e| (e.is_a?(Hash) || e.is_a?(Array)) ? flattened_args(e.to_a) : e }
|
|
2090
|
+
end
|
|
2091
|
+
|
|
1874
2092
|
def preprocess_order_args(order_args)
|
|
1875
|
-
|
|
1876
|
-
order_args
|
|
1877
|
-
permit:
|
|
2093
|
+
model.disallow_raw_sql!(
|
|
2094
|
+
flattened_args(order_args),
|
|
2095
|
+
permit: model.adapter_class.column_name_with_order_matcher
|
|
1878
2096
|
)
|
|
1879
2097
|
|
|
1880
2098
|
validate_order_args(order_args)
|
|
@@ -1888,14 +2106,20 @@ module ActiveRecord
|
|
|
1888
2106
|
when Symbol
|
|
1889
2107
|
order_column(arg.to_s).asc
|
|
1890
2108
|
when Hash
|
|
1891
|
-
arg.map
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2109
|
+
arg.map do |key, value|
|
|
2110
|
+
if value.is_a?(Hash)
|
|
2111
|
+
value.map do |field, dir|
|
|
2112
|
+
order_column([key.to_s, field.to_s].join(".")).public_send(dir.downcase)
|
|
2113
|
+
end
|
|
1895
2114
|
else
|
|
1896
|
-
|
|
2115
|
+
case key
|
|
2116
|
+
when Arel::Nodes::SqlLiteral, Arel::Nodes::Node, Arel::Attribute
|
|
2117
|
+
key.public_send(value.downcase)
|
|
2118
|
+
else
|
|
2119
|
+
order_column(key.to_s).public_send(value.downcase)
|
|
2120
|
+
end
|
|
1897
2121
|
end
|
|
1898
|
-
|
|
2122
|
+
end
|
|
1899
2123
|
else
|
|
1900
2124
|
arg
|
|
1901
2125
|
end
|
|
@@ -1904,7 +2128,7 @@ module ActiveRecord
|
|
|
1904
2128
|
|
|
1905
2129
|
def sanitize_order_arguments(order_args)
|
|
1906
2130
|
order_args.map! do |arg|
|
|
1907
|
-
|
|
2131
|
+
model.sanitize_sql_for_order(arg)
|
|
1908
2132
|
end
|
|
1909
2133
|
end
|
|
1910
2134
|
|
|
@@ -1930,7 +2154,7 @@ module ActiveRecord
|
|
|
1930
2154
|
arg.expr.relation.name
|
|
1931
2155
|
end
|
|
1932
2156
|
end
|
|
1933
|
-
end.
|
|
2157
|
+
end.filter_map { |ref| Arel.sql(ref, retryable: true) if ref }
|
|
1934
2158
|
end
|
|
1935
2159
|
|
|
1936
2160
|
def extract_table_name_from(string)
|
|
@@ -1942,17 +2166,18 @@ module ActiveRecord
|
|
|
1942
2166
|
if attr_name == "count" && !group_values.empty?
|
|
1943
2167
|
table[attr_name]
|
|
1944
2168
|
else
|
|
1945
|
-
Arel.sql(
|
|
2169
|
+
Arel.sql(model.adapter_class.quote_table_name(attr_name), retryable: true)
|
|
1946
2170
|
end
|
|
1947
2171
|
end
|
|
1948
2172
|
end
|
|
1949
2173
|
|
|
1950
|
-
def build_case_for_value_position(column, values)
|
|
2174
|
+
def build_case_for_value_position(column, values, filter: true)
|
|
1951
2175
|
node = Arel::Nodes::Case.new
|
|
1952
2176
|
values.each.with_index(1) do |value, order|
|
|
1953
2177
|
node.when(column.eq(value)).then(order)
|
|
1954
2178
|
end
|
|
1955
2179
|
|
|
2180
|
+
node = node.else(values.length + 1) unless filter
|
|
1956
2181
|
Arel::Nodes::Ascending.new(node)
|
|
1957
2182
|
end
|
|
1958
2183
|
|
|
@@ -2010,38 +2235,40 @@ module ActiveRecord
|
|
|
2010
2235
|
def process_select_args(fields)
|
|
2011
2236
|
fields.flat_map do |field|
|
|
2012
2237
|
if field.is_a?(Hash)
|
|
2013
|
-
|
|
2238
|
+
arel_column_aliases_from_hash(field)
|
|
2014
2239
|
else
|
|
2015
2240
|
field
|
|
2016
2241
|
end
|
|
2017
2242
|
end
|
|
2018
2243
|
end
|
|
2019
2244
|
|
|
2020
|
-
def
|
|
2245
|
+
def arel_column_aliases_from_hash(fields)
|
|
2021
2246
|
fields.flat_map do |key, columns_aliases|
|
|
2247
|
+
table_name = key.is_a?(Symbol) ? key.name : key
|
|
2022
2248
|
case columns_aliases
|
|
2023
2249
|
when Hash
|
|
2024
2250
|
columns_aliases.map do |column, column_alias|
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
self.references_values |= references unless references.empty?
|
|
2028
|
-
end
|
|
2029
|
-
arel_column("#{key}.#{column}") do
|
|
2030
|
-
predicate_builder.resolve_arel_attribute(key.to_s, column)
|
|
2031
|
-
end.as(column_alias.to_s)
|
|
2251
|
+
arel_column_with_table(table_name, column)
|
|
2252
|
+
.as(model.adapter_class.quote_column_name(column_alias.to_s))
|
|
2032
2253
|
end
|
|
2033
2254
|
when Array
|
|
2034
2255
|
columns_aliases.map do |column|
|
|
2035
|
-
|
|
2256
|
+
arel_column_with_table(table_name, column)
|
|
2036
2257
|
end
|
|
2037
2258
|
when String, Symbol
|
|
2038
|
-
arel_column(key
|
|
2039
|
-
|
|
2040
|
-
end.as(columns_aliases.to_s)
|
|
2259
|
+
arel_column(key)
|
|
2260
|
+
.as(model.adapter_class.quote_column_name(columns_aliases.to_s))
|
|
2041
2261
|
end
|
|
2042
2262
|
end
|
|
2043
2263
|
end
|
|
2044
2264
|
|
|
2265
|
+
def process_with_args(args)
|
|
2266
|
+
args.flat_map do |arg|
|
|
2267
|
+
raise ArgumentError, "Unsupported argument type: #{arg} #{arg.class}" unless arg.is_a?(Hash)
|
|
2268
|
+
arg.map { |k, v| { k => v } }
|
|
2269
|
+
end
|
|
2270
|
+
end
|
|
2271
|
+
|
|
2045
2272
|
STRUCTURAL_VALUE_METHODS = (
|
|
2046
2273
|
Relation::VALUE_METHODS -
|
|
2047
2274
|
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
|
@@ -2051,11 +2278,11 @@ module ActiveRecord
|
|
|
2051
2278
|
values = other.values
|
|
2052
2279
|
STRUCTURAL_VALUE_METHODS.reject do |method|
|
|
2053
2280
|
v1, v2 = @values[method], values[method]
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2281
|
+
|
|
2282
|
+
# `and`/`or` are focused to combine where-like clauses, so it relaxes
|
|
2283
|
+
# the difference when other's multi values are uninitialized.
|
|
2284
|
+
next true if v1.is_a?(Array) && v2.nil?
|
|
2285
|
+
|
|
2059
2286
|
v1 == v2
|
|
2060
2287
|
end
|
|
2061
2288
|
end
|