activerecord 5.0.7.2 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +829 -2015
- data/MIT-LICENSE +3 -1
- data/README.rdoc +11 -9
- data/examples/performance.rb +31 -29
- data/examples/simple.rb +5 -3
- data/lib/active_record.rb +37 -29
- data/lib/active_record/aggregations.rb +249 -247
- data/lib/active_record/association_relation.rb +30 -18
- data/lib/active_record/associations.rb +1714 -1596
- data/lib/active_record/associations/alias_tracker.rb +36 -42
- data/lib/active_record/associations/association.rb +143 -68
- data/lib/active_record/associations/association_scope.rb +98 -94
- data/lib/active_record/associations/belongs_to_association.rb +76 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +27 -28
- data/lib/active_record/associations/builder/belongs_to.rb +52 -60
- data/lib/active_record/associations/builder/collection_association.rb +12 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +40 -62
- data/lib/active_record/associations/builder/has_many.rb +10 -2
- data/lib/active_record/associations/builder/has_one.rb +35 -2
- data/lib/active_record/associations/builder/singular_association.rb +5 -1
- data/lib/active_record/associations/collection_association.rb +104 -259
- data/lib/active_record/associations/collection_proxy.rb +169 -125
- data/lib/active_record/associations/foreign_association.rb +22 -0
- data/lib/active_record/associations/has_many_association.rb +46 -31
- data/lib/active_record/associations/has_many_through_association.rb +66 -46
- data/lib/active_record/associations/has_one_association.rb +71 -52
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +169 -180
- data/lib/active_record/associations/join_dependency/join_association.rb +53 -79
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/preloader.rb +97 -104
- data/lib/active_record/associations/preloader/association.rb +109 -97
- data/lib/active_record/associations/preloader/through_association.rb +77 -76
- data/lib/active_record/associations/singular_association.rb +12 -45
- data/lib/active_record/associations/through_association.rb +27 -15
- data/lib/active_record/attribute_assignment.rb +55 -60
- data/lib/active_record/attribute_methods.rb +111 -141
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -9
- data/lib/active_record/attribute_methods/dirty.rb +172 -112
- data/lib/active_record/attribute_methods/primary_key.rb +88 -91
- data/lib/active_record/attribute_methods/query.rb +6 -8
- data/lib/active_record/attribute_methods/read.rb +18 -50
- data/lib/active_record/attribute_methods/serialization.rb +38 -10
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -66
- data/lib/active_record/attribute_methods/write.rb +25 -32
- data/lib/active_record/attributes.rb +69 -31
- data/lib/active_record/autosave_association.rb +102 -66
- data/lib/active_record/base.rb +16 -25
- data/lib/active_record/callbacks.rb +202 -43
- data/lib/active_record/coders/json.rb +2 -0
- data/lib/active_record/coders/yaml_column.rb +11 -12
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +661 -375
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +14 -38
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +269 -105
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +54 -35
- data/lib/active_record/connection_adapters/abstract/quoting.rb +137 -93
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +155 -113
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -162
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +68 -80
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +591 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +229 -91
- data/lib/active_record/connection_adapters/abstract_adapter.rb +392 -244
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +457 -582
- data/lib/active_record/connection_adapters/column.rb +55 -13
- 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 +8 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +135 -49
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +24 -23
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -20
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +79 -49
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +66 -56
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +70 -36
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +20 -12
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +74 -37
- 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 +39 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -101
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +26 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +22 -11
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +6 -5
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +19 -18
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -5
- data/lib/active_record/connection_adapters/postgresql/oid/{json.rb → oid.rb} +6 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +30 -9
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +18 -4
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +98 -38
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +21 -27
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +147 -105
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +34 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +426 -324
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +32 -23
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -6
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +418 -293
- data/lib/active_record/connection_adapters/schema_cache.rb +135 -18
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +22 -7
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +3 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +72 -18
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +170 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +282 -290
- data/lib/active_record/connection_adapters/statement_pool.rb +9 -8
- data/lib/active_record/connection_handling.rb +287 -45
- data/lib/active_record/core.rb +385 -181
- data/lib/active_record/counter_cache.rb +60 -28
- data/lib/active_record/database_configurations.rb +272 -0
- 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/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -87
- data/lib/active_record/enum.rb +122 -47
- data/lib/active_record/errors.rb +153 -22
- data/lib/active_record/explain.rb +13 -8
- data/lib/active_record/explain_registry.rb +3 -1
- data/lib/active_record/explain_subscriber.rb +9 -4
- data/lib/active_record/fixture_set/file.rb +20 -22
- 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 +246 -507
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +168 -95
- data/lib/active_record/insert_all.rb +208 -0
- data/lib/active_record/integration.rb +114 -25
- data/lib/active_record/internal_metadata.rb +30 -24
- data/lib/active_record/legacy_yaml_adapter.rb +11 -5
- data/lib/active_record/locking/optimistic.rb +81 -85
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +68 -31
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/migration.rb +439 -342
- data/lib/active_record/migration/command_recorder.rb +152 -98
- data/lib/active_record/migration/compatibility.rb +229 -60
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/model_schema.rb +230 -122
- data/lib/active_record/nested_attributes.rb +213 -203
- data/lib/active_record/no_touching.rb +11 -2
- data/lib/active_record/null_relation.rb +12 -34
- data/lib/active_record/persistence.rb +471 -97
- data/lib/active_record/query_cache.rb +23 -12
- data/lib/active_record/querying.rb +43 -25
- data/lib/active_record/railtie.rb +155 -43
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +507 -195
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +245 -269
- data/lib/active_record/relation.rb +475 -324
- data/lib/active_record/relation/batches.rb +125 -72
- data/lib/active_record/relation/batches/batch_enumerator.rb +28 -10
- data/lib/active_record/relation/calculations.rb +267 -171
- data/lib/active_record/relation/delegation.rb +73 -69
- data/lib/active_record/relation/finder_methods.rb +238 -248
- data/lib/active_record/relation/from_clause.rb +7 -9
- data/lib/active_record/relation/merger.rb +95 -77
- data/lib/active_record/relation/predicate_builder.rb +109 -110
- data/lib/active_record/relation/predicate_builder/array_handler.rb +22 -17
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +6 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +55 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +7 -18
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/query_attribute.rb +33 -2
- data/lib/active_record/relation/query_methods.rb +654 -374
- data/lib/active_record/relation/record_fetch_warning.rb +8 -6
- data/lib/active_record/relation/spawn_methods.rb +15 -14
- data/lib/active_record/relation/where_clause.rb +171 -109
- data/lib/active_record/result.rb +88 -51
- data/lib/active_record/runtime_registry.rb +5 -3
- data/lib/active_record/sanitization.rb +73 -100
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +101 -69
- data/lib/active_record/schema_migration.rb +16 -12
- data/lib/active_record/scoping.rb +20 -20
- data/lib/active_record/scoping/default.rb +92 -95
- data/lib/active_record/scoping/named.rb +39 -30
- data/lib/active_record/secure_token.rb +19 -9
- data/lib/active_record/serialization.rb +7 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +80 -29
- data/lib/active_record/store.rb +122 -42
- data/lib/active_record/suppressor.rb +6 -3
- data/lib/active_record/table_metadata.rb +51 -39
- data/lib/active_record/tasks/database_tasks.rb +332 -115
- data/lib/active_record/tasks/mysql_database_tasks.rb +66 -104
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -56
- data/lib/active_record/tasks/sqlite_database_tasks.rb +40 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +246 -0
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +26 -24
- data/lib/active_record/transactions.rb +121 -184
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +29 -17
- data/lib/active_record/type/adapter_specific_registry.rb +44 -48
- data/lib/active_record/type/date.rb +2 -0
- data/lib/active_record/type/date_time.rb +2 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
- data/lib/active_record/type/internal/timezone.rb +2 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +20 -9
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +12 -1
- data/lib/active_record/type/type_map.rb +14 -17
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type_caster.rb +4 -2
- data/lib/active_record/type_caster/connection.rb +17 -13
- data/lib/active_record/type_caster/map.rb +10 -6
- data/lib/active_record/validations.rb +8 -5
- data/lib/active_record/validations/absence.rb +2 -0
- data/lib/active_record/validations/associated.rb +4 -3
- data/lib/active_record/validations/length.rb +2 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +4 -2
- data/lib/active_record/validations/uniqueness.rb +52 -45
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +54 -0
- 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.rb +70 -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/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.rb +13 -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/window_predications.rb +9 -0
- data/lib/rails/generators/active_record.rb +7 -5
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
- data/lib/rails/generators/active_record/migration.rb +22 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +38 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +3 -1
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +7 -5
- data/lib/rails/generators/active_record/model/model_generator.rb +41 -25
- 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 → model.rb.tt} +10 -1
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- metadata +141 -57
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -17
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -15
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -20
- data/lib/active_record/attribute.rb +0 -213
- data/lib/active_record/attribute/user_provided_default.rb +0 -28
- data/lib/active_record/attribute_decorators.rb +0 -67
- data/lib/active_record/attribute_mutation_tracker.rb +0 -70
- data/lib/active_record/attribute_set.rb +0 -110
- data/lib/active_record/attribute_set/builder.rb +0 -132
- data/lib/active_record/collection_cache_key.rb +0 -50
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -263
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -22
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +0 -50
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -17
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -57
- data/lib/active_record/relation/where_clause_factory.rb +0 -38
- data/lib/active_record/type/internal/abstract_json.rb +0 -33
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract"
|
4
|
+
|
1
5
|
module ActiveRecord
|
2
6
|
class PredicateBuilder
|
3
7
|
class ArrayHandler # :nodoc:
|
@@ -6,38 +10,39 @@ module ActiveRecord
|
|
6
10
|
end
|
7
11
|
|
8
12
|
def call(attribute, value)
|
9
|
-
|
10
|
-
nils, values = values.partition(&:nil?)
|
13
|
+
return attribute.in([]) if value.empty?
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
ranges
|
15
|
+
values = value.map { |x| x.is_a?(Base) ? x.id : x }
|
16
|
+
nils = values.extract!(&:nil?)
|
17
|
+
ranges = values.extract! { |v| v.is_a?(Range) }
|
15
18
|
|
16
19
|
values_predicate =
|
17
20
|
case values.length
|
18
21
|
when 0 then NullPredicate
|
19
22
|
when 1 then predicate_builder.build(attribute, values.first)
|
20
|
-
else
|
23
|
+
else Arel::Nodes::HomogeneousIn.new(values, attribute, :in)
|
21
24
|
end
|
22
25
|
|
23
26
|
unless nils.empty?
|
24
|
-
values_predicate = values_predicate.or(
|
27
|
+
values_predicate = values_predicate.or(attribute.eq(nil))
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
if ranges.empty?
|
31
|
+
values_predicate
|
32
|
+
else
|
33
|
+
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
|
34
|
+
array_predicates.inject(values_predicate, &:or)
|
35
|
+
end
|
30
36
|
end
|
31
37
|
|
32
|
-
|
33
|
-
|
34
|
-
attr_reader :predicate_builder
|
38
|
+
private
|
39
|
+
attr_reader :predicate_builder
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
|
41
|
+
module NullPredicate # :nodoc:
|
42
|
+
def self.or(other)
|
43
|
+
other
|
44
|
+
end
|
39
45
|
end
|
40
|
-
end
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class PredicateBuilder
|
5
|
+
class AssociationQueryValue # :nodoc:
|
6
|
+
def initialize(associated_table, value)
|
7
|
+
@associated_table = associated_table
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def queries
|
12
|
+
[associated_table.join_foreign_key => ids]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :associated_table, :value
|
17
|
+
|
18
|
+
def ids
|
19
|
+
case value
|
20
|
+
when Relation
|
21
|
+
value.select_values.empty? ? value.select(primary_key) : value
|
22
|
+
when Array
|
23
|
+
value.map { |v| convert_to_id(v) }
|
24
|
+
else
|
25
|
+
convert_to_id(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def primary_key
|
30
|
+
associated_table.join_primary_key
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert_to_id(value)
|
34
|
+
if value.respond_to?(primary_key)
|
35
|
+
value.public_send(primary_key)
|
36
|
+
else
|
37
|
+
value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class PredicateBuilder
|
3
5
|
class BasicObjectHandler # :nodoc:
|
@@ -6,12 +8,12 @@ module ActiveRecord
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def call(attribute, value)
|
9
|
-
attribute.
|
11
|
+
bind = predicate_builder.build_bind_attribute(attribute.name, value)
|
12
|
+
attribute.eq(bind)
|
10
13
|
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
attr_reader :predicate_builder
|
15
|
+
private
|
16
|
+
attr_reader :predicate_builder
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class PredicateBuilder
|
5
|
+
class PolymorphicArrayValue # :nodoc:
|
6
|
+
def initialize(associated_table, values)
|
7
|
+
@associated_table = associated_table
|
8
|
+
@values = values
|
9
|
+
end
|
10
|
+
|
11
|
+
def queries
|
12
|
+
type_to_ids_mapping.map do |type, ids|
|
13
|
+
query = {}
|
14
|
+
query[associated_table.join_foreign_type] = type if type
|
15
|
+
query[associated_table.join_foreign_key] = ids
|
16
|
+
query
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
attr_reader :associated_table, :values
|
22
|
+
|
23
|
+
def type_to_ids_mapping
|
24
|
+
default_hash = Hash.new { |hsh, key| hsh[key] = [] }
|
25
|
+
values.each_with_object(default_hash) do |value, hash|
|
26
|
+
hash[klass(value)&.polymorphic_name] << convert_to_id(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def primary_key(value)
|
31
|
+
associated_table.join_primary_key(klass(value))
|
32
|
+
end
|
33
|
+
|
34
|
+
def klass(value)
|
35
|
+
case value
|
36
|
+
when Base
|
37
|
+
value.class
|
38
|
+
when Relation
|
39
|
+
value.klass
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_to_id(value)
|
44
|
+
case value
|
45
|
+
when Base
|
46
|
+
value._read_attribute(primary_key(value))
|
47
|
+
when Relation
|
48
|
+
value.select(primary_key(value))
|
49
|
+
else
|
50
|
+
value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class PredicateBuilder
|
3
5
|
class RangeHandler # :nodoc:
|
@@ -8,26 +10,13 @@ module ActiveRecord
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def call(attribute, value)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
elsif value.exclude_end?
|
15
|
-
attribute.lt(value.end)
|
16
|
-
else
|
17
|
-
attribute.lteq(value.end)
|
18
|
-
end
|
19
|
-
elsif value.end.respond_to?(:infinite?) && value.end.infinite?
|
20
|
-
attribute.gteq(value.begin)
|
21
|
-
elsif value.exclude_end?
|
22
|
-
attribute.gteq(value.begin).and(attribute.lt(value.end))
|
23
|
-
else
|
24
|
-
attribute.between(value)
|
25
|
-
end
|
13
|
+
begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin)
|
14
|
+
end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end)
|
15
|
+
attribute.between(RangeWithBinds.new(begin_bind, end_bind, value.exclude_end?))
|
26
16
|
end
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
attr_reader :predicate_builder
|
18
|
+
private
|
19
|
+
attr_reader :predicate_builder
|
31
20
|
end
|
32
21
|
end
|
33
22
|
end
|
@@ -1,9 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class PredicateBuilder
|
3
5
|
class RelationHandler # :nodoc:
|
4
6
|
def call(attribute, value)
|
7
|
+
if value.eager_loading?
|
8
|
+
value = value.send(:apply_join_dependency)
|
9
|
+
end
|
10
|
+
|
5
11
|
if value.select_values.empty?
|
6
|
-
value = value.select(value.
|
12
|
+
value = value.select(value.table[value.klass.primary_key])
|
7
13
|
end
|
8
14
|
|
9
15
|
attribute.in(value.arel)
|
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute"
|
2
4
|
|
3
5
|
module ActiveRecord
|
4
6
|
class Relation
|
5
|
-
class QueryAttribute < Attribute # :nodoc:
|
7
|
+
class QueryAttribute < ActiveModel::Attribute # :nodoc:
|
6
8
|
def type_cast(value)
|
7
9
|
value
|
8
10
|
end
|
@@ -14,6 +16,35 @@ module ActiveRecord
|
|
14
16
|
def with_cast_value(value)
|
15
17
|
QueryAttribute.new(name, value, type)
|
16
18
|
end
|
19
|
+
|
20
|
+
def nil?
|
21
|
+
unless value_before_type_cast.is_a?(StatementCache::Substitute)
|
22
|
+
value_before_type_cast.nil? ||
|
23
|
+
type.respond_to?(:subtype, true) && value_for_database.nil?
|
24
|
+
end
|
25
|
+
rescue ::RangeError
|
26
|
+
end
|
27
|
+
|
28
|
+
def infinite?
|
29
|
+
infinity?(value_before_type_cast) || infinity?(value_for_database)
|
30
|
+
rescue ::RangeError
|
31
|
+
end
|
32
|
+
|
33
|
+
def unboundable?
|
34
|
+
if defined?(@_unboundable)
|
35
|
+
@_unboundable
|
36
|
+
else
|
37
|
+
value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
|
38
|
+
@_unboundable = nil
|
39
|
+
end
|
40
|
+
rescue ::RangeError
|
41
|
+
@_unboundable = type.cast(value_before_type_cast) <=> 0
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def infinity?(value)
|
46
|
+
value.respond_to?(:infinite?) && value.infinite?
|
47
|
+
end
|
17
48
|
end
|
18
49
|
end
|
19
50
|
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_record/relation/from_clause"
|
2
4
|
require "active_record/relation/query_attribute"
|
3
5
|
require "active_record/relation/where_clause"
|
4
|
-
require "
|
5
|
-
require
|
6
|
-
require 'active_support/core_ext/string/filters'
|
6
|
+
require "active_model/forbidden_attributes_protection"
|
7
|
+
require "active_support/core_ext/array/wrap"
|
7
8
|
|
8
9
|
module ActiveRecord
|
9
10
|
module QueryMethods
|
@@ -14,8 +15,6 @@ module ActiveRecord
|
|
14
15
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
15
16
|
# In this case, #where must be chained with #not to return a new relation.
|
16
17
|
class WhereChain
|
17
|
-
include ActiveModel::ForbiddenAttributesProtection
|
18
|
-
|
19
18
|
def initialize(scope)
|
20
19
|
@scope = scope
|
21
20
|
end
|
@@ -42,92 +41,71 @@ module ActiveRecord
|
|
42
41
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
43
42
|
#
|
44
43
|
# User.where.not(name: "Jon", role: "admin")
|
45
|
-
# # SELECT * FROM users WHERE name
|
44
|
+
# # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
|
46
45
|
def not(opts, *rest)
|
47
|
-
|
48
|
-
|
49
|
-
where_clause = @scope.send(:where_clause_factory).build(opts, rest)
|
46
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
50
47
|
|
51
|
-
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
52
48
|
@scope.where_clause += where_clause.invert
|
49
|
+
|
53
50
|
@scope
|
54
51
|
end
|
55
|
-
end
|
56
52
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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)
|
62
77
|
end
|
63
78
|
|
64
|
-
|
65
|
-
|
66
|
-
@values[:#{name}] = values
|
67
|
-
end
|
68
|
-
CODE
|
79
|
+
@scope
|
80
|
+
end
|
69
81
|
end
|
70
82
|
|
71
|
-
|
72
|
-
|
73
|
-
def #{name}_value # def readonly_value
|
74
|
-
@values[:#{name}] # @values[:readonly]
|
75
|
-
end # end
|
76
|
-
CODE
|
77
|
-
end
|
83
|
+
FROZEN_EMPTY_ARRAY = [].freeze
|
84
|
+
FROZEN_EMPTY_HASH = {}.freeze
|
78
85
|
|
79
|
-
Relation::
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
86
|
+
Relation::VALUE_METHODS.each do |name|
|
87
|
+
method_name, default =
|
88
|
+
case name
|
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"]
|
95
|
+
end
|
87
96
|
|
88
|
-
Relation::CLAUSE_METHODS.each do |name|
|
89
97
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
90
|
-
def #{
|
91
|
-
@values
|
92
|
-
end
|
93
|
-
|
94
|
-
def #{
|
95
|
-
assert_mutability!
|
96
|
-
@values[:#{name}] = value
|
97
|
-
end
|
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
|
98
106
|
CODE
|
99
107
|
end
|
100
108
|
|
101
|
-
def bound_attributes
|
102
|
-
if limit_value && !string_containing_comma?(limit_value)
|
103
|
-
limit_bind = Attribute.with_cast_value(
|
104
|
-
"LIMIT".freeze,
|
105
|
-
connection.sanitize_limit(limit_value),
|
106
|
-
Type::Value.new,
|
107
|
-
)
|
108
|
-
end
|
109
|
-
if offset_value
|
110
|
-
offset_bind = Attribute.with_cast_value(
|
111
|
-
"OFFSET".freeze,
|
112
|
-
offset_value.to_i,
|
113
|
-
Type::Value.new,
|
114
|
-
)
|
115
|
-
end
|
116
|
-
connection.combine_bind_parameters(
|
117
|
-
from_clause: from_clause.binds,
|
118
|
-
join_clause: arel.bind_values,
|
119
|
-
where_clause: where_clause.binds,
|
120
|
-
having_clause: having_clause.binds,
|
121
|
-
limit: limit_bind,
|
122
|
-
offset: offset_bind,
|
123
|
-
)
|
124
|
-
end
|
125
|
-
|
126
|
-
FROZEN_EMPTY_HASH = {}.freeze
|
127
|
-
def create_with_value # :nodoc:
|
128
|
-
@values[:create_with] || FROZEN_EMPTY_HASH
|
129
|
-
end
|
130
|
-
|
131
109
|
alias extensions extending_values
|
132
110
|
|
133
111
|
# Specify relationships to be included in the result set. For
|
@@ -152,7 +130,7 @@ module ActiveRecord
|
|
152
130
|
#
|
153
131
|
# === conditions
|
154
132
|
#
|
155
|
-
# 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
|
156
134
|
# to explicitly reference them. For example:
|
157
135
|
#
|
158
136
|
# User.includes(:posts).where('posts.name = ?', 'example')
|
@@ -163,15 +141,18 @@ module ActiveRecord
|
|
163
141
|
#
|
164
142
|
# Note that #includes works with association names while #references needs
|
165
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' })
|
166
150
|
def includes(*args)
|
167
151
|
check_if_method_has_arguments!(:includes, args)
|
168
152
|
spawn.includes!(*args)
|
169
153
|
end
|
170
154
|
|
171
155
|
def includes!(*args) # :nodoc:
|
172
|
-
args.reject!(&:blank?)
|
173
|
-
args.flatten!
|
174
|
-
|
175
156
|
self.includes_values |= args
|
176
157
|
self
|
177
158
|
end
|
@@ -188,7 +169,7 @@ module ActiveRecord
|
|
188
169
|
end
|
189
170
|
|
190
171
|
def eager_load!(*args) # :nodoc:
|
191
|
-
self.eager_load_values
|
172
|
+
self.eager_load_values |= args
|
192
173
|
self
|
193
174
|
end
|
194
175
|
|
@@ -202,10 +183,23 @@ module ActiveRecord
|
|
202
183
|
end
|
203
184
|
|
204
185
|
def preload!(*args) # :nodoc:
|
205
|
-
self.preload_values
|
186
|
+
self.preload_values |= args
|
206
187
|
self
|
207
188
|
end
|
208
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
|
+
|
209
203
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
210
204
|
# and should therefore be JOINed in any query rather than loaded separately.
|
211
205
|
# This method only works in conjunction with #includes.
|
@@ -222,21 +216,19 @@ module ActiveRecord
|
|
222
216
|
end
|
223
217
|
|
224
218
|
def references!(*table_names) # :nodoc:
|
225
|
-
table_names.flatten!
|
226
|
-
table_names.map!(&:to_s)
|
227
|
-
|
228
219
|
self.references_values |= table_names
|
229
220
|
self
|
230
221
|
end
|
231
222
|
|
232
223
|
# Works in two unique ways.
|
233
224
|
#
|
234
|
-
# First: takes a block so it can be used just like
|
225
|
+
# First: takes a block so it can be used just like <tt>Array#select</tt>.
|
235
226
|
#
|
236
227
|
# Model.all.select { |m| m.field == value }
|
237
228
|
#
|
238
229
|
# This will build an array of objects from the database for the scope,
|
239
|
-
# converting them into an array and iterating through them using
|
230
|
+
# converting them into an array and iterating through them using
|
231
|
+
# <tt>Array#select</tt>.
|
240
232
|
#
|
241
233
|
# Second: Modifies the SELECT statement for the query so that only certain
|
242
234
|
# fields are retrieved:
|
@@ -269,17 +261,41 @@ module ActiveRecord
|
|
269
261
|
# Model.select(:field).first.other_field
|
270
262
|
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
|
271
263
|
def select(*fields)
|
272
|
-
|
273
|
-
|
264
|
+
if block_given?
|
265
|
+
if fields.any?
|
266
|
+
raise ArgumentError, "`select' with block doesn't take arguments."
|
267
|
+
end
|
268
|
+
|
269
|
+
return super()
|
270
|
+
end
|
271
|
+
|
272
|
+
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
|
274
273
|
spawn._select!(*fields)
|
275
274
|
end
|
276
275
|
|
277
276
|
def _select!(*fields) # :nodoc:
|
278
|
-
fields
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
283
299
|
self
|
284
300
|
end
|
285
301
|
|
@@ -309,8 +325,6 @@ module ActiveRecord
|
|
309
325
|
end
|
310
326
|
|
311
327
|
def group!(*args) # :nodoc:
|
312
|
-
args.flatten!
|
313
|
-
|
314
328
|
self.group_values += args
|
315
329
|
self
|
316
330
|
end
|
@@ -335,14 +349,16 @@ module ActiveRecord
|
|
335
349
|
# User.order('name DESC, email')
|
336
350
|
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
337
351
|
def order(*args)
|
338
|
-
check_if_method_has_arguments!(:order, args)
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
353
|
+
sanitize_order_arguments(args)
|
354
|
+
end
|
339
355
|
spawn.order!(*args)
|
340
356
|
end
|
341
357
|
|
358
|
+
# Same as #order but operates on relation in-place instead of copying.
|
342
359
|
def order!(*args) # :nodoc:
|
343
|
-
preprocess_order_args(args)
|
344
|
-
|
345
|
-
self.order_values += args
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
361
|
+
self.order_values |= args
|
346
362
|
self
|
347
363
|
end
|
348
364
|
|
@@ -356,21 +372,24 @@ module ActiveRecord
|
|
356
372
|
#
|
357
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
358
374
|
def reorder(*args)
|
359
|
-
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
|
360
378
|
spawn.reorder!(*args)
|
361
379
|
end
|
362
380
|
|
381
|
+
# Same as #reorder but operates on relation in-place instead of copying.
|
363
382
|
def reorder!(*args) # :nodoc:
|
364
|
-
preprocess_order_args(args)
|
365
|
-
|
383
|
+
preprocess_order_args(args) unless args.all?(&:blank?)
|
384
|
+
args.uniq!
|
366
385
|
self.reordering_value = true
|
367
386
|
self.order_values = args
|
368
387
|
self
|
369
388
|
end
|
370
389
|
|
371
390
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
372
|
-
:limit, :offset, :joins, :
|
373
|
-
:readonly, :having])
|
391
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
392
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
374
393
|
|
375
394
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
376
395
|
# This is useful when passing around chains of relations and would like to
|
@@ -411,20 +430,24 @@ module ActiveRecord
|
|
411
430
|
end
|
412
431
|
|
413
432
|
def unscope!(*args) # :nodoc:
|
414
|
-
args.flatten!
|
415
433
|
self.unscope_values += args
|
416
434
|
|
417
435
|
args.each do |scope|
|
418
436
|
case scope
|
419
437
|
when Symbol
|
420
|
-
|
438
|
+
scope = :left_outer_joins if scope == :left_joins
|
439
|
+
if !VALID_UNSCOPING_VALUES.include?(scope)
|
440
|
+
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
441
|
+
end
|
442
|
+
assert_mutability!
|
443
|
+
@values.delete(scope)
|
421
444
|
when Hash
|
422
445
|
scope.each do |key, target_value|
|
423
446
|
if key != :where
|
424
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
425
448
|
end
|
426
449
|
|
427
|
-
target_values = Array(target_value)
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
428
451
|
self.where_clause = where_clause.except(*target_values)
|
429
452
|
end
|
430
453
|
else
|
@@ -457,8 +480,7 @@ module ActiveRecord
|
|
457
480
|
# # SELECT "users".*
|
458
481
|
# # FROM "users"
|
459
482
|
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
460
|
-
# # INNER JOIN "comments" "
|
461
|
-
# # ON "comments_posts"."post_id" = "posts"."id"
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
462
484
|
#
|
463
485
|
# You can use strings in order to customize your joins:
|
464
486
|
#
|
@@ -470,9 +492,7 @@ module ActiveRecord
|
|
470
492
|
end
|
471
493
|
|
472
494
|
def joins!(*args) # :nodoc:
|
473
|
-
args
|
474
|
-
args.flatten!
|
475
|
-
self.joins_values += args
|
495
|
+
self.joins_values |= args
|
476
496
|
self
|
477
497
|
end
|
478
498
|
|
@@ -482,20 +502,15 @@ module ActiveRecord
|
|
482
502
|
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
483
503
|
#
|
484
504
|
def left_outer_joins(*args)
|
485
|
-
check_if_method_has_arguments!(
|
486
|
-
|
487
|
-
args.compact!
|
488
|
-
args.flatten!
|
489
|
-
|
505
|
+
check_if_method_has_arguments!(__callee__, args)
|
490
506
|
spawn.left_outer_joins!(*args)
|
491
507
|
end
|
492
508
|
alias :left_joins :left_outer_joins
|
493
509
|
|
494
510
|
def left_outer_joins!(*args) # :nodoc:
|
495
|
-
self.left_outer_joins_values
|
511
|
+
self.left_outer_joins_values |= args
|
496
512
|
self
|
497
513
|
end
|
498
|
-
alias :left_joins! :left_outer_joins!
|
499
514
|
|
500
515
|
# Returns a new relation, which is the result of filtering the current relation
|
501
516
|
# according to the conditions in the arguments.
|
@@ -616,20 +631,18 @@ module ActiveRecord
|
|
616
631
|
#
|
617
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
618
633
|
# the current relation.
|
619
|
-
def where(
|
620
|
-
if
|
634
|
+
def where(*args)
|
635
|
+
if args.empty?
|
621
636
|
WhereChain.new(spawn)
|
622
|
-
elsif
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
623
638
|
self
|
624
639
|
else
|
625
|
-
spawn.where!(
|
640
|
+
spawn.where!(*args)
|
626
641
|
end
|
627
642
|
end
|
628
643
|
|
629
644
|
def where!(opts, *rest) # :nodoc:
|
630
|
-
|
631
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
632
|
-
self.where_clause += where_clause_factory.build(opts, rest)
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
633
646
|
self
|
634
647
|
end
|
635
648
|
|
@@ -647,7 +660,44 @@ module ActiveRecord
|
|
647
660
|
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
648
661
|
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
649
662
|
def rewhere(conditions)
|
650
|
-
|
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
|
651
701
|
end
|
652
702
|
|
653
703
|
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
@@ -655,28 +705,29 @@ module ActiveRecord
|
|
655
705
|
#
|
656
706
|
# The two relations must be structurally compatible: they must be scoping the same model, and
|
657
707
|
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
658
|
-
# present).
|
708
|
+
# present).
|
659
709
|
#
|
660
710
|
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
661
|
-
# # SELECT `posts`.* FROM `posts`
|
711
|
+
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
662
712
|
#
|
663
713
|
def or(other)
|
664
|
-
|
714
|
+
if other.is_a?(Relation)
|
715
|
+
spawn.or!(other)
|
716
|
+
else
|
665
717
|
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
666
718
|
end
|
667
|
-
|
668
|
-
spawn.or!(other)
|
669
719
|
end
|
670
720
|
|
671
721
|
def or!(other) # :nodoc:
|
672
|
-
incompatible_values =
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
673
723
|
|
674
724
|
unless incompatible_values.empty?
|
675
725
|
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
676
726
|
end
|
677
727
|
|
678
728
|
self.where_clause = self.where_clause.or(other.where_clause)
|
679
|
-
self.having_clause =
|
729
|
+
self.having_clause = having_clause.or(other.having_clause)
|
730
|
+
self.references_values |= other.references_values
|
680
731
|
|
681
732
|
self
|
682
733
|
end
|
@@ -690,10 +741,7 @@ module ActiveRecord
|
|
690
741
|
end
|
691
742
|
|
692
743
|
def having!(opts, *rest) # :nodoc:
|
693
|
-
|
694
|
-
references!(PredicateBuilder.references(opts)) if Hash === opts
|
695
|
-
|
696
|
-
self.having_clause += having_clause_factory.build(opts, rest)
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
697
745
|
self
|
698
746
|
end
|
699
747
|
|
@@ -707,13 +755,6 @@ module ActiveRecord
|
|
707
755
|
end
|
708
756
|
|
709
757
|
def limit!(value) # :nodoc:
|
710
|
-
if string_containing_comma?(value)
|
711
|
-
# Remove `string_containing_comma?` when removing this deprecation
|
712
|
-
ActiveSupport::Deprecation.warn(<<-WARNING.squish)
|
713
|
-
Passing a string to limit in the form "1,2" is deprecated and will be
|
714
|
-
removed in Rails 5.1. Please call `offset` explicitly instead.
|
715
|
-
WARNING
|
716
|
-
end
|
717
758
|
self.limit_value = value
|
718
759
|
self
|
719
760
|
end
|
@@ -780,7 +821,7 @@ module ActiveRecord
|
|
780
821
|
# end
|
781
822
|
#
|
782
823
|
def none
|
783
|
-
|
824
|
+
spawn.none!
|
784
825
|
end
|
785
826
|
|
786
827
|
def none! # :nodoc:
|
@@ -802,6 +843,21 @@ module ActiveRecord
|
|
802
843
|
self
|
803
844
|
end
|
804
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
|
+
|
805
861
|
# Sets attributes to be used when creating new records from a
|
806
862
|
# relation object.
|
807
863
|
#
|
@@ -824,7 +880,7 @@ module ActiveRecord
|
|
824
880
|
value = sanitize_forbidden_attributes(value)
|
825
881
|
self.create_with_value = create_with_value.merge(value)
|
826
882
|
else
|
827
|
-
self.create_with_value =
|
883
|
+
self.create_with_value = FROZEN_EMPTY_HASH
|
828
884
|
end
|
829
885
|
|
830
886
|
self
|
@@ -865,16 +921,12 @@ module ActiveRecord
|
|
865
921
|
def distinct(value = true)
|
866
922
|
spawn.distinct!(value)
|
867
923
|
end
|
868
|
-
alias uniq distinct
|
869
|
-
deprecate uniq: :distinct
|
870
924
|
|
871
925
|
# Like #distinct, but modifies relation in place.
|
872
926
|
def distinct!(value = true) # :nodoc:
|
873
927
|
self.distinct_value = value
|
874
928
|
self
|
875
929
|
end
|
876
|
-
alias uniq! distinct!
|
877
|
-
deprecate uniq!: :distinct!
|
878
930
|
|
879
931
|
# Used to extend a scope with additional methods, either through
|
880
932
|
# a module or through a block provided.
|
@@ -930,6 +982,27 @@ module ActiveRecord
|
|
930
982
|
self
|
931
983
|
end
|
932
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
|
+
|
933
1006
|
# Reverse the existing order clause on the relation.
|
934
1007
|
#
|
935
1008
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -938,305 +1011,512 @@ module ActiveRecord
|
|
938
1011
|
end
|
939
1012
|
|
940
1013
|
def reverse_order! # :nodoc:
|
941
|
-
orders = order_values.
|
942
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
943
1015
|
self.order_values = reverse_sql_order(orders)
|
944
1016
|
self
|
945
1017
|
end
|
946
1018
|
|
947
|
-
|
948
|
-
|
949
|
-
|
1019
|
+
def skip_query_cache!(value = true) # :nodoc:
|
1020
|
+
self.skip_query_cache_value = value
|
1021
|
+
self
|
950
1022
|
end
|
951
1023
|
|
952
|
-
|
1024
|
+
def skip_preloading! # :nodoc:
|
1025
|
+
self.skip_preloading_value = true
|
1026
|
+
self
|
1027
|
+
end
|
953
1028
|
|
954
|
-
|
955
|
-
|
956
|
-
|
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)
|
957
1041
|
end
|
958
1042
|
|
959
|
-
|
960
|
-
|
1043
|
+
# Like #annotate, but modifies relation in place.
|
1044
|
+
def annotate!(*args) # :nodoc:
|
1045
|
+
self.annotate_values += args
|
1046
|
+
self
|
1047
|
+
end
|
961
1048
|
|
962
|
-
|
963
|
-
|
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
|
964
1056
|
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
1057
|
+
# Returns the Arel object associated with the relation.
|
1058
|
+
def arel(aliases = nil) # :nodoc:
|
1059
|
+
@arel ||= build_arel(aliases)
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1063
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1064
|
+
klass, table, associations, join_type
|
1065
|
+
)
|
1066
|
+
end
|
1067
|
+
|
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?
|
972
1074
|
end
|
973
1075
|
end
|
974
|
-
arel.skip(Arel::Nodes::BindParam.new) if offset_value
|
975
|
-
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
976
|
-
|
977
|
-
build_order(arel)
|
978
1076
|
|
979
|
-
|
1077
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1078
|
+
opts = sanitize_forbidden_attributes(opts)
|
980
1079
|
|
981
|
-
|
982
|
-
|
983
|
-
|
1080
|
+
case opts
|
1081
|
+
when String, Array
|
1082
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1083
|
+
when Hash
|
1084
|
+
opts = opts.transform_keys do |key|
|
1085
|
+
key = key.to_s
|
1086
|
+
klass.attribute_aliases[key] || key
|
1087
|
+
end
|
1088
|
+
references = PredicateBuilder.references(opts)
|
1089
|
+
self.references_values |= references unless references.empty?
|
984
1090
|
|
985
|
-
|
986
|
-
|
1091
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1092
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1093
|
+
end
|
1094
|
+
when Arel::Nodes::Node
|
1095
|
+
parts = [opts]
|
1096
|
+
else
|
1097
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1098
|
+
end
|
987
1099
|
|
988
|
-
|
989
|
-
if !VALID_UNSCOPING_VALUES.include?(scope)
|
990
|
-
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
1100
|
+
Relation::WhereClause.new(parts)
|
991
1101
|
end
|
1102
|
+
alias :build_having_clause :build_where_clause
|
992
1103
|
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
1104
|
+
private
|
1105
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
|
+
each_join_dependencies do |join|
|
1107
|
+
return join.base_klass if table_name == join.table_name
|
1108
|
+
end
|
1109
|
+
nil
|
999
1110
|
end
|
1000
1111
|
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1112
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1113
|
+
join_dependencies.each do |join_dependency|
|
1114
|
+
join_dependency.each do |join|
|
1115
|
+
yield join
|
1116
|
+
end
|
1117
|
+
end
|
1006
1118
|
end
|
1007
1119
|
|
1008
|
-
|
1009
|
-
|
1120
|
+
def build_join_dependencies
|
1121
|
+
associations = joins_values | left_outer_joins_values
|
1122
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1123
|
+
associations |= includes_values unless includes_values.empty?
|
1010
1124
|
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1125
|
+
join_dependencies = []
|
1126
|
+
join_dependencies.unshift construct_join_dependency(
|
1127
|
+
select_association_list(associations, join_dependencies), nil
|
1128
|
+
)
|
1129
|
+
end
|
1016
1130
|
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
case opts
|
1021
|
-
when Relation
|
1022
|
-
name ||= 'subquery'
|
1023
|
-
opts.arel.as(name.to_s)
|
1024
|
-
else
|
1025
|
-
opts
|
1131
|
+
def assert_mutability!
|
1132
|
+
raise ImmutableRelation if @loaded
|
1133
|
+
raise ImmutableRelation if defined?(@arel) && @arel
|
1026
1134
|
end
|
1027
|
-
end
|
1028
1135
|
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1136
|
+
def build_arel(aliases)
|
1137
|
+
arel = Arel::SelectManager.new(table)
|
1138
|
+
|
1139
|
+
build_joins(arel.join_sources, aliases)
|
1140
|
+
|
1141
|
+
arel.where(where_clause.ast) unless where_clause.empty?
|
1142
|
+
arel.having(having_clause.ast) unless having_clause.empty?
|
1143
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1144
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1145
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1146
|
+
|
1147
|
+
build_order(arel)
|
1148
|
+
build_select(arel)
|
1149
|
+
|
1150
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1151
|
+
arel.distinct(distinct_value)
|
1152
|
+
arel.from(build_from) unless from_clause.empty?
|
1153
|
+
arel.lock(lock_value) if lock_value
|
1154
|
+
|
1155
|
+
unless annotate_values.empty?
|
1156
|
+
annotates = annotate_values
|
1157
|
+
annotates = annotates.uniq if annotates.size > 1
|
1158
|
+
unless annotates == annotate_values
|
1159
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1160
|
+
Duplicated query annotations are no longer shown in queries in Rails 6.2.
|
1161
|
+
To migrate to Rails 6.2's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1162
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1163
|
+
MSG
|
1164
|
+
annotates = annotate_values
|
1165
|
+
end
|
1166
|
+
arel.comment(*annotates)
|
1036
1167
|
end
|
1168
|
+
|
1169
|
+
arel
|
1037
1170
|
end
|
1038
1171
|
|
1039
|
-
|
1040
|
-
|
1172
|
+
def build_cast_value(name, value)
|
1173
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1174
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1175
|
+
end
|
1041
1176
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
:join_node
|
1177
|
+
def build_from
|
1178
|
+
opts = from_clause.value
|
1179
|
+
name = from_clause.name
|
1180
|
+
case opts
|
1181
|
+
when Relation
|
1182
|
+
if opts.eager_loading?
|
1183
|
+
opts = opts.send(:apply_join_dependency)
|
1184
|
+
end
|
1185
|
+
name ||= "subquery"
|
1186
|
+
opts.arel.as(name.to_s)
|
1053
1187
|
else
|
1054
|
-
|
1188
|
+
opts
|
1055
1189
|
end
|
1056
1190
|
end
|
1057
1191
|
|
1058
|
-
|
1059
|
-
|
1192
|
+
def select_association_list(associations, stashed_joins = nil)
|
1193
|
+
result = []
|
1194
|
+
associations.each do |association|
|
1195
|
+
case association
|
1196
|
+
when Hash, Symbol, Array
|
1197
|
+
result << association
|
1198
|
+
when ActiveRecord::Associations::JoinDependency
|
1199
|
+
stashed_joins&.<< association
|
1200
|
+
else
|
1201
|
+
yield association if block_given?
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
result
|
1205
|
+
end
|
1060
1206
|
|
1061
|
-
|
1062
|
-
|
1207
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1208
|
+
end
|
1063
1209
|
|
1064
|
-
|
1065
|
-
|
1066
|
-
join_nodes = buckets[:join_node].uniq
|
1067
|
-
string_joins = buckets[:string_join].map(&:strip).uniq
|
1210
|
+
def build_join_buckets
|
1211
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1068
1212
|
|
1069
|
-
|
1213
|
+
unless left_outer_joins_values.empty?
|
1214
|
+
stashed_left_joins = []
|
1215
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1216
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1217
|
+
end
|
1070
1218
|
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1219
|
+
if joins_values.empty?
|
1220
|
+
buckets[:association_join] = left_joins
|
1221
|
+
buckets[:stashed_join] = stashed_left_joins
|
1222
|
+
return buckets, Arel::Nodes::OuterJoin
|
1223
|
+
else
|
1224
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
joins = joins_values.dup
|
1229
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1230
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
joins.each_with_index do |join, i|
|
1234
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1238
|
+
join_node = joins.shift
|
1239
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1240
|
+
buckets[:join_node] << join_node
|
1241
|
+
else
|
1242
|
+
buckets[:leading_join] << join_node
|
1243
|
+
end
|
1244
|
+
end
|
1076
1245
|
|
1077
|
-
|
1246
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1247
|
+
if join.is_a?(Arel::Nodes::Join)
|
1248
|
+
buckets[:join_node] << join
|
1249
|
+
else
|
1250
|
+
raise "unknown class: %s" % join.class.name
|
1251
|
+
end
|
1252
|
+
end
|
1078
1253
|
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1254
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1255
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1256
|
+
|
1257
|
+
return buckets, Arel::Nodes::InnerJoin
|
1082
1258
|
end
|
1083
1259
|
|
1084
|
-
|
1260
|
+
def build_joins(join_sources, aliases = nil)
|
1261
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
1085
1262
|
|
1086
|
-
|
1087
|
-
end
|
1263
|
+
buckets, join_type = build_join_buckets
|
1088
1264
|
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
.map { |join| table.create_string_join(Arel.sql(join)) }
|
1094
|
-
end
|
1265
|
+
association_joins = buckets[:association_join]
|
1266
|
+
stashed_joins = buckets[:stashed_join]
|
1267
|
+
leading_joins = buckets[:leading_join]
|
1268
|
+
join_nodes = buckets[:join_node]
|
1095
1269
|
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1270
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1271
|
+
|
1272
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1273
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1274
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1275
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1276
|
+
end
|
1277
|
+
|
1278
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1279
|
+
join_sources
|
1101
1280
|
end
|
1102
|
-
end
|
1103
1281
|
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
connection.quote_table_name(field.to_s)
|
1282
|
+
def build_select(arel)
|
1283
|
+
if select_values.any?
|
1284
|
+
arel.project(*arel_columns(select_values))
|
1285
|
+
elsif klass.ignored_columns.any?
|
1286
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1110
1287
|
else
|
1111
|
-
|
1288
|
+
arel.project(table[Arel.star])
|
1112
1289
|
end
|
1113
1290
|
end
|
1114
|
-
end
|
1115
1291
|
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1292
|
+
def arel_columns(columns)
|
1293
|
+
columns.flat_map do |field|
|
1294
|
+
case field
|
1295
|
+
when Symbol
|
1296
|
+
arel_column(field.to_s) do |attr_name|
|
1297
|
+
connection.quote_table_name(attr_name)
|
1298
|
+
end
|
1299
|
+
when String
|
1300
|
+
arel_column(field, &:itself)
|
1301
|
+
when Proc
|
1302
|
+
field.call
|
1303
|
+
else
|
1304
|
+
field
|
1305
|
+
end
|
1306
|
+
end
|
1121
1307
|
end
|
1122
1308
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
o.split(',').map! do |s|
|
1134
|
-
s.strip!
|
1135
|
-
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
1309
|
+
def arel_column(field)
|
1310
|
+
field = klass.attribute_aliases[field] || field
|
1311
|
+
from = from_clause.name || from_clause.value
|
1312
|
+
|
1313
|
+
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1314
|
+
table[field]
|
1315
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1316
|
+
table, column = field.split(".")
|
1317
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1318
|
+
lookup_table_klass_from_join_dependencies(table)
|
1136
1319
|
end
|
1137
1320
|
else
|
1138
|
-
|
1321
|
+
yield field
|
1139
1322
|
end
|
1140
1323
|
end
|
1141
|
-
end
|
1142
1324
|
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
#
|
1147
|
-
|
1148
|
-
end
|
1325
|
+
def table_name_matches?(from)
|
1326
|
+
table_name = Regexp.escape(table.name)
|
1327
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1328
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1329
|
+
end
|
1149
1330
|
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1331
|
+
def reverse_sql_order(order_query)
|
1332
|
+
if order_query.empty?
|
1333
|
+
return [table[primary_key].desc] if primary_key
|
1334
|
+
raise IrreversibleOrderError,
|
1335
|
+
"Relation has no current order and table has no primary key to be used as default order"
|
1336
|
+
end
|
1153
1337
|
|
1154
|
-
|
1155
|
-
|
1338
|
+
order_query.flat_map do |o|
|
1339
|
+
case o
|
1340
|
+
when Arel::Attribute
|
1341
|
+
o.desc
|
1342
|
+
when Arel::Nodes::Ordering
|
1343
|
+
o.reverse
|
1344
|
+
when Arel::Nodes::NodeExpression
|
1345
|
+
o.desc
|
1346
|
+
when String
|
1347
|
+
if does_not_support_reverse?(o)
|
1348
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1349
|
+
end
|
1350
|
+
o.split(",").map! do |s|
|
1351
|
+
s.strip!
|
1352
|
+
s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
|
1353
|
+
end
|
1354
|
+
else
|
1355
|
+
o
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
end
|
1156
1359
|
|
1157
|
-
|
1158
|
-
|
1360
|
+
def does_not_support_reverse?(order)
|
1361
|
+
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
1362
|
+
# override methods like #count.
|
1363
|
+
order = String.new(order) unless order.instance_of?(String)
|
1159
1364
|
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
|
1165
|
-
"directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
|
1166
|
-
end
|
1365
|
+
# Uses SQL function with multiple arguments.
|
1366
|
+
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1367
|
+
# Uses "nulls first" like construction.
|
1368
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1167
1369
|
end
|
1168
|
-
end
|
1169
1370
|
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1371
|
+
def build_order(arel)
|
1372
|
+
orders = order_values.compact_blank
|
1373
|
+
arel.order(*orders) unless orders.empty?
|
1173
1374
|
end
|
1174
|
-
order_args.flatten!
|
1175
|
-
validate_order_args(order_args)
|
1176
1375
|
|
1177
|
-
|
1178
|
-
|
1179
|
-
references!(references) if references.any?
|
1376
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1377
|
+
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
|
1180
1378
|
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
else
|
1191
|
-
arg
|
1379
|
+
def validate_order_args(args)
|
1380
|
+
args.each do |arg|
|
1381
|
+
next unless arg.is_a?(Hash)
|
1382
|
+
arg.each do |_key, value|
|
1383
|
+
unless VALID_DIRECTIONS.include?(value)
|
1384
|
+
raise ArgumentError,
|
1385
|
+
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1386
|
+
end
|
1387
|
+
end
|
1192
1388
|
end
|
1193
|
-
end
|
1194
|
-
end
|
1389
|
+
end
|
1195
1390
|
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1391
|
+
def preprocess_order_args(order_args)
|
1392
|
+
@klass.disallow_raw_sql!(
|
1393
|
+
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1394
|
+
permit: connection.column_name_with_order_matcher
|
1395
|
+
)
|
1396
|
+
|
1397
|
+
validate_order_args(order_args)
|
1398
|
+
|
1399
|
+
references = column_references(order_args)
|
1400
|
+
self.references_values |= references unless references.empty?
|
1401
|
+
|
1402
|
+
# if a symbol is given we prepend the quoted table name
|
1403
|
+
order_args.map! do |arg|
|
1404
|
+
case arg
|
1405
|
+
when Symbol
|
1406
|
+
order_column(arg.to_s).asc
|
1407
|
+
when Hash
|
1408
|
+
arg.map { |field, dir|
|
1409
|
+
case field
|
1410
|
+
when Arel::Nodes::SqlLiteral
|
1411
|
+
field.public_send(dir.downcase)
|
1412
|
+
else
|
1413
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1414
|
+
end
|
1415
|
+
}
|
1416
|
+
else
|
1417
|
+
arg
|
1418
|
+
end
|
1419
|
+
end.flatten!
|
1215
1420
|
end
|
1216
|
-
end
|
1217
1421
|
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1422
|
+
def sanitize_order_arguments(order_args)
|
1423
|
+
order_args.map! do |arg|
|
1424
|
+
klass.sanitize_sql_for_order(arg)
|
1425
|
+
end
|
1426
|
+
order_args.flatten!
|
1427
|
+
order_args.compact_blank!
|
1428
|
+
end
|
1223
1429
|
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1430
|
+
def column_references(order_args)
|
1431
|
+
references = order_args.grep(String)
|
1432
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1433
|
+
references
|
1434
|
+
end
|
1228
1435
|
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1436
|
+
def order_column(field)
|
1437
|
+
arel_column(field) do |attr_name|
|
1438
|
+
if attr_name == "count" && !group_values.empty?
|
1439
|
+
table[attr_name]
|
1440
|
+
else
|
1441
|
+
Arel.sql(connection.quote_table_name(attr_name))
|
1442
|
+
end
|
1443
|
+
end
|
1444
|
+
end
|
1233
1445
|
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1446
|
+
def resolve_arel_attributes(attrs)
|
1447
|
+
attrs.flat_map do |attr|
|
1448
|
+
case attr
|
1449
|
+
when Arel::Predications
|
1450
|
+
attr
|
1451
|
+
when Hash
|
1452
|
+
attr.flat_map do |table, columns|
|
1453
|
+
table = table.to_s
|
1454
|
+
Array(columns).map do |column|
|
1455
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
else
|
1459
|
+
attr = attr.to_s
|
1460
|
+
if attr.include?(".")
|
1461
|
+
table, column = attr.split(".", 2)
|
1462
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1463
|
+
else
|
1464
|
+
attr
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
# Checks to make sure that the arguments are not blank. Note that if some
|
1471
|
+
# blank-like object were initially passed into the query method, then this
|
1472
|
+
# method will not raise an error.
|
1473
|
+
#
|
1474
|
+
# Example:
|
1475
|
+
#
|
1476
|
+
# Post.references() # raises an error
|
1477
|
+
# Post.references([]) # does not raise an error
|
1478
|
+
#
|
1479
|
+
# This particular method should be called with a method_name and the args
|
1480
|
+
# passed into that method as an input. For example:
|
1481
|
+
#
|
1482
|
+
# def references(*args)
|
1483
|
+
# check_if_method_has_arguments!("references", args)
|
1484
|
+
# ...
|
1485
|
+
# end
|
1486
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1487
|
+
if args.blank?
|
1488
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1489
|
+
elsif block_given?
|
1490
|
+
yield args
|
1491
|
+
else
|
1492
|
+
args.flatten!
|
1493
|
+
args.compact_blank!
|
1494
|
+
end
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
STRUCTURAL_VALUE_METHODS = (
|
1498
|
+
Relation::VALUE_METHODS -
|
1499
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1500
|
+
).freeze # :nodoc:
|
1501
|
+
|
1502
|
+
def structurally_incompatible_values_for(other)
|
1503
|
+
values = other.values
|
1504
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1505
|
+
v1, v2 = @values[method], values[method]
|
1506
|
+
if v1.is_a?(Array)
|
1507
|
+
next true unless v2.is_a?(Array)
|
1508
|
+
v1 = v1.uniq
|
1509
|
+
v2 = v2.uniq
|
1510
|
+
end
|
1511
|
+
v1 == v2
|
1512
|
+
end
|
1513
|
+
end
|
1514
|
+
end
|
1237
1515
|
|
1238
|
-
|
1239
|
-
|
1516
|
+
class Relation # :nodoc:
|
1517
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1518
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1519
|
+
class WhereClauseFactory # :nodoc:
|
1240
1520
|
end
|
1241
1521
|
end
|
1242
1522
|
end
|