activerecord 5.2.8.1 → 6.1.6.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1255 -596
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +9 -8
- data/lib/active_record/association_relation.rb +30 -10
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +100 -41
- data/lib/active_record/associations/association_scope.rb +23 -21
- data/lib/active_record/associations/belongs_to_association.rb +55 -48
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
- data/lib/active_record/associations/builder/association.rb +45 -22
- data/lib/active_record/associations/builder/belongs_to.rb +29 -59
- data/lib/active_record/associations/builder/collection_association.rb +8 -17
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -2
- data/lib/active_record/associations/builder/has_one.rb +33 -2
- data/lib/active_record/associations/builder/singular_association.rb +3 -1
- data/lib/active_record/associations/collection_association.rb +44 -34
- data/lib/active_record/associations/collection_proxy.rb +25 -21
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +26 -13
- data/lib/active_record/associations/has_many_through_association.rb +24 -18
- data/lib/active_record/associations/has_one_association.rb +43 -31
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +91 -60
- data/lib/active_record/associations/preloader/association.rb +69 -43
- data/lib/active_record/associations/preloader/through_association.rb +49 -40
- data/lib/active_record/associations/preloader.rb +47 -34
- data/lib/active_record/associations/singular_association.rb +3 -17
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +137 -25
- data/lib/active_record/attribute_assignment.rb +17 -19
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -40
- data/lib/active_record/attribute_methods/primary_key.rb +20 -25
- data/lib/active_record/attribute_methods/query.rb +4 -8
- data/lib/active_record/attribute_methods/read.rb +14 -56
- data/lib/active_record/attribute_methods/serialization.rb +12 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +18 -34
- data/lib/active_record/attribute_methods.rb +81 -143
- data/lib/active_record/attributes.rb +46 -9
- data/lib/active_record/autosave_association.rb +57 -42
- data/lib/active_record/base.rb +4 -17
- data/lib/active_record/callbacks.rb +158 -43
- data/lib/active_record/coders/yaml_column.rb +1 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +211 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
- data/lib/active_record/connection_adapters/abstract/transaction.rb +167 -69
- data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -99
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
- data/lib/active_record/connection_adapters/column.rb +30 -12
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +88 -32
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +142 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -54
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
- data/lib/active_record/connection_adapters/schema_cache.rb +159 -21
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +293 -33
- data/lib/active_record/core.rb +333 -98
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +273 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +108 -36
- data/lib/active_record/errors.rb +62 -19
- data/lib/active_record/explain.rb +10 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +200 -481
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +53 -24
- data/lib/active_record/insert_all.rb +212 -0
- data/lib/active_record/integration.rb +67 -17
- data/lib/active_record/internal_metadata.rb +28 -9
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +37 -23
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +35 -35
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +145 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +206 -157
- data/lib/active_record/model_schema.rb +148 -22
- data/lib/active_record/nested_attributes.rb +4 -7
- data/lib/active_record/no_touching.rb +8 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +267 -59
- data/lib/active_record/query_cache.rb +21 -4
- data/lib/active_record/querying.rb +40 -23
- data/lib/active_record/railtie.rb +116 -59
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +411 -80
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +109 -93
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +44 -35
- data/lib/active_record/relation/calculations.rb +157 -90
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +64 -39
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +32 -40
- data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +62 -45
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +476 -187
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +9 -9
- data/lib/active_record/relation/where_clause.rb +115 -62
- data/lib/active_record/relation.rb +379 -115
- data/lib/active_record/result.rb +64 -38
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +22 -41
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +54 -9
- data/lib/active_record/schema_migration.rb +7 -9
- data/lib/active_record/scoping/default.rb +4 -8
- data/lib/active_record/scoping/named.rb +17 -24
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +49 -6
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -43
- data/lib/active_record/tasks/database_tasks.rb +277 -81
- data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
- data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +287 -0
- data/lib/active_record/timestamp.rb +43 -32
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +62 -118
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +10 -5
- data/lib/active_record/type_caster/connection.rb +15 -15
- data/lib/active_record/type_caster/map.rb +8 -8
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +38 -30
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record.rb +13 -12
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +76 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +70 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +54 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
- data/lib/rails/generators/active_record/migration.rb +19 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +116 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
- data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -3,8 +3,8 @@
|
|
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_record/relation/where_clause_factory"
|
7
6
|
require "active_model/forbidden_attributes_protection"
|
7
|
+
require "active_support/core_ext/array/wrap"
|
8
8
|
|
9
9
|
module ActiveRecord
|
10
10
|
module QueryMethods
|
@@ -15,8 +15,6 @@ module ActiveRecord
|
|
15
15
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
16
16
|
# In this case, #where must be chained with #not to return a new relation.
|
17
17
|
class WhereChain
|
18
|
-
include ActiveModel::ForbiddenAttributesProtection
|
19
|
-
|
20
18
|
def initialize(scope)
|
21
19
|
@scope = scope
|
22
20
|
end
|
@@ -43,14 +41,41 @@ module ActiveRecord
|
|
43
41
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
44
42
|
#
|
45
43
|
# User.where.not(name: "Jon", role: "admin")
|
46
|
-
# # SELECT * FROM users WHERE name
|
44
|
+
# # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
|
47
45
|
def not(opts, *rest)
|
48
|
-
|
49
|
-
|
50
|
-
where_clause = @scope.send(:where_clause_factory).build(opts, rest)
|
46
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
51
47
|
|
52
|
-
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
53
48
|
@scope.where_clause += where_clause.invert
|
49
|
+
|
50
|
+
@scope
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a new relation with left outer joins and where clause to identify
|
54
|
+
# missing relations.
|
55
|
+
#
|
56
|
+
# For example, posts that are missing a related author:
|
57
|
+
#
|
58
|
+
# Post.where.missing(:author)
|
59
|
+
# # SELECT "posts".* FROM "posts"
|
60
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
61
|
+
# # WHERE "authors"."id" IS NULL
|
62
|
+
#
|
63
|
+
# Additionally, multiple relations can be combined. This will return posts
|
64
|
+
# that are missing both an author and any comments:
|
65
|
+
#
|
66
|
+
# Post.where.missing(:author, :comments)
|
67
|
+
# # SELECT "posts".* FROM "posts"
|
68
|
+
# # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
|
69
|
+
# # LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
70
|
+
# # WHERE "authors"."id" IS NULL AND "comments"."id" IS NULL
|
71
|
+
def missing(*args)
|
72
|
+
args.each do |arg|
|
73
|
+
reflection = @scope.klass._reflect_on_association(arg)
|
74
|
+
opts = { reflection.table_name => { reflection.association_primary_key => nil } }
|
75
|
+
@scope.left_outer_joins!(arg)
|
76
|
+
@scope.where!(opts)
|
77
|
+
end
|
78
|
+
|
54
79
|
@scope
|
55
80
|
end
|
56
81
|
end
|
@@ -59,20 +84,25 @@ module ActiveRecord
|
|
59
84
|
FROZEN_EMPTY_HASH = {}.freeze
|
60
85
|
|
61
86
|
Relation::VALUE_METHODS.each do |name|
|
62
|
-
method_name =
|
87
|
+
method_name, default =
|
63
88
|
case name
|
64
|
-
when *Relation::MULTI_VALUE_METHODS
|
65
|
-
|
66
|
-
when *Relation::
|
89
|
+
when *Relation::MULTI_VALUE_METHODS
|
90
|
+
["#{name}_values", "FROZEN_EMPTY_ARRAY"]
|
91
|
+
when *Relation::SINGLE_VALUE_METHODS
|
92
|
+
["#{name}_value", name == :create_with ? "FROZEN_EMPTY_HASH" : "nil"]
|
93
|
+
when *Relation::CLAUSE_METHODS
|
94
|
+
["#{name}_clause", name == :from ? "Relation::FromClause.empty" : "Relation::WhereClause.empty"]
|
67
95
|
end
|
68
|
-
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
69
|
-
def #{method_name} # def includes_values
|
70
|
-
get_value(#{name.inspect}) # get_value(:includes)
|
71
|
-
end # end
|
72
96
|
|
73
|
-
|
74
|
-
|
75
|
-
|
97
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
98
|
+
def #{method_name} # def includes_values
|
99
|
+
@values.fetch(:#{name}, #{default}) # @values.fetch(:includes, FROZEN_EMPTY_ARRAY)
|
100
|
+
end # end
|
101
|
+
|
102
|
+
def #{method_name}=(value) # def includes_values=(value)
|
103
|
+
assert_mutability! # assert_mutability!
|
104
|
+
@values[:#{name}] = value # @values[:includes] = value
|
105
|
+
end # end
|
76
106
|
CODE
|
77
107
|
end
|
78
108
|
|
@@ -100,7 +130,7 @@ module ActiveRecord
|
|
100
130
|
#
|
101
131
|
# === conditions
|
102
132
|
#
|
103
|
-
# If you want to add conditions to your included models you'll have
|
133
|
+
# If you want to add string conditions to your included models, you'll have
|
104
134
|
# to explicitly reference them. For example:
|
105
135
|
#
|
106
136
|
# User.includes(:posts).where('posts.name = ?', 'example')
|
@@ -111,15 +141,18 @@ module ActiveRecord
|
|
111
141
|
#
|
112
142
|
# Note that #includes works with association names while #references needs
|
113
143
|
# the actual table name.
|
144
|
+
#
|
145
|
+
# If you pass the conditions via hash, you don't need to call #references
|
146
|
+
# explicitly, as #where references the tables for you. For example, this
|
147
|
+
# will work correctly:
|
148
|
+
#
|
149
|
+
# User.includes(:posts).where(posts: { name: 'example' })
|
114
150
|
def includes(*args)
|
115
151
|
check_if_method_has_arguments!(:includes, args)
|
116
152
|
spawn.includes!(*args)
|
117
153
|
end
|
118
154
|
|
119
155
|
def includes!(*args) # :nodoc:
|
120
|
-
args.reject!(&:blank?)
|
121
|
-
args.flatten!
|
122
|
-
|
123
156
|
self.includes_values |= args
|
124
157
|
self
|
125
158
|
end
|
@@ -136,7 +169,7 @@ module ActiveRecord
|
|
136
169
|
end
|
137
170
|
|
138
171
|
def eager_load!(*args) # :nodoc:
|
139
|
-
self.eager_load_values
|
172
|
+
self.eager_load_values |= args
|
140
173
|
self
|
141
174
|
end
|
142
175
|
|
@@ -150,10 +183,23 @@ module ActiveRecord
|
|
150
183
|
end
|
151
184
|
|
152
185
|
def preload!(*args) # :nodoc:
|
153
|
-
self.preload_values
|
186
|
+
self.preload_values |= args
|
154
187
|
self
|
155
188
|
end
|
156
189
|
|
190
|
+
# Extracts a named +association+ from the relation. The named association is first preloaded,
|
191
|
+
# then the individual association records are collected from the relation. Like so:
|
192
|
+
#
|
193
|
+
# account.memberships.extract_associated(:user)
|
194
|
+
# # => Returns collection of User records
|
195
|
+
#
|
196
|
+
# This is short-hand for:
|
197
|
+
#
|
198
|
+
# account.memberships.preload(:user).collect(&:user)
|
199
|
+
def extract_associated(association)
|
200
|
+
preload(association).collect(&association)
|
201
|
+
end
|
202
|
+
|
157
203
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
158
204
|
# and should therefore be JOINed in any query rather than loaded separately.
|
159
205
|
# This method only works in conjunction with #includes.
|
@@ -170,9 +216,6 @@ module ActiveRecord
|
|
170
216
|
end
|
171
217
|
|
172
218
|
def references!(*table_names) # :nodoc:
|
173
|
-
table_names.flatten!
|
174
|
-
table_names.map!(&:to_s)
|
175
|
-
|
176
219
|
self.references_values |= table_names
|
177
220
|
self
|
178
221
|
end
|
@@ -226,13 +269,33 @@ module ActiveRecord
|
|
226
269
|
return super()
|
227
270
|
end
|
228
271
|
|
229
|
-
|
272
|
+
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
|
230
273
|
spawn._select!(*fields)
|
231
274
|
end
|
232
275
|
|
233
276
|
def _select!(*fields) # :nodoc:
|
234
|
-
fields
|
235
|
-
self
|
277
|
+
self.select_values |= fields
|
278
|
+
self
|
279
|
+
end
|
280
|
+
|
281
|
+
# Allows you to change a previously set select statement.
|
282
|
+
#
|
283
|
+
# Post.select(:title, :body)
|
284
|
+
# # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
|
285
|
+
#
|
286
|
+
# Post.select(:title, :body).reselect(:created_at)
|
287
|
+
# # SELECT `posts`.`created_at` FROM `posts`
|
288
|
+
#
|
289
|
+
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
|
290
|
+
# Note that we're unscoping the entire select statement.
|
291
|
+
def reselect(*args)
|
292
|
+
check_if_method_has_arguments!(:reselect, args)
|
293
|
+
spawn.reselect!(*args)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Same as #reselect but operates on relation in-place instead of copying.
|
297
|
+
def reselect!(*args) # :nodoc:
|
298
|
+
self.select_values = args
|
236
299
|
self
|
237
300
|
end
|
238
301
|
|
@@ -262,8 +325,6 @@ module ActiveRecord
|
|
262
325
|
end
|
263
326
|
|
264
327
|
def group!(*args) # :nodoc:
|
265
|
-
args.flatten!
|
266
|
-
|
267
328
|
self.group_values += args
|
268
329
|
self
|
269
330
|
end
|
@@ -288,15 +349,16 @@ module ActiveRecord
|
|
288
349
|
# User.order('name DESC, email')
|
289
350
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
290
351
|
def order(*args)
|
291
|
-
check_if_method_has_arguments!(:order, args)
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
353
|
+
sanitize_order_arguments(args)
|
354
|
+
end
|
292
355
|
spawn.order!(*args)
|
293
356
|
end
|
294
357
|
|
295
358
|
# Same as #order but operates on relation in-place instead of copying.
|
296
359
|
def order!(*args) # :nodoc:
|
297
|
-
preprocess_order_args(args)
|
298
|
-
|
299
|
-
self.order_values += args
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
361
|
+
self.order_values |= args
|
300
362
|
self
|
301
363
|
end
|
302
364
|
|
@@ -310,22 +372,24 @@ module ActiveRecord
|
|
310
372
|
#
|
311
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
312
374
|
def reorder(*args)
|
313
|
-
check_if_method_has_arguments!(:reorder, args)
|
375
|
+
check_if_method_has_arguments!(:reorder, args) do
|
376
|
+
sanitize_order_arguments(args) unless args.all?(&:blank?)
|
377
|
+
end
|
314
378
|
spawn.reorder!(*args)
|
315
379
|
end
|
316
380
|
|
317
381
|
# Same as #reorder but operates on relation in-place instead of copying.
|
318
382
|
def reorder!(*args) # :nodoc:
|
319
|
-
preprocess_order_args(args)
|
320
|
-
|
383
|
+
preprocess_order_args(args) unless args.all?(&:blank?)
|
384
|
+
args.uniq!
|
321
385
|
self.reordering_value = true
|
322
386
|
self.order_values = args
|
323
387
|
self
|
324
388
|
end
|
325
389
|
|
326
390
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
327
|
-
:limit, :offset, :joins, :left_outer_joins,
|
328
|
-
:includes, :from, :readonly, :having])
|
391
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
392
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
329
393
|
|
330
394
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
331
395
|
# This is useful when passing around chains of relations and would like to
|
@@ -366,7 +430,6 @@ module ActiveRecord
|
|
366
430
|
end
|
367
431
|
|
368
432
|
def unscope!(*args) # :nodoc:
|
369
|
-
args.flatten!
|
370
433
|
self.unscope_values += args
|
371
434
|
|
372
435
|
args.each do |scope|
|
@@ -376,14 +439,15 @@ module ActiveRecord
|
|
376
439
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
377
440
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
378
441
|
end
|
379
|
-
|
442
|
+
assert_mutability!
|
443
|
+
@values.delete(scope)
|
380
444
|
when Hash
|
381
445
|
scope.each do |key, target_value|
|
382
446
|
if key != :where
|
383
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
384
448
|
end
|
385
449
|
|
386
|
-
target_values = Array(target_value)
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
387
451
|
self.where_clause = where_clause.except(*target_values)
|
388
452
|
end
|
389
453
|
else
|
@@ -416,8 +480,7 @@ module ActiveRecord
|
|
416
480
|
# # SELECT "users".*
|
417
481
|
# # FROM "users"
|
418
482
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
419
|
-
# # INNER JOIN "comments" "
|
420
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
421
484
|
#
|
422
485
|
# You can use strings in order to customize your joins:
|
423
486
|
#
|
@@ -429,9 +492,7 @@ module ActiveRecord
|
|
429
492
|
end
|
430
493
|
|
431
494
|
def joins!(*args) # :nodoc:
|
432
|
-
args
|
433
|
-
args.flatten!
|
434
|
-
self.joins_values += args
|
495
|
+
self.joins_values |= args
|
435
496
|
self
|
436
497
|
end
|
437
498
|
|
@@ -447,9 +508,7 @@ module ActiveRecord
|
|
447
508
|
alias :left_joins :left_outer_joins
|
448
509
|
|
449
510
|
def left_outer_joins!(*args) # :nodoc:
|
450
|
-
args
|
451
|
-
args.flatten!
|
452
|
-
self.left_outer_joins_values += args
|
511
|
+
self.left_outer_joins_values |= args
|
453
512
|
self
|
454
513
|
end
|
455
514
|
|
@@ -572,20 +631,18 @@ module ActiveRecord
|
|
572
631
|
#
|
573
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
574
633
|
# the current relation.
|
575
|
-
def where(
|
576
|
-
if
|
634
|
+
def where(*args)
|
635
|
+
if args.empty?
|
577
636
|
WhereChain.new(spawn)
|
578
|
-
elsif
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
579
638
|
self
|
580
639
|
else
|
581
|
-
spawn.where!(
|
640
|
+
spawn.where!(*args)
|
582
641
|
end
|
583
642
|
end
|
584
643
|
|
585
644
|
def where!(opts, *rest) # :nodoc:
|
586
|
-
|
587
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
588
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
589
646
|
self
|
590
647
|
end
|
591
648
|
|
@@ -603,7 +660,44 @@ module ActiveRecord
|
|
603
660
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
604
661
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
605
662
|
def rewhere(conditions)
|
606
|
-
|
663
|
+
scope = spawn
|
664
|
+
where_clause = scope.build_where_clause(conditions)
|
665
|
+
|
666
|
+
scope.unscope!(where: where_clause.extract_attributes)
|
667
|
+
scope.where_clause += where_clause
|
668
|
+
scope
|
669
|
+
end
|
670
|
+
|
671
|
+
# Returns a new relation, which is the logical intersection of this relation and the one passed
|
672
|
+
# as an argument.
|
673
|
+
#
|
674
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
675
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
676
|
+
# present).
|
677
|
+
#
|
678
|
+
# Post.where(id: [1, 2]).and(Post.where(id: [2, 3]))
|
679
|
+
# # SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (1, 2) AND `posts`.`id` IN (2, 3)
|
680
|
+
#
|
681
|
+
def and(other)
|
682
|
+
if other.is_a?(Relation)
|
683
|
+
spawn.and!(other)
|
684
|
+
else
|
685
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #and. Pass an ActiveRecord::Relation object instead."
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
def and!(other) # :nodoc:
|
690
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
691
|
+
|
692
|
+
unless incompatible_values.empty?
|
693
|
+
raise ArgumentError, "Relation passed to #and must be structurally compatible. Incompatible values: #{incompatible_values}"
|
694
|
+
end
|
695
|
+
|
696
|
+
self.where_clause |= other.where_clause
|
697
|
+
self.having_clause |= other.having_clause
|
698
|
+
self.references_values |= other.references_values
|
699
|
+
|
700
|
+
self
|
607
701
|
end
|
608
702
|
|
609
703
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
@@ -611,21 +705,21 @@ module ActiveRecord
|
|
611
705
|
#
|
612
706
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
613
707
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
614
|
-
# present).
|
708
|
+
# present).
|
615
709
|
#
|
616
710
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
617
711
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
618
712
|
#
|
619
713
|
def or(other)
|
620
|
-
|
714
|
+
if other.is_a?(Relation)
|
715
|
+
spawn.or!(other)
|
716
|
+
else
|
621
717
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
622
718
|
end
|
623
|
-
|
624
|
-
spawn.or!(other)
|
625
719
|
end
|
626
720
|
|
627
721
|
def or!(other) # :nodoc:
|
628
|
-
incompatible_values =
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
629
723
|
|
630
724
|
unless incompatible_values.empty?
|
631
725
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
@@ -633,7 +727,7 @@ module ActiveRecord
|
|
633
727
|
|
634
728
|
self.where_clause = self.where_clause.or(other.where_clause)
|
635
729
|
self.having_clause = having_clause.or(other.having_clause)
|
636
|
-
self.references_values
|
730
|
+
self.references_values |= other.references_values
|
637
731
|
|
638
732
|
self
|
639
733
|
end
|
@@ -647,10 +741,7 @@ module ActiveRecord
|
|
647
741
|
end
|
648
742
|
|
649
743
|
def having!(opts, *rest) # :nodoc:
|
650
|
-
|
651
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
652
|
-
|
653
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
654
745
|
self
|
655
746
|
end
|
656
747
|
|
@@ -752,6 +843,21 @@ module ActiveRecord
|
|
752
843
|
self
|
753
844
|
end
|
754
845
|
|
846
|
+
# Sets the returned relation to strict_loading mode. This will raise an error
|
847
|
+
# if the record tries to lazily load an association.
|
848
|
+
#
|
849
|
+
# user = User.strict_loading.first
|
850
|
+
# user.comments.to_a
|
851
|
+
# => ActiveRecord::StrictLoadingViolationError
|
852
|
+
def strict_loading(value = true)
|
853
|
+
spawn.strict_loading!(value)
|
854
|
+
end
|
855
|
+
|
856
|
+
def strict_loading!(value = true) # :nodoc:
|
857
|
+
self.strict_loading_value = value
|
858
|
+
self
|
859
|
+
end
|
860
|
+
|
755
861
|
# Sets attributes to be used when creating new records from a
|
756
862
|
# relation object.
|
757
863
|
#
|
@@ -876,6 +982,27 @@ module ActiveRecord
|
|
876
982
|
self
|
877
983
|
end
|
878
984
|
|
985
|
+
# Specify optimizer hints to be used in the SELECT statement.
|
986
|
+
#
|
987
|
+
# Example (for MySQL):
|
988
|
+
#
|
989
|
+
# Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
|
990
|
+
# # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
|
991
|
+
#
|
992
|
+
# Example (for PostgreSQL with pg_hint_plan):
|
993
|
+
#
|
994
|
+
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
|
995
|
+
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
|
996
|
+
def optimizer_hints(*args)
|
997
|
+
check_if_method_has_arguments!(:optimizer_hints, args)
|
998
|
+
spawn.optimizer_hints!(*args)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def optimizer_hints!(*args) # :nodoc:
|
1002
|
+
self.optimizer_hints_values |= args
|
1003
|
+
self
|
1004
|
+
end
|
1005
|
+
|
879
1006
|
# Reverse the existing order clause on the relation.
|
880
1007
|
#
|
881
1008
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -884,8 +1011,7 @@ module ActiveRecord
|
|
884
1011
|
end
|
885
1012
|
|
886
1013
|
def reverse_order! # :nodoc:
|
887
|
-
orders = order_values.
|
888
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
889
1015
|
self.order_values = reverse_sql_order(orders)
|
890
1016
|
self
|
891
1017
|
end
|
@@ -895,68 +1021,159 @@ module ActiveRecord
|
|
895
1021
|
self
|
896
1022
|
end
|
897
1023
|
|
1024
|
+
def skip_preloading! # :nodoc:
|
1025
|
+
self.skip_preloading_value = true
|
1026
|
+
self
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
# Adds an SQL comment to queries generated from this relation. For example:
|
1030
|
+
#
|
1031
|
+
# User.annotate("selecting user names").select(:name)
|
1032
|
+
# # SELECT "users"."name" FROM "users" /* selecting user names */
|
1033
|
+
#
|
1034
|
+
# User.annotate("selecting", "user", "names").select(:name)
|
1035
|
+
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1036
|
+
#
|
1037
|
+
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1038
|
+
def annotate(*args)
|
1039
|
+
check_if_method_has_arguments!(:annotate, args)
|
1040
|
+
spawn.annotate!(*args)
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
# Like #annotate, but modifies relation in place.
|
1044
|
+
def annotate!(*args) # :nodoc:
|
1045
|
+
self.annotate_values += args
|
1046
|
+
self
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# Deduplicate multiple values.
|
1050
|
+
def uniq!(name)
|
1051
|
+
if values = @values[name]
|
1052
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1053
|
+
end
|
1054
|
+
self
|
1055
|
+
end
|
1056
|
+
|
898
1057
|
# Returns the Arel object associated with the relation.
|
899
1058
|
def arel(aliases = nil) # :nodoc:
|
900
1059
|
@arel ||= build_arel(aliases)
|
901
1060
|
end
|
902
1061
|
|
903
|
-
|
904
|
-
|
905
|
-
|
1062
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1063
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1064
|
+
klass, table, associations, join_type
|
1065
|
+
)
|
906
1066
|
end
|
907
1067
|
|
908
1068
|
protected
|
1069
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1070
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
1071
|
+
|
1072
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1073
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1074
|
+
end
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1078
|
+
opts = sanitize_forbidden_attributes(opts)
|
1079
|
+
|
1080
|
+
case opts
|
1081
|
+
when String, Array
|
1082
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1083
|
+
when Hash
|
1084
|
+
opts = opts.transform_keys do |key|
|
1085
|
+
key = key.to_s
|
1086
|
+
klass.attribute_aliases[key] || key
|
1087
|
+
end
|
1088
|
+
references = PredicateBuilder.references(opts)
|
1089
|
+
self.references_values |= references unless references.empty?
|
1090
|
+
|
1091
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1092
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1093
|
+
end
|
1094
|
+
when Arel::Nodes::Node
|
1095
|
+
parts = [opts]
|
1096
|
+
else
|
1097
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1098
|
+
end
|
909
1099
|
|
910
|
-
|
911
|
-
def set_value(name, value) # :nodoc:
|
912
|
-
assert_mutability!
|
913
|
-
@values[name] = value
|
1100
|
+
Relation::WhereClause.new(parts)
|
914
1101
|
end
|
1102
|
+
alias :build_having_clause :build_where_clause
|
915
1103
|
|
916
1104
|
private
|
1105
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
|
+
each_join_dependencies do |join|
|
1107
|
+
return join.base_klass if table_name == join.table_name
|
1108
|
+
end
|
1109
|
+
nil
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1113
|
+
join_dependencies.each do |join_dependency|
|
1114
|
+
join_dependency.each do |join|
|
1115
|
+
yield join
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
def build_join_dependencies
|
1121
|
+
associations = joins_values | left_outer_joins_values
|
1122
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1123
|
+
associations |= includes_values unless includes_values.empty?
|
1124
|
+
|
1125
|
+
join_dependencies = []
|
1126
|
+
join_dependencies.unshift construct_join_dependency(
|
1127
|
+
select_association_list(associations, join_dependencies), nil
|
1128
|
+
)
|
1129
|
+
end
|
917
1130
|
|
918
1131
|
def assert_mutability!
|
919
1132
|
raise ImmutableRelation if @loaded
|
920
1133
|
raise ImmutableRelation if defined?(@arel) && @arel
|
921
1134
|
end
|
922
1135
|
|
923
|
-
def build_arel(aliases)
|
1136
|
+
def build_arel(aliases = nil)
|
924
1137
|
arel = Arel::SelectManager.new(table)
|
925
1138
|
|
926
|
-
|
927
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
|
1139
|
+
build_joins(arel.join_sources, aliases)
|
928
1140
|
|
929
1141
|
arel.where(where_clause.ast) unless where_clause.empty?
|
930
1142
|
arel.having(having_clause.ast) unless having_clause.empty?
|
931
|
-
if limit_value
|
932
|
-
|
933
|
-
|
934
|
-
connection.sanitize_limit(limit_value),
|
935
|
-
Type.default_value,
|
936
|
-
)
|
937
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
938
|
-
end
|
939
|
-
if offset_value
|
940
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
941
|
-
"OFFSET".freeze,
|
942
|
-
offset_value.to_i,
|
943
|
-
Type.default_value,
|
944
|
-
)
|
945
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
946
|
-
end
|
947
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
1143
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1144
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1145
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
948
1146
|
|
949
1147
|
build_order(arel)
|
950
|
-
|
951
1148
|
build_select(arel)
|
952
1149
|
|
1150
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
953
1151
|
arel.distinct(distinct_value)
|
954
1152
|
arel.from(build_from) unless from_clause.empty?
|
955
1153
|
arel.lock(lock_value) if lock_value
|
956
1154
|
|
1155
|
+
unless annotate_values.empty?
|
1156
|
+
annotates = annotate_values
|
1157
|
+
annotates = annotates.uniq if annotates.size > 1
|
1158
|
+
unless annotates == annotate_values
|
1159
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1160
|
+
Duplicated query annotations are no longer shown in queries in Rails 7.0.
|
1161
|
+
To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1162
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1163
|
+
MSG
|
1164
|
+
annotates = annotate_values
|
1165
|
+
end
|
1166
|
+
arel.comment(*annotates)
|
1167
|
+
end
|
1168
|
+
|
957
1169
|
arel
|
958
1170
|
end
|
959
1171
|
|
1172
|
+
def build_cast_value(name, value)
|
1173
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1174
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1175
|
+
end
|
1176
|
+
|
960
1177
|
def build_from
|
961
1178
|
opts = from_clause.value
|
962
1179
|
name = from_clause.name
|
@@ -972,75 +1189,101 @@ module ActiveRecord
|
|
972
1189
|
end
|
973
1190
|
end
|
974
1191
|
|
975
|
-
def
|
976
|
-
|
977
|
-
|
1192
|
+
def select_association_list(associations, stashed_joins = nil)
|
1193
|
+
result = []
|
1194
|
+
associations.each do |association|
|
1195
|
+
case association
|
978
1196
|
when Hash, Symbol, Array
|
979
|
-
|
1197
|
+
result << association
|
980
1198
|
when ActiveRecord::Associations::JoinDependency
|
981
|
-
|
1199
|
+
stashed_joins&.<< association
|
982
1200
|
else
|
983
|
-
|
1201
|
+
yield association if block_given?
|
984
1202
|
end
|
985
1203
|
end
|
1204
|
+
result
|
1205
|
+
end
|
986
1206
|
|
987
|
-
|
1207
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
988
1208
|
end
|
989
1209
|
|
990
|
-
def
|
991
|
-
buckets =
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
:
|
1210
|
+
def build_join_buckets
|
1211
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1212
|
+
|
1213
|
+
unless left_outer_joins_values.empty?
|
1214
|
+
stashed_left_joins = []
|
1215
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1216
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
if joins_values.empty?
|
1220
|
+
buckets[:association_join] = left_joins
|
1221
|
+
buckets[:stashed_join] = stashed_left_joins
|
1222
|
+
return buckets, Arel::Nodes::OuterJoin
|
1223
|
+
else
|
1224
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
joins = joins_values.dup
|
1229
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1230
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
joins.each_with_index do |join, i|
|
1234
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1238
|
+
join_node = joins.shift
|
1239
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1240
|
+
buckets[:join_node] << join_node
|
1241
|
+
else
|
1242
|
+
buckets[:leading_join] << join_node
|
1243
|
+
end
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1247
|
+
if join.is_a?(Arel::Nodes::Join)
|
1248
|
+
buckets[:join_node] << join
|
1001
1249
|
else
|
1002
1250
|
raise "unknown class: %s" % join.class.name
|
1003
1251
|
end
|
1004
1252
|
end
|
1005
1253
|
|
1006
|
-
|
1254
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1255
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1256
|
+
|
1257
|
+
return buckets, Arel::Nodes::InnerJoin
|
1007
1258
|
end
|
1008
1259
|
|
1009
|
-
def
|
1010
|
-
|
1260
|
+
def build_joins(join_sources, aliases = nil)
|
1261
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1262
|
+
|
1263
|
+
buckets, join_type = build_join_buckets
|
1011
1264
|
|
1012
1265
|
association_joins = buckets[:association_join]
|
1013
1266
|
stashed_joins = buckets[:stashed_join]
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
join_list = join_nodes + convert_join_strings_to_ast(string_joins)
|
1018
|
-
alias_tracker = alias_tracker(join_list, aliases)
|
1019
|
-
|
1020
|
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(
|
1021
|
-
klass, table, association_joins
|
1022
|
-
)
|
1267
|
+
leading_joins = buckets[:leading_join]
|
1268
|
+
join_nodes = buckets[:join_node]
|
1023
1269
|
|
1024
|
-
|
1025
|
-
joins.each { |join| manager.from(join) }
|
1270
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1026
1271
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1272
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1273
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1274
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1275
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1276
|
+
end
|
1031
1277
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
.flatten
|
1035
|
-
.reject(&:blank?)
|
1036
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
1278
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1279
|
+
join_sources
|
1037
1280
|
end
|
1038
1281
|
|
1039
1282
|
def build_select(arel)
|
1040
1283
|
if select_values.any?
|
1041
|
-
arel.project(*arel_columns(select_values
|
1284
|
+
arel.project(*arel_columns(select_values))
|
1042
1285
|
elsif klass.ignored_columns.any?
|
1043
|
-
arel.project(*klass.column_names.map { |field|
|
1286
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1044
1287
|
else
|
1045
1288
|
arel.project(table[Arel.star])
|
1046
1289
|
end
|
@@ -1064,23 +1307,30 @@ module ActiveRecord
|
|
1064
1307
|
end
|
1065
1308
|
|
1066
1309
|
def arel_column(field)
|
1067
|
-
field = klass.
|
1310
|
+
field = klass.attribute_aliases[field] || field
|
1068
1311
|
from = from_clause.name || from_clause.value
|
1069
1312
|
|
1070
1313
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1071
|
-
|
1314
|
+
table[field]
|
1315
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1316
|
+
table, column = field.split(".")
|
1317
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1318
|
+
lookup_table_klass_from_join_dependencies(table)
|
1319
|
+
end
|
1072
1320
|
else
|
1073
1321
|
yield field
|
1074
1322
|
end
|
1075
1323
|
end
|
1076
1324
|
|
1077
1325
|
def table_name_matches?(from)
|
1078
|
-
|
1326
|
+
table_name = Regexp.escape(table.name)
|
1327
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1328
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1079
1329
|
end
|
1080
1330
|
|
1081
1331
|
def reverse_sql_order(order_query)
|
1082
1332
|
if order_query.empty?
|
1083
|
-
return [
|
1333
|
+
return [table[primary_key].desc] if primary_key
|
1084
1334
|
raise IrreversibleOrderError,
|
1085
1335
|
"Relation has no current order and table has no primary key to be used as default order"
|
1086
1336
|
end
|
@@ -1091,9 +1341,11 @@ module ActiveRecord
|
|
1091
1341
|
o.desc
|
1092
1342
|
when Arel::Nodes::Ordering
|
1093
1343
|
o.reverse
|
1344
|
+
when Arel::Nodes::NodeExpression
|
1345
|
+
o.desc
|
1094
1346
|
when String
|
1095
1347
|
if does_not_support_reverse?(o)
|
1096
|
-
raise IrreversibleOrderError, "Order #{o.inspect}
|
1348
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1097
1349
|
end
|
1098
1350
|
o.split(",").map! do |s|
|
1099
1351
|
s.strip!
|
@@ -1113,13 +1365,11 @@ module ActiveRecord
|
|
1113
1365
|
# Uses SQL function with multiple arguments.
|
1114
1366
|
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1115
1367
|
# Uses "nulls first" like construction.
|
1116
|
-
|
1368
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1117
1369
|
end
|
1118
1370
|
|
1119
1371
|
def build_order(arel)
|
1120
|
-
orders = order_values.
|
1121
|
-
orders.reject!(&:blank?)
|
1122
|
-
|
1372
|
+
orders = order_values.compact_blank
|
1123
1373
|
arel.order(*orders) unless orders.empty?
|
1124
1374
|
end
|
1125
1375
|
|
@@ -1139,21 +1389,15 @@ module ActiveRecord
|
|
1139
1389
|
end
|
1140
1390
|
|
1141
1391
|
def preprocess_order_args(order_args)
|
1142
|
-
|
1143
|
-
klass.sanitize_sql_for_order(arg)
|
1144
|
-
end
|
1145
|
-
order_args.flatten!
|
1146
|
-
|
1147
|
-
@klass.enforce_raw_sql_whitelist(
|
1392
|
+
@klass.disallow_raw_sql!(
|
1148
1393
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1149
|
-
|
1394
|
+
permit: connection.column_name_with_order_matcher
|
1150
1395
|
)
|
1151
1396
|
|
1152
1397
|
validate_order_args(order_args)
|
1153
1398
|
|
1154
|
-
references = order_args
|
1155
|
-
|
1156
|
-
references!(references) if references.any?
|
1399
|
+
references = column_references(order_args)
|
1400
|
+
self.references_values |= references unless references.empty?
|
1157
1401
|
|
1158
1402
|
# if a symbol is given we prepend the quoted table name
|
1159
1403
|
order_args.map! do |arg|
|
@@ -1164,9 +1408,9 @@ module ActiveRecord
|
|
1164
1408
|
arg.map { |field, dir|
|
1165
1409
|
case field
|
1166
1410
|
when Arel::Nodes::SqlLiteral
|
1167
|
-
field.
|
1411
|
+
field.public_send(dir.downcase)
|
1168
1412
|
else
|
1169
|
-
order_column(field.to_s).
|
1413
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1170
1414
|
end
|
1171
1415
|
}
|
1172
1416
|
else
|
@@ -1175,16 +1419,54 @@ module ActiveRecord
|
|
1175
1419
|
end.flatten!
|
1176
1420
|
end
|
1177
1421
|
|
1422
|
+
def sanitize_order_arguments(order_args)
|
1423
|
+
order_args.map! do |arg|
|
1424
|
+
klass.sanitize_sql_for_order(arg)
|
1425
|
+
end
|
1426
|
+
order_args.flatten!
|
1427
|
+
order_args.compact_blank!
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
def column_references(order_args)
|
1431
|
+
references = order_args.grep(String)
|
1432
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1433
|
+
references
|
1434
|
+
end
|
1435
|
+
|
1178
1436
|
def order_column(field)
|
1179
1437
|
arel_column(field) do |attr_name|
|
1180
1438
|
if attr_name == "count" && !group_values.empty?
|
1181
|
-
|
1439
|
+
table[attr_name]
|
1182
1440
|
else
|
1183
1441
|
Arel.sql(connection.quote_table_name(attr_name))
|
1184
1442
|
end
|
1185
1443
|
end
|
1186
1444
|
end
|
1187
1445
|
|
1446
|
+
def resolve_arel_attributes(attrs)
|
1447
|
+
attrs.flat_map do |attr|
|
1448
|
+
case attr
|
1449
|
+
when Arel::Predications
|
1450
|
+
attr
|
1451
|
+
when Hash
|
1452
|
+
attr.flat_map do |table, columns|
|
1453
|
+
table = table.to_s
|
1454
|
+
Array(columns).map do |column|
|
1455
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
else
|
1459
|
+
attr = attr.to_s
|
1460
|
+
if attr.include?(".")
|
1461
|
+
table, column = attr.split(".", 2)
|
1462
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1463
|
+
else
|
1464
|
+
attr
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1188
1470
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1189
1471
|
# blank-like object were initially passed into the query method, then this
|
1190
1472
|
# method will not raise an error.
|
@@ -1201,33 +1483,40 @@ module ActiveRecord
|
|
1201
1483
|
# check_if_method_has_arguments!("references", args)
|
1202
1484
|
# ...
|
1203
1485
|
# end
|
1204
|
-
def check_if_method_has_arguments!(method_name, args)
|
1486
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1205
1487
|
if args.blank?
|
1206
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1488
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
+
elsif block_given?
|
1490
|
+
yield args
|
1491
|
+
else
|
1492
|
+
args.flatten!
|
1493
|
+
args.compact_blank!
|
1207
1494
|
end
|
1208
1495
|
end
|
1209
1496
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1497
|
+
STRUCTURAL_VALUE_METHODS = (
|
1498
|
+
Relation::VALUE_METHODS -
|
1499
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1500
|
+
).freeze # :nodoc:
|
1501
|
+
|
1502
|
+
def structurally_incompatible_values_for(other)
|
1503
|
+
values = other.values
|
1504
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1505
|
+
v1, v2 = @values[method], values[method]
|
1506
|
+
if v1.is_a?(Array)
|
1507
|
+
next true unless v2.is_a?(Array)
|
1508
|
+
v1 = v1.uniq
|
1509
|
+
v2 = v2.uniq
|
1510
|
+
end
|
1511
|
+
v1 == v2
|
1214
1512
|
end
|
1215
1513
|
end
|
1514
|
+
end
|
1216
1515
|
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
DEFAULT_VALUES = {
|
1223
|
-
create_with: FROZEN_EMPTY_HASH,
|
1224
|
-
where: Relation::WhereClause.empty,
|
1225
|
-
having: Relation::WhereClause.empty,
|
1226
|
-
from: Relation::FromClause.empty
|
1227
|
-
}
|
1228
|
-
|
1229
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1230
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1231
|
-
end
|
1516
|
+
class Relation # :nodoc:
|
1517
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1518
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1519
|
+
class WhereClauseFactory # :nodoc:
|
1520
|
+
end
|
1232
1521
|
end
|
1233
1522
|
end
|