activerecord 5.2.8.1 → 6.1.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1347 -624
- 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 +16 -7
- 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 +107 -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 +73 -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 +225 -121
- 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 +341 -99
- 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 +4 -4
- 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 +113 -74
- 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 +478 -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 +94 -10
- 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 +291 -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 +118 -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,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,161 @@ 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
|
+
#
|
1039
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
1040
|
+
def annotate(*args)
|
1041
|
+
check_if_method_has_arguments!(:annotate, args)
|
1042
|
+
spawn.annotate!(*args)
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
# Like #annotate, but modifies relation in place.
|
1046
|
+
def annotate!(*args) # :nodoc:
|
1047
|
+
self.annotate_values += args
|
1048
|
+
self
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# Deduplicate multiple values.
|
1052
|
+
def uniq!(name)
|
1053
|
+
if values = @values[name]
|
1054
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1055
|
+
end
|
1056
|
+
self
|
1057
|
+
end
|
1058
|
+
|
898
1059
|
# Returns the Arel object associated with the relation.
|
899
1060
|
def arel(aliases = nil) # :nodoc:
|
900
1061
|
@arel ||= build_arel(aliases)
|
901
1062
|
end
|
902
1063
|
|
903
|
-
|
904
|
-
|
905
|
-
|
1064
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1065
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1066
|
+
klass, table, associations, join_type
|
1067
|
+
)
|
906
1068
|
end
|
907
1069
|
|
908
1070
|
protected
|
1071
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1072
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
909
1073
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
@values[name] = value
|
1074
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1075
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1076
|
+
end
|
914
1077
|
end
|
915
1078
|
|
1079
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1080
|
+
opts = sanitize_forbidden_attributes(opts)
|
1081
|
+
|
1082
|
+
case opts
|
1083
|
+
when String, Array
|
1084
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1085
|
+
when Hash
|
1086
|
+
opts = opts.transform_keys do |key|
|
1087
|
+
key = key.to_s
|
1088
|
+
klass.attribute_aliases[key] || key
|
1089
|
+
end
|
1090
|
+
references = PredicateBuilder.references(opts)
|
1091
|
+
self.references_values |= references unless references.empty?
|
1092
|
+
|
1093
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1094
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1095
|
+
end
|
1096
|
+
when Arel::Nodes::Node
|
1097
|
+
parts = [opts]
|
1098
|
+
else
|
1099
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
Relation::WhereClause.new(parts)
|
1103
|
+
end
|
1104
|
+
alias :build_having_clause :build_where_clause
|
1105
|
+
|
916
1106
|
private
|
1107
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1108
|
+
each_join_dependencies do |join|
|
1109
|
+
return join.base_klass if table_name == join.table_name
|
1110
|
+
end
|
1111
|
+
nil
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1115
|
+
join_dependencies.each do |join_dependency|
|
1116
|
+
join_dependency.each do |join|
|
1117
|
+
yield join
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
def build_join_dependencies
|
1123
|
+
associations = joins_values | left_outer_joins_values
|
1124
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1125
|
+
associations |= includes_values unless includes_values.empty?
|
1126
|
+
|
1127
|
+
join_dependencies = []
|
1128
|
+
join_dependencies.unshift construct_join_dependency(
|
1129
|
+
select_association_list(associations, join_dependencies), nil
|
1130
|
+
)
|
1131
|
+
end
|
917
1132
|
|
918
1133
|
def assert_mutability!
|
919
1134
|
raise ImmutableRelation if @loaded
|
920
1135
|
raise ImmutableRelation if defined?(@arel) && @arel
|
921
1136
|
end
|
922
1137
|
|
923
|
-
def build_arel(aliases)
|
1138
|
+
def build_arel(aliases = nil)
|
924
1139
|
arel = Arel::SelectManager.new(table)
|
925
1140
|
|
926
|
-
|
927
|
-
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
|
1141
|
+
build_joins(arel.join_sources, aliases)
|
928
1142
|
|
929
1143
|
arel.where(where_clause.ast) unless where_clause.empty?
|
930
1144
|
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?
|
1145
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1146
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1147
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
948
1148
|
|
949
1149
|
build_order(arel)
|
950
|
-
|
951
1150
|
build_select(arel)
|
952
1151
|
|
1152
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
953
1153
|
arel.distinct(distinct_value)
|
954
1154
|
arel.from(build_from) unless from_clause.empty?
|
955
1155
|
arel.lock(lock_value) if lock_value
|
956
1156
|
|
1157
|
+
unless annotate_values.empty?
|
1158
|
+
annotates = annotate_values
|
1159
|
+
annotates = annotates.uniq if annotates.size > 1
|
1160
|
+
unless annotates == annotate_values
|
1161
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1162
|
+
Duplicated query annotations are no longer shown in queries in Rails 7.0.
|
1163
|
+
To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1164
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1165
|
+
MSG
|
1166
|
+
annotates = annotate_values
|
1167
|
+
end
|
1168
|
+
arel.comment(*annotates)
|
1169
|
+
end
|
1170
|
+
|
957
1171
|
arel
|
958
1172
|
end
|
959
1173
|
|
1174
|
+
def build_cast_value(name, value)
|
1175
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1176
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1177
|
+
end
|
1178
|
+
|
960
1179
|
def build_from
|
961
1180
|
opts = from_clause.value
|
962
1181
|
name = from_clause.name
|
@@ -972,75 +1191,101 @@ module ActiveRecord
|
|
972
1191
|
end
|
973
1192
|
end
|
974
1193
|
|
975
|
-
def
|
976
|
-
|
977
|
-
|
1194
|
+
def select_association_list(associations, stashed_joins = nil)
|
1195
|
+
result = []
|
1196
|
+
associations.each do |association|
|
1197
|
+
case association
|
978
1198
|
when Hash, Symbol, Array
|
979
|
-
|
1199
|
+
result << association
|
980
1200
|
when ActiveRecord::Associations::JoinDependency
|
981
|
-
|
1201
|
+
stashed_joins&.<< association
|
982
1202
|
else
|
983
|
-
|
1203
|
+
yield association if block_given?
|
984
1204
|
end
|
985
1205
|
end
|
1206
|
+
result
|
1207
|
+
end
|
986
1208
|
|
987
|
-
|
1209
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
988
1210
|
end
|
989
1211
|
|
990
|
-
def
|
991
|
-
buckets =
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
:
|
1212
|
+
def build_join_buckets
|
1213
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1214
|
+
|
1215
|
+
unless left_outer_joins_values.empty?
|
1216
|
+
stashed_left_joins = []
|
1217
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1218
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
if joins_values.empty?
|
1222
|
+
buckets[:association_join] = left_joins
|
1223
|
+
buckets[:stashed_join] = stashed_left_joins
|
1224
|
+
return buckets, Arel::Nodes::OuterJoin
|
1225
|
+
else
|
1226
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1227
|
+
end
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
joins = joins_values.dup
|
1231
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1232
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
joins.each_with_index do |join, i|
|
1236
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1240
|
+
join_node = joins.shift
|
1241
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1242
|
+
buckets[:join_node] << join_node
|
1243
|
+
else
|
1244
|
+
buckets[:leading_join] << join_node
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1249
|
+
if join.is_a?(Arel::Nodes::Join)
|
1250
|
+
buckets[:join_node] << join
|
1001
1251
|
else
|
1002
1252
|
raise "unknown class: %s" % join.class.name
|
1003
1253
|
end
|
1004
1254
|
end
|
1005
1255
|
|
1006
|
-
|
1256
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1257
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1258
|
+
|
1259
|
+
return buckets, Arel::Nodes::InnerJoin
|
1007
1260
|
end
|
1008
1261
|
|
1009
|
-
def
|
1010
|
-
|
1262
|
+
def build_joins(join_sources, aliases = nil)
|
1263
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1264
|
+
|
1265
|
+
buckets, join_type = build_join_buckets
|
1011
1266
|
|
1012
1267
|
association_joins = buckets[:association_join]
|
1013
1268
|
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
|
-
)
|
1269
|
+
leading_joins = buckets[:leading_join]
|
1270
|
+
join_nodes = buckets[:join_node]
|
1023
1271
|
|
1024
|
-
|
1025
|
-
joins.each { |join| manager.from(join) }
|
1272
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1026
1273
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1274
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1275
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1276
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1277
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1278
|
+
end
|
1031
1279
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
.flatten
|
1035
|
-
.reject(&:blank?)
|
1036
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
1280
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1281
|
+
join_sources
|
1037
1282
|
end
|
1038
1283
|
|
1039
1284
|
def build_select(arel)
|
1040
1285
|
if select_values.any?
|
1041
|
-
arel.project(*arel_columns(select_values
|
1286
|
+
arel.project(*arel_columns(select_values))
|
1042
1287
|
elsif klass.ignored_columns.any?
|
1043
|
-
arel.project(*klass.column_names.map { |field|
|
1288
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1044
1289
|
else
|
1045
1290
|
arel.project(table[Arel.star])
|
1046
1291
|
end
|
@@ -1064,23 +1309,30 @@ module ActiveRecord
|
|
1064
1309
|
end
|
1065
1310
|
|
1066
1311
|
def arel_column(field)
|
1067
|
-
field = klass.
|
1312
|
+
field = klass.attribute_aliases[field] || field
|
1068
1313
|
from = from_clause.name || from_clause.value
|
1069
1314
|
|
1070
1315
|
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1071
|
-
|
1316
|
+
table[field]
|
1317
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1318
|
+
table, column = field.split(".")
|
1319
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1320
|
+
lookup_table_klass_from_join_dependencies(table)
|
1321
|
+
end
|
1072
1322
|
else
|
1073
1323
|
yield field
|
1074
1324
|
end
|
1075
1325
|
end
|
1076
1326
|
|
1077
1327
|
def table_name_matches?(from)
|
1078
|
-
|
1328
|
+
table_name = Regexp.escape(table.name)
|
1329
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1330
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1079
1331
|
end
|
1080
1332
|
|
1081
1333
|
def reverse_sql_order(order_query)
|
1082
1334
|
if order_query.empty?
|
1083
|
-
return [
|
1335
|
+
return [table[primary_key].desc] if primary_key
|
1084
1336
|
raise IrreversibleOrderError,
|
1085
1337
|
"Relation has no current order and table has no primary key to be used as default order"
|
1086
1338
|
end
|
@@ -1091,9 +1343,11 @@ module ActiveRecord
|
|
1091
1343
|
o.desc
|
1092
1344
|
when Arel::Nodes::Ordering
|
1093
1345
|
o.reverse
|
1346
|
+
when Arel::Nodes::NodeExpression
|
1347
|
+
o.desc
|
1094
1348
|
when String
|
1095
1349
|
if does_not_support_reverse?(o)
|
1096
|
-
raise IrreversibleOrderError, "Order #{o.inspect}
|
1350
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1097
1351
|
end
|
1098
1352
|
o.split(",").map! do |s|
|
1099
1353
|
s.strip!
|
@@ -1113,13 +1367,11 @@ module ActiveRecord
|
|
1113
1367
|
# Uses SQL function with multiple arguments.
|
1114
1368
|
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1115
1369
|
# Uses "nulls first" like construction.
|
1116
|
-
|
1370
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1117
1371
|
end
|
1118
1372
|
|
1119
1373
|
def build_order(arel)
|
1120
|
-
orders = order_values.
|
1121
|
-
orders.reject!(&:blank?)
|
1122
|
-
|
1374
|
+
orders = order_values.compact_blank
|
1123
1375
|
arel.order(*orders) unless orders.empty?
|
1124
1376
|
end
|
1125
1377
|
|
@@ -1139,21 +1391,15 @@ module ActiveRecord
|
|
1139
1391
|
end
|
1140
1392
|
|
1141
1393
|
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(
|
1394
|
+
@klass.disallow_raw_sql!(
|
1148
1395
|
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1149
|
-
|
1396
|
+
permit: connection.column_name_with_order_matcher
|
1150
1397
|
)
|
1151
1398
|
|
1152
1399
|
validate_order_args(order_args)
|
1153
1400
|
|
1154
|
-
references = order_args
|
1155
|
-
|
1156
|
-
references!(references) if references.any?
|
1401
|
+
references = column_references(order_args)
|
1402
|
+
self.references_values |= references unless references.empty?
|
1157
1403
|
|
1158
1404
|
# if a symbol is given we prepend the quoted table name
|
1159
1405
|
order_args.map! do |arg|
|
@@ -1164,9 +1410,9 @@ module ActiveRecord
|
|
1164
1410
|
arg.map { |field, dir|
|
1165
1411
|
case field
|
1166
1412
|
when Arel::Nodes::SqlLiteral
|
1167
|
-
field.
|
1413
|
+
field.public_send(dir.downcase)
|
1168
1414
|
else
|
1169
|
-
order_column(field.to_s).
|
1415
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1170
1416
|
end
|
1171
1417
|
}
|
1172
1418
|
else
|
@@ -1175,16 +1421,54 @@ module ActiveRecord
|
|
1175
1421
|
end.flatten!
|
1176
1422
|
end
|
1177
1423
|
|
1424
|
+
def sanitize_order_arguments(order_args)
|
1425
|
+
order_args.map! do |arg|
|
1426
|
+
klass.sanitize_sql_for_order(arg)
|
1427
|
+
end
|
1428
|
+
order_args.flatten!
|
1429
|
+
order_args.compact_blank!
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
def column_references(order_args)
|
1433
|
+
references = order_args.grep(String)
|
1434
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1435
|
+
references
|
1436
|
+
end
|
1437
|
+
|
1178
1438
|
def order_column(field)
|
1179
1439
|
arel_column(field) do |attr_name|
|
1180
1440
|
if attr_name == "count" && !group_values.empty?
|
1181
|
-
|
1441
|
+
table[attr_name]
|
1182
1442
|
else
|
1183
1443
|
Arel.sql(connection.quote_table_name(attr_name))
|
1184
1444
|
end
|
1185
1445
|
end
|
1186
1446
|
end
|
1187
1447
|
|
1448
|
+
def resolve_arel_attributes(attrs)
|
1449
|
+
attrs.flat_map do |attr|
|
1450
|
+
case attr
|
1451
|
+
when Arel::Predications
|
1452
|
+
attr
|
1453
|
+
when Hash
|
1454
|
+
attr.flat_map do |table, columns|
|
1455
|
+
table = table.to_s
|
1456
|
+
Array(columns).map do |column|
|
1457
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
else
|
1461
|
+
attr = attr.to_s
|
1462
|
+
if attr.include?(".")
|
1463
|
+
table, column = attr.split(".", 2)
|
1464
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1465
|
+
else
|
1466
|
+
attr
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
end
|
1470
|
+
end
|
1471
|
+
|
1188
1472
|
# Checks to make sure that the arguments are not blank. Note that if some
|
1189
1473
|
# blank-like object were initially passed into the query method, then this
|
1190
1474
|
# method will not raise an error.
|
@@ -1201,33 +1485,40 @@ module ActiveRecord
|
|
1201
1485
|
# check_if_method_has_arguments!("references", args)
|
1202
1486
|
# ...
|
1203
1487
|
# end
|
1204
|
-
def check_if_method_has_arguments!(method_name, args)
|
1488
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1205
1489
|
if args.blank?
|
1206
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1490
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1491
|
+
elsif block_given?
|
1492
|
+
yield args
|
1493
|
+
else
|
1494
|
+
args.flatten!
|
1495
|
+
args.compact_blank!
|
1207
1496
|
end
|
1208
1497
|
end
|
1209
1498
|
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1499
|
+
STRUCTURAL_VALUE_METHODS = (
|
1500
|
+
Relation::VALUE_METHODS -
|
1501
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1502
|
+
).freeze # :nodoc:
|
1503
|
+
|
1504
|
+
def structurally_incompatible_values_for(other)
|
1505
|
+
values = other.values
|
1506
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1507
|
+
v1, v2 = @values[method], values[method]
|
1508
|
+
if v1.is_a?(Array)
|
1509
|
+
next true unless v2.is_a?(Array)
|
1510
|
+
v1 = v1.uniq
|
1511
|
+
v2 = v2.uniq
|
1512
|
+
end
|
1513
|
+
v1 == v2
|
1214
1514
|
end
|
1215
1515
|
end
|
1516
|
+
end
|
1216
1517
|
|
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
|
1518
|
+
class Relation # :nodoc:
|
1519
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1520
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1521
|
+
class WhereClauseFactory # :nodoc:
|
1522
|
+
end
|
1232
1523
|
end
|
1233
1524
|
end
|