activerecord 5.2.3 → 6.1.0
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 +898 -532
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +5 -4
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +95 -42
- data/lib/active_record/associations/association_scope.rb +21 -21
- data/lib/active_record/associations/belongs_to_association.rb +50 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
- data/lib/active_record/associations/builder/association.rb +23 -21
- data/lib/active_record/associations/builder/belongs_to.rb +29 -59
- data/lib/active_record/associations/builder/collection_association.rb +10 -19
- 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 +31 -29
- 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 +27 -28
- 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 +54 -12
- 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 +71 -43
- data/lib/active_record/associations/preloader/through_association.rb +49 -40
- data/lib/active_record/associations/preloader.rb +48 -35
- 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 +133 -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 +45 -8
- data/lib/active_record/autosave_association.rb +76 -47
- 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 +293 -132
- 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 +21 -17
- 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 +203 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
- data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
- 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 +31 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -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 +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -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 +63 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -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/money.rb +2 -2
- 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 +222 -112
- data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -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 +175 -187
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_handling.rb +285 -33
- data/lib/active_record/core.rb +308 -100
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -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 +272 -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 +71 -17
- 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 +197 -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 +208 -0
- data/lib/active_record/integration.rb +67 -17
- data/lib/active_record/internal_metadata.rb +26 -9
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +26 -22
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +34 -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 +141 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +205 -156
- 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 +115 -58
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +402 -78
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +113 -101
- 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 -93
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +65 -40
- 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 +4 -7
- 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 +58 -40
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +487 -199
- 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 +108 -58
- data/lib/active_record/relation.rb +375 -104
- 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 +6 -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 +51 -8
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +39 -43
- data/lib/active_record/tasks/database_tasks.rb +276 -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 +246 -0
- data/lib/active_record/timestamp.rb +43 -32
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +59 -117
- 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 +72 -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 +117 -32
- 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,16 +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
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
239
299
|
self
|
240
300
|
end
|
241
301
|
|
@@ -265,8 +325,6 @@ module ActiveRecord
|
|
265
325
|
end
|
266
326
|
|
267
327
|
def group!(*args) # :nodoc:
|
268
|
-
args.flatten!
|
269
|
-
|
270
328
|
self.group_values += args
|
271
329
|
self
|
272
330
|
end
|
@@ -291,15 +349,16 @@ module ActiveRecord
|
|
291
349
|
# User.order('name DESC, email')
|
292
350
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
293
351
|
def order(*args)
|
294
|
-
check_if_method_has_arguments!(:order, args)
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
353
|
+
sanitize_order_arguments(args)
|
354
|
+
end
|
295
355
|
spawn.order!(*args)
|
296
356
|
end
|
297
357
|
|
298
358
|
# Same as #order but operates on relation in-place instead of copying.
|
299
359
|
def order!(*args) # :nodoc:
|
300
|
-
preprocess_order_args(args)
|
301
|
-
|
302
|
-
self.order_values += args
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
361
|
+
self.order_values |= args
|
303
362
|
self
|
304
363
|
end
|
305
364
|
|
@@ -313,22 +372,24 @@ module ActiveRecord
|
|
313
372
|
#
|
314
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
315
374
|
def reorder(*args)
|
316
|
-
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
|
317
378
|
spawn.reorder!(*args)
|
318
379
|
end
|
319
380
|
|
320
381
|
# Same as #reorder but operates on relation in-place instead of copying.
|
321
382
|
def reorder!(*args) # :nodoc:
|
322
|
-
preprocess_order_args(args)
|
323
|
-
|
383
|
+
preprocess_order_args(args) unless args.all?(&:blank?)
|
384
|
+
args.uniq!
|
324
385
|
self.reordering_value = true
|
325
386
|
self.order_values = args
|
326
387
|
self
|
327
388
|
end
|
328
389
|
|
329
390
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
330
|
-
:limit, :offset, :joins, :left_outer_joins,
|
331
|
-
:includes, :from, :readonly, :having])
|
391
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
392
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
332
393
|
|
333
394
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
334
395
|
# This is useful when passing around chains of relations and would like to
|
@@ -369,7 +430,6 @@ module ActiveRecord
|
|
369
430
|
end
|
370
431
|
|
371
432
|
def unscope!(*args) # :nodoc:
|
372
|
-
args.flatten!
|
373
433
|
self.unscope_values += args
|
374
434
|
|
375
435
|
args.each do |scope|
|
@@ -379,14 +439,15 @@ module ActiveRecord
|
|
379
439
|
if !VALID_UNSCOPING_VALUES.include?(scope)
|
380
440
|
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
381
441
|
end
|
382
|
-
|
442
|
+
assert_mutability!
|
443
|
+
@values.delete(scope)
|
383
444
|
when Hash
|
384
445
|
scope.each do |key, target_value|
|
385
446
|
if key != :where
|
386
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
387
448
|
end
|
388
449
|
|
389
|
-
target_values = Array(target_value)
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
390
451
|
self.where_clause = where_clause.except(*target_values)
|
391
452
|
end
|
392
453
|
else
|
@@ -419,8 +480,7 @@ module ActiveRecord
|
|
419
480
|
# # SELECT "users".*
|
420
481
|
# # FROM "users"
|
421
482
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
422
|
-
# # INNER JOIN "comments" "
|
423
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
424
484
|
#
|
425
485
|
# You can use strings in order to customize your joins:
|
426
486
|
#
|
@@ -432,9 +492,7 @@ module ActiveRecord
|
|
432
492
|
end
|
433
493
|
|
434
494
|
def joins!(*args) # :nodoc:
|
435
|
-
args
|
436
|
-
args.flatten!
|
437
|
-
self.joins_values += args
|
495
|
+
self.joins_values |= args
|
438
496
|
self
|
439
497
|
end
|
440
498
|
|
@@ -450,9 +508,7 @@ module ActiveRecord
|
|
450
508
|
alias :left_joins :left_outer_joins
|
451
509
|
|
452
510
|
def left_outer_joins!(*args) # :nodoc:
|
453
|
-
args
|
454
|
-
args.flatten!
|
455
|
-
self.left_outer_joins_values += args
|
511
|
+
self.left_outer_joins_values |= args
|
456
512
|
self
|
457
513
|
end
|
458
514
|
|
@@ -575,20 +631,18 @@ module ActiveRecord
|
|
575
631
|
#
|
576
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
577
633
|
# the current relation.
|
578
|
-
def where(
|
579
|
-
if
|
634
|
+
def where(*args)
|
635
|
+
if args.empty?
|
580
636
|
WhereChain.new(spawn)
|
581
|
-
elsif
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
582
638
|
self
|
583
639
|
else
|
584
|
-
spawn.where!(
|
640
|
+
spawn.where!(*args)
|
585
641
|
end
|
586
642
|
end
|
587
643
|
|
588
644
|
def where!(opts, *rest) # :nodoc:
|
589
|
-
|
590
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
591
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
592
646
|
self
|
593
647
|
end
|
594
648
|
|
@@ -606,7 +660,44 @@ module ActiveRecord
|
|
606
660
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
607
661
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
608
662
|
def rewhere(conditions)
|
609
|
-
|
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
|
610
701
|
end
|
611
702
|
|
612
703
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
@@ -614,21 +705,21 @@ module ActiveRecord
|
|
614
705
|
#
|
615
706
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
616
707
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
617
|
-
# present).
|
708
|
+
# present).
|
618
709
|
#
|
619
710
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
620
711
|
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
621
712
|
#
|
622
713
|
def or(other)
|
623
|
-
|
714
|
+
if other.is_a?(Relation)
|
715
|
+
spawn.or!(other)
|
716
|
+
else
|
624
717
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
625
718
|
end
|
626
|
-
|
627
|
-
spawn.or!(other)
|
628
719
|
end
|
629
720
|
|
630
721
|
def or!(other) # :nodoc:
|
631
|
-
incompatible_values =
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
632
723
|
|
633
724
|
unless incompatible_values.empty?
|
634
725
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
@@ -636,7 +727,7 @@ module ActiveRecord
|
|
636
727
|
|
637
728
|
self.where_clause = self.where_clause.or(other.where_clause)
|
638
729
|
self.having_clause = having_clause.or(other.having_clause)
|
639
|
-
self.references_values
|
730
|
+
self.references_values |= other.references_values
|
640
731
|
|
641
732
|
self
|
642
733
|
end
|
@@ -650,10 +741,7 @@ module ActiveRecord
|
|
650
741
|
end
|
651
742
|
|
652
743
|
def having!(opts, *rest) # :nodoc:
|
653
|
-
|
654
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
655
|
-
|
656
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
657
745
|
self
|
658
746
|
end
|
659
747
|
|
@@ -755,6 +843,21 @@ module ActiveRecord
|
|
755
843
|
self
|
756
844
|
end
|
757
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
|
+
|
758
861
|
# Sets attributes to be used when creating new records from a
|
759
862
|
# relation object.
|
760
863
|
#
|
@@ -879,6 +982,27 @@ module ActiveRecord
|
|
879
982
|
self
|
880
983
|
end
|
881
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
|
+
|
882
1006
|
# Reverse the existing order clause on the relation.
|
883
1007
|
#
|
884
1008
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -887,8 +1011,7 @@ module ActiveRecord
|
|
887
1011
|
end
|
888
1012
|
|
889
1013
|
def reverse_order! # :nodoc:
|
890
|
-
orders = order_values.
|
891
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
892
1015
|
self.order_values = reverse_sql_order(orders)
|
893
1016
|
self
|
894
1017
|
end
|
@@ -898,25 +1021,109 @@ module ActiveRecord
|
|
898
1021
|
self
|
899
1022
|
end
|
900
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
|
+
|
901
1057
|
# Returns the Arel object associated with the relation.
|
902
1058
|
def arel(aliases = nil) # :nodoc:
|
903
1059
|
@arel ||= build_arel(aliases)
|
904
1060
|
end
|
905
1061
|
|
906
|
-
|
907
|
-
|
908
|
-
|
1062
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1063
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1064
|
+
klass, table, associations, join_type
|
1065
|
+
)
|
909
1066
|
end
|
910
1067
|
|
911
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.stringify_keys
|
1085
|
+
references = PredicateBuilder.references(opts)
|
1086
|
+
self.references_values |= references unless references.empty?
|
1087
|
+
|
1088
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1089
|
+
lookup_reflection_from_join_dependencies(table_name)
|
1090
|
+
end
|
1091
|
+
when Arel::Nodes::Node
|
1092
|
+
parts = [opts]
|
1093
|
+
else
|
1094
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1095
|
+
end
|
912
1096
|
|
913
|
-
|
914
|
-
def set_value(name, value) # :nodoc:
|
915
|
-
assert_mutability!
|
916
|
-
@values[name] = value
|
1097
|
+
Relation::WhereClause.new(parts)
|
917
1098
|
end
|
1099
|
+
alias :build_having_clause :build_where_clause
|
918
1100
|
|
919
1101
|
private
|
1102
|
+
def lookup_reflection_from_join_dependencies(table_name)
|
1103
|
+
each_join_dependencies do |join|
|
1104
|
+
return join.reflection if table_name == join.table_name
|
1105
|
+
end
|
1106
|
+
nil
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1110
|
+
join_dependencies.each do |join_dependency|
|
1111
|
+
join_dependency.each do |join|
|
1112
|
+
yield join
|
1113
|
+
end
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
def build_join_dependencies
|
1118
|
+
associations = joins_values | left_outer_joins_values
|
1119
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1120
|
+
associations |= includes_values unless includes_values.empty?
|
1121
|
+
|
1122
|
+
join_dependencies = []
|
1123
|
+
join_dependencies.unshift construct_join_dependency(
|
1124
|
+
select_association_list(associations, join_dependencies), nil
|
1125
|
+
)
|
1126
|
+
end
|
920
1127
|
|
921
1128
|
def assert_mutability!
|
922
1129
|
raise ImmutableRelation if @loaded
|
@@ -926,40 +1133,44 @@ module ActiveRecord
|
|
926
1133
|
def build_arel(aliases)
|
927
1134
|
arel = Arel::SelectManager.new(table)
|
928
1135
|
|
929
|
-
|
930
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
|
1136
|
+
build_joins(arel.join_sources, aliases)
|
931
1137
|
|
932
1138
|
arel.where(where_clause.ast) unless where_clause.empty?
|
933
1139
|
arel.having(having_clause.ast) unless having_clause.empty?
|
934
|
-
if limit_value
|
935
|
-
|
936
|
-
|
937
|
-
connection.sanitize_limit(limit_value),
|
938
|
-
Type.default_value,
|
939
|
-
)
|
940
|
-
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
941
|
-
end
|
942
|
-
if offset_value
|
943
|
-
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
944
|
-
"OFFSET".freeze,
|
945
|
-
offset_value.to_i,
|
946
|
-
Type.default_value,
|
947
|
-
)
|
948
|
-
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
949
|
-
end
|
950
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
1140
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1141
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1142
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
951
1143
|
|
952
1144
|
build_order(arel)
|
953
|
-
|
954
1145
|
build_select(arel)
|
955
1146
|
|
1147
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
956
1148
|
arel.distinct(distinct_value)
|
957
1149
|
arel.from(build_from) unless from_clause.empty?
|
958
1150
|
arel.lock(lock_value) if lock_value
|
959
1151
|
|
1152
|
+
unless annotate_values.empty?
|
1153
|
+
annotates = annotate_values
|
1154
|
+
annotates = annotates.uniq if annotates.size > 1
|
1155
|
+
unless annotates == annotate_values
|
1156
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1157
|
+
Duplicated query annotations are no longer shown in queries in Rails 6.2.
|
1158
|
+
To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1159
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1160
|
+
MSG
|
1161
|
+
annotates = annotate_values
|
1162
|
+
end
|
1163
|
+
arel.comment(*annotates)
|
1164
|
+
end
|
1165
|
+
|
960
1166
|
arel
|
961
1167
|
end
|
962
1168
|
|
1169
|
+
def build_cast_value(name, value)
|
1170
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1171
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1172
|
+
end
|
1173
|
+
|
963
1174
|
def build_from
|
964
1175
|
opts = from_clause.value
|
965
1176
|
name = from_clause.name
|
@@ -975,75 +1186,101 @@ module ActiveRecord
|
|
975
1186
|
end
|
976
1187
|
end
|
977
1188
|
|
978
|
-
def
|
979
|
-
|
980
|
-
|
1189
|
+
def select_association_list(associations, stashed_joins = nil)
|
1190
|
+
result = []
|
1191
|
+
associations.each do |association|
|
1192
|
+
case association
|
981
1193
|
when Hash, Symbol, Array
|
982
|
-
|
1194
|
+
result << association
|
983
1195
|
when ActiveRecord::Associations::JoinDependency
|
984
|
-
|
1196
|
+
stashed_joins&.<< association
|
985
1197
|
else
|
986
|
-
|
1198
|
+
yield association if block_given?
|
987
1199
|
end
|
988
1200
|
end
|
1201
|
+
result
|
1202
|
+
end
|
989
1203
|
|
990
|
-
|
1204
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
991
1205
|
end
|
992
1206
|
|
993
|
-
def
|
994
|
-
buckets =
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
:
|
1207
|
+
def build_join_buckets
|
1208
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1209
|
+
|
1210
|
+
unless left_outer_joins_values.empty?
|
1211
|
+
stashed_left_joins = []
|
1212
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1213
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
if joins_values.empty?
|
1217
|
+
buckets[:association_join] = left_joins
|
1218
|
+
buckets[:stashed_join] = stashed_left_joins
|
1219
|
+
return buckets, Arel::Nodes::OuterJoin
|
1220
|
+
else
|
1221
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1222
|
+
end
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
joins = joins_values.dup
|
1226
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1227
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
joins.each_with_index do |join, i|
|
1231
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1235
|
+
join_node = joins.shift
|
1236
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1237
|
+
buckets[:join_node] << join_node
|
1238
|
+
else
|
1239
|
+
buckets[:leading_join] << join_node
|
1240
|
+
end
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1244
|
+
if join.is_a?(Arel::Nodes::Join)
|
1245
|
+
buckets[:join_node] << join
|
1004
1246
|
else
|
1005
1247
|
raise "unknown class: %s" % join.class.name
|
1006
1248
|
end
|
1007
1249
|
end
|
1008
1250
|
|
1009
|
-
|
1251
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1252
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1253
|
+
|
1254
|
+
return buckets, Arel::Nodes::InnerJoin
|
1010
1255
|
end
|
1011
1256
|
|
1012
|
-
def
|
1013
|
-
|
1257
|
+
def build_joins(join_sources, aliases = nil)
|
1258
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1259
|
+
|
1260
|
+
buckets, join_type = build_join_buckets
|
1014
1261
|
|
1015
1262
|
association_joins = buckets[:association_join]
|
1016
1263
|
stashed_joins = buckets[:stashed_join]
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
join_list = join_nodes + convert_join_strings_to_ast(string_joins)
|
1021
|
-
alias_tracker = alias_tracker(join_list, aliases)
|
1264
|
+
leading_joins = buckets[:leading_join]
|
1265
|
+
join_nodes = buckets[:join_node]
|
1022
1266
|
|
1023
|
-
|
1024
|
-
klass, table, association_joins
|
1025
|
-
)
|
1026
|
-
|
1027
|
-
joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
|
1028
|
-
joins.each { |join| manager.from(join) }
|
1029
|
-
|
1030
|
-
manager.join_sources.concat(join_list)
|
1267
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1031
1268
|
|
1032
|
-
|
1033
|
-
|
1269
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1270
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1271
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1272
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1273
|
+
end
|
1034
1274
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
.flatten
|
1038
|
-
.reject(&:blank?)
|
1039
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
1275
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1276
|
+
join_sources
|
1040
1277
|
end
|
1041
1278
|
|
1042
1279
|
def build_select(arel)
|
1043
1280
|
if select_values.any?
|
1044
|
-
arel.project(*arel_columns(select_values
|
1281
|
+
arel.project(*arel_columns(select_values))
|
1045
1282
|
elsif klass.ignored_columns.any?
|
1046
|
-
arel.project(*klass.column_names.map { |field|
|
1283
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1047
1284
|
else
|
1048
1285
|
arel.project(table[Arel.star])
|
1049
1286
|
end
|
@@ -1053,10 +1290,11 @@ module ActiveRecord
|
|
1053
1290
|
columns.flat_map do |field|
|
1054
1291
|
case field
|
1055
1292
|
when Symbol
|
1056
|
-
field
|
1057
|
-
|
1293
|
+
arel_column(field.to_s) do |attr_name|
|
1294
|
+
connection.quote_table_name(attr_name)
|
1295
|
+
end
|
1058
1296
|
when String
|
1059
|
-
arel_column(field)
|
1297
|
+
arel_column(field, &:itself)
|
1060
1298
|
when Proc
|
1061
1299
|
field.call
|
1062
1300
|
else
|
@@ -1066,23 +1304,30 @@ module ActiveRecord
|
|
1066
1304
|
end
|
1067
1305
|
|
1068
1306
|
def arel_column(field)
|
1069
|
-
field = klass.
|
1307
|
+
field = klass.attribute_aliases[field] || field
|
1070
1308
|
from = from_clause.name || from_clause.value
|
1071
1309
|
|
1072
1310
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1073
|
-
|
1311
|
+
table[field]
|
1312
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1313
|
+
table, column = field.split(".")
|
1314
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1315
|
+
lookup_reflection_from_join_dependencies(table)
|
1316
|
+
end
|
1074
1317
|
else
|
1075
|
-
yield
|
1318
|
+
yield field
|
1076
1319
|
end
|
1077
1320
|
end
|
1078
1321
|
|
1079
1322
|
def table_name_matches?(from)
|
1080
|
-
|
1323
|
+
table_name = Regexp.escape(table.name)
|
1324
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1325
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1081
1326
|
end
|
1082
1327
|
|
1083
1328
|
def reverse_sql_order(order_query)
|
1084
1329
|
if order_query.empty?
|
1085
|
-
return [
|
1330
|
+
return [table[primary_key].desc] if primary_key
|
1086
1331
|
raise IrreversibleOrderError,
|
1087
1332
|
"Relation has no current order and table has no primary key to be used as default order"
|
1088
1333
|
end
|
@@ -1093,9 +1338,11 @@ module ActiveRecord
|
|
1093
1338
|
o.desc
|
1094
1339
|
when Arel::Nodes::Ordering
|
1095
1340
|
o.reverse
|
1341
|
+
when Arel::Nodes::NodeExpression
|
1342
|
+
o.desc
|
1096
1343
|
when String
|
1097
1344
|
if does_not_support_reverse?(o)
|
1098
|
-
raise IrreversibleOrderError, "Order #{o.inspect}
|
1345
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1099
1346
|
end
|
1100
1347
|
o.split(",").map! do |s|
|
1101
1348
|
s.strip!
|
@@ -1115,13 +1362,11 @@ module ActiveRecord
|
|
1115
1362
|
# Uses SQL function with multiple arguments.
|
1116
1363
|
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1117
1364
|
# Uses "nulls first" like construction.
|
1118
|
-
|
1365
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1119
1366
|
end
|
1120
1367
|
|
1121
1368
|
def build_order(arel)
|
1122
|
-
orders = order_values.
|
1123
|
-
orders.reject!(&:blank?)
|
1124
|
-
|
1369
|
+
orders = order_values.compact_blank
|
1125
1370
|
arel.order(*orders) unless orders.empty?
|
1126
1371
|
end
|
1127
1372
|
|
@@ -1141,40 +1386,28 @@ module ActiveRecord
|
|
1141
1386
|
end
|
1142
1387
|
|
1143
1388
|
def preprocess_order_args(order_args)
|
1144
|
-
|
1145
|
-
klass.sanitize_sql_for_order(arg)
|
1146
|
-
end
|
1147
|
-
order_args.flatten!
|
1148
|
-
|
1149
|
-
@klass.enforce_raw_sql_whitelist(
|
1389
|
+
@klass.disallow_raw_sql!(
|
1150
1390
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1151
|
-
|
1391
|
+
permit: connection.column_name_with_order_matcher
|
1152
1392
|
)
|
1153
1393
|
|
1154
1394
|
validate_order_args(order_args)
|
1155
1395
|
|
1156
|
-
references = order_args
|
1157
|
-
|
1158
|
-
references!(references) if references.any?
|
1396
|
+
references = column_references(order_args)
|
1397
|
+
self.references_values |= references unless references.empty?
|
1159
1398
|
|
1160
1399
|
# if a symbol is given we prepend the quoted table name
|
1161
1400
|
order_args.map! do |arg|
|
1162
1401
|
case arg
|
1163
1402
|
when Symbol
|
1164
|
-
arg
|
1165
|
-
arel_column(arg) {
|
1166
|
-
Arel.sql(connection.quote_table_name(arg))
|
1167
|
-
}.asc
|
1403
|
+
order_column(arg.to_s).asc
|
1168
1404
|
when Hash
|
1169
1405
|
arg.map { |field, dir|
|
1170
1406
|
case field
|
1171
1407
|
when Arel::Nodes::SqlLiteral
|
1172
|
-
field.
|
1408
|
+
field.public_send(dir.downcase)
|
1173
1409
|
else
|
1174
|
-
field
|
1175
|
-
arel_column(field) {
|
1176
|
-
Arel.sql(connection.quote_table_name(field))
|
1177
|
-
}.send(dir.downcase)
|
1410
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1178
1411
|
end
|
1179
1412
|
}
|
1180
1413
|
else
|
@@ -1183,6 +1416,54 @@ module ActiveRecord
|
|
1183
1416
|
end.flatten!
|
1184
1417
|
end
|
1185
1418
|
|
1419
|
+
def sanitize_order_arguments(order_args)
|
1420
|
+
order_args.map! do |arg|
|
1421
|
+
klass.sanitize_sql_for_order(arg)
|
1422
|
+
end
|
1423
|
+
order_args.flatten!
|
1424
|
+
order_args.compact_blank!
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
def column_references(order_args)
|
1428
|
+
references = order_args.grep(String)
|
1429
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1430
|
+
references
|
1431
|
+
end
|
1432
|
+
|
1433
|
+
def order_column(field)
|
1434
|
+
arel_column(field) do |attr_name|
|
1435
|
+
if attr_name == "count" && !group_values.empty?
|
1436
|
+
table[attr_name]
|
1437
|
+
else
|
1438
|
+
Arel.sql(connection.quote_table_name(attr_name))
|
1439
|
+
end
|
1440
|
+
end
|
1441
|
+
end
|
1442
|
+
|
1443
|
+
def resolve_arel_attributes(attrs)
|
1444
|
+
attrs.flat_map do |attr|
|
1445
|
+
case attr
|
1446
|
+
when Arel::Predications
|
1447
|
+
attr
|
1448
|
+
when Hash
|
1449
|
+
attr.flat_map do |table, columns|
|
1450
|
+
table = table.to_s
|
1451
|
+
Array(columns).map do |column|
|
1452
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
else
|
1456
|
+
attr = attr.to_s
|
1457
|
+
if attr.include?(".")
|
1458
|
+
table, column = attr.split(".", 2)
|
1459
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1460
|
+
else
|
1461
|
+
attr
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
|
1186
1467
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1187
1468
|
# blank-like object were initially passed into the query method, then this
|
1188
1469
|
# method will not raise an error.
|
@@ -1199,33 +1480,40 @@ module ActiveRecord
|
|
1199
1480
|
# check_if_method_has_arguments!("references", args)
|
1200
1481
|
# ...
|
1201
1482
|
# end
|
1202
|
-
def check_if_method_has_arguments!(method_name, args)
|
1483
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1203
1484
|
if args.blank?
|
1204
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1485
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1486
|
+
elsif block_given?
|
1487
|
+
yield args
|
1488
|
+
else
|
1489
|
+
args.flatten!
|
1490
|
+
args.compact_blank!
|
1205
1491
|
end
|
1206
1492
|
end
|
1207
1493
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1494
|
+
STRUCTURAL_VALUE_METHODS = (
|
1495
|
+
Relation::VALUE_METHODS -
|
1496
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1497
|
+
).freeze # :nodoc:
|
1498
|
+
|
1499
|
+
def structurally_incompatible_values_for(other)
|
1500
|
+
values = other.values
|
1501
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1502
|
+
v1, v2 = @values[method], values[method]
|
1503
|
+
if v1.is_a?(Array)
|
1504
|
+
next true unless v2.is_a?(Array)
|
1505
|
+
v1 = v1.uniq
|
1506
|
+
v2 = v2.uniq
|
1507
|
+
end
|
1508
|
+
v1 == v2
|
1212
1509
|
end
|
1213
1510
|
end
|
1511
|
+
end
|
1214
1512
|
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
DEFAULT_VALUES = {
|
1221
|
-
create_with: FROZEN_EMPTY_HASH,
|
1222
|
-
where: Relation::WhereClause.empty,
|
1223
|
-
having: Relation::WhereClause.empty,
|
1224
|
-
from: Relation::FromClause.empty
|
1225
|
-
}
|
1226
|
-
|
1227
|
-
Relation::MULTI_VALUE_METHODS.each do |value|
|
1228
|
-
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1229
|
-
end
|
1513
|
+
class Relation # :nodoc:
|
1514
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1515
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1516
|
+
class WhereClauseFactory # :nodoc:
|
1517
|
+
end
|
1230
1518
|
end
|
1231
1519
|
end
|