activerecord 4.2.9 → 6.1.4.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 +5 -5
- data/CHANGELOG.md +964 -1382
- data/MIT-LICENSE +4 -2
- data/README.rdoc +15 -14
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +266 -251
- data/lib/active_record/association_relation.rb +40 -15
- data/lib/active_record/associations/alias_tracker.rb +40 -43
- data/lib/active_record/associations/association.rb +162 -69
- data/lib/active_record/associations/association_scope.rb +105 -130
- data/lib/active_record/associations/belongs_to_association.rb +83 -65
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -12
- data/lib/active_record/associations/builder/association.rb +57 -43
- data/lib/active_record/associations/builder/belongs_to.rb +74 -57
- data/lib/active_record/associations/builder/collection_association.rb +15 -37
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +49 -66
- data/lib/active_record/associations/builder/has_many.rb +13 -5
- data/lib/active_record/associations/builder/has_one.rb +44 -6
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +148 -287
- data/lib/active_record/associations/collection_proxy.rb +252 -150
- data/lib/active_record/associations/foreign_association.rb +23 -1
- data/lib/active_record/associations/has_many_association.rb +56 -98
- data/lib/active_record/associations/has_many_through_association.rb +68 -89
- data/lib/active_record/associations/has_one_association.rb +73 -47
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -81
- 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/join_dependency.rb +174 -169
- data/lib/active_record/associations/preloader/association.rb +108 -115
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +97 -94
- data/lib/active_record/associations/singular_association.rb +18 -39
- data/lib/active_record/associations/through_association.rb +39 -19
- data/lib/active_record/associations.rb +1845 -1598
- data/lib/active_record/attribute_assignment.rb +59 -185
- data/lib/active_record/attribute_methods/before_type_cast.rb +18 -10
- data/lib/active_record/attribute_methods/dirty.rb +168 -148
- data/lib/active_record/attribute_methods/primary_key.rb +93 -83
- data/lib/active_record/attribute_methods/query.rb +8 -10
- data/lib/active_record/attribute_methods/read.rb +19 -79
- data/lib/active_record/attribute_methods/serialization.rb +49 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +55 -36
- data/lib/active_record/attribute_methods/write.rb +24 -55
- data/lib/active_record/attribute_methods.rb +149 -154
- data/lib/active_record/attributes.rb +234 -78
- data/lib/active_record/autosave_association.rb +133 -60
- data/lib/active_record/base.rb +46 -46
- data/lib/active_record/callbacks.rb +234 -79
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +34 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -323
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +292 -124
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +177 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +8 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +473 -255
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +869 -286
- data/lib/active_record/connection_adapters/abstract/transaction.rb +257 -91
- data/lib/active_record/connection_adapters/abstract_adapter.rb +483 -230
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +557 -640
- data/lib/active_record/connection_adapters/column.rb +67 -40
- 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 +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +194 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +96 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +103 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +91 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +80 -192
- 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 +44 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -160
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +14 -19
- 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 +31 -20
- 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 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{infinity.rb → oid.rb} +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +32 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +70 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- 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 +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +25 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +145 -48
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +80 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +496 -298
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +588 -375
- data/lib/active_record/connection_adapters/schema_cache.rb +167 -29
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +102 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +21 -0
- 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 +322 -373
- data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
- data/lib/active_record/connection_adapters.rb +52 -0
- data/lib/active_record/connection_handling.rb +314 -41
- data/lib/active_record/core.rb +458 -241
- data/lib/active_record/counter_cache.rb +70 -49
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -106
- data/lib/active_record/enum.rb +211 -92
- data/lib/active_record/errors.rb +224 -54
- data/lib/active_record/explain.rb +27 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +33 -14
- 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 +275 -500
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +175 -110
- data/lib/active_record/insert_all.rb +212 -0
- data/lib/active_record/integration.rb +121 -29
- data/lib/active_record/internal_metadata.rb +62 -0
- data/lib/active_record/legacy_yaml_adapter.rb +27 -5
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +98 -92
- data/lib/active_record/locking/pessimistic.rb +22 -6
- data/lib/active_record/log_subscriber.rb +93 -31
- 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 +185 -90
- data/lib/active_record/migration/compatibility.rb +295 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +673 -325
- data/lib/active_record/model_schema.rb +418 -113
- data/lib/active_record/nested_attributes.rb +263 -224
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +572 -136
- data/lib/active_record/query_cache.rb +29 -23
- data/lib/active_record/querying.rb +50 -31
- data/lib/active_record/railtie.rb +170 -51
- data/lib/active_record/railties/console_sandbox.rb +3 -3
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +523 -199
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +454 -291
- data/lib/active_record/relation/batches/batch_enumerator.rb +85 -0
- data/lib/active_record/relation/batches.rb +217 -59
- data/lib/active_record/relation/calculations.rb +324 -249
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +316 -242
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +95 -103
- data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -26
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +136 -122
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +757 -413
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -20
- data/lib/active_record/relation/where_clause.rb +239 -0
- data/lib/active_record/relation.rb +554 -343
- data/lib/active_record/result.rb +91 -47
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +134 -122
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +141 -92
- data/lib/active_record/schema_migration.rb +24 -23
- data/lib/active_record/scoping/default.rb +96 -83
- data/lib/active_record/scoping/named.rb +78 -36
- data/lib/active_record/scoping.rb +45 -27
- data/lib/active_record/secure_token.rb +48 -0
- data/lib/active_record/serialization.rb +8 -6
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +89 -36
- data/lib/active_record/store.rb +128 -43
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +81 -0
- data/lib/active_record/tasks/database_tasks.rb +364 -130
- data/lib/active_record/tasks/mysql_database_tasks.rb +67 -113
- data/lib/active_record/tasks/postgresql_database_tasks.rb +86 -49
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -19
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +287 -0
- data/lib/active_record/timestamp.rb +86 -43
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +182 -163
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type/adapter_specific_registry.rb +126 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -4
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +27 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +21 -16
- data/lib/active_record/type/type_map.rb +16 -19
- data/lib/active_record/type/unsigned_integer.rb +9 -8
- data/lib/active_record/type.rb +84 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +12 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +63 -56
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +42 -29
- 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 +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +26 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +13 -4
- data/lib/rails/generators/active_record/migration.rb +35 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +55 -22
- 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 +22 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +172 -65
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- 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 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_decorators.rb +0 -66
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -275
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -1,6 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/relation/from_clause"
|
4
|
+
require "active_record/relation/query_attribute"
|
5
|
+
require "active_record/relation/where_clause"
|
6
|
+
require "active_model/forbidden_attributes_protection"
|
7
|
+
require "active_support/core_ext/array/wrap"
|
4
8
|
|
5
9
|
module ActiveRecord
|
6
10
|
module QueryMethods
|
@@ -18,7 +22,7 @@ module ActiveRecord
|
|
18
22
|
# Returns a new relation expressing WHERE + NOT condition according to
|
19
23
|
# the conditions in the arguments.
|
20
24
|
#
|
21
|
-
#
|
25
|
+
# #not accepts conditions as a string, array, or hash. See QueryMethods#where for
|
22
26
|
# more details on each format.
|
23
27
|
#
|
24
28
|
# User.where.not("name = 'Jon'")
|
@@ -37,75 +41,71 @@ module ActiveRecord
|
|
37
41
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
38
42
|
#
|
39
43
|
# User.where.not(name: "Jon", role: "admin")
|
40
|
-
# # SELECT * FROM users WHERE name
|
44
|
+
# # SELECT * FROM users WHERE NOT (name == 'Jon' AND role == 'admin')
|
41
45
|
def not(opts, *rest)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
where_clause = @scope.send(:build_where_clause, opts, rest)
|
47
|
+
|
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)
|
55
77
|
end
|
56
78
|
|
57
|
-
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
58
|
-
@scope.where_values += where_value
|
59
79
|
@scope
|
60
80
|
end
|
61
81
|
end
|
62
82
|
|
63
|
-
|
64
|
-
|
65
|
-
def #{name}_values # def select_values
|
66
|
-
@values[:#{name}] || [] # @values[:select] || []
|
67
|
-
end # end
|
68
|
-
#
|
69
|
-
def #{name}_values=(values) # def select_values=(values)
|
70
|
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
71
|
-
check_cached_relation
|
72
|
-
@values[:#{name}] = values # @values[:select] = values
|
73
|
-
end # end
|
74
|
-
CODE
|
75
|
-
end
|
83
|
+
FROZEN_EMPTY_ARRAY = [].freeze
|
84
|
+
FROZEN_EMPTY_HASH = {}.freeze
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
84
96
|
|
85
|
-
Relation::SINGLE_VALUE_METHODS.each do |name|
|
86
97
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
87
|
-
def #{
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
92
106
|
CODE
|
93
107
|
end
|
94
108
|
|
95
|
-
def check_cached_relation # :nodoc:
|
96
|
-
if defined?(@arel) && @arel
|
97
|
-
@arel = nil
|
98
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
99
|
-
Modifying already cached Relation. The cache will be reset. Use a
|
100
|
-
cloned Relation to prevent this warning.
|
101
|
-
MSG
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def create_with_value # :nodoc:
|
106
|
-
@values[:create_with] || {}
|
107
|
-
end
|
108
|
-
|
109
109
|
alias extensions extending_values
|
110
110
|
|
111
111
|
# Specify relationships to be included in the result set. For
|
@@ -118,7 +118,7 @@ module ActiveRecord
|
|
118
118
|
#
|
119
119
|
# allows you to access the +address+ attribute of the +User+ model without
|
120
120
|
# firing an additional query. This will often result in a
|
121
|
-
# performance improvement over a simple
|
121
|
+
# performance improvement over a simple join.
|
122
122
|
#
|
123
123
|
# You can also specify multiple relationships, like this:
|
124
124
|
#
|
@@ -130,7 +130,7 @@ module ActiveRecord
|
|
130
130
|
#
|
131
131
|
# === conditions
|
132
132
|
#
|
133
|
-
# 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
|
134
134
|
# to explicitly reference them. For example:
|
135
135
|
#
|
136
136
|
# User.includes(:posts).where('posts.name = ?', 'example')
|
@@ -139,17 +139,20 @@ module ActiveRecord
|
|
139
139
|
#
|
140
140
|
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
141
141
|
#
|
142
|
-
# Note that
|
142
|
+
# Note that #includes works with association names while #references needs
|
143
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' })
|
144
150
|
def includes(*args)
|
145
151
|
check_if_method_has_arguments!(:includes, args)
|
146
152
|
spawn.includes!(*args)
|
147
153
|
end
|
148
154
|
|
149
155
|
def includes!(*args) # :nodoc:
|
150
|
-
args.reject!(&:blank?)
|
151
|
-
args.flatten!
|
152
|
-
|
153
156
|
self.includes_values |= args
|
154
157
|
self
|
155
158
|
end
|
@@ -157,64 +160,75 @@ module ActiveRecord
|
|
157
160
|
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
|
158
161
|
#
|
159
162
|
# User.eager_load(:posts)
|
160
|
-
#
|
161
|
-
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
162
|
-
# "users"."id"
|
163
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
|
164
|
+
# # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
165
|
+
# # "users"."id"
|
163
166
|
def eager_load(*args)
|
164
167
|
check_if_method_has_arguments!(:eager_load, args)
|
165
168
|
spawn.eager_load!(*args)
|
166
169
|
end
|
167
170
|
|
168
171
|
def eager_load!(*args) # :nodoc:
|
169
|
-
self.eager_load_values
|
172
|
+
self.eager_load_values |= args
|
170
173
|
self
|
171
174
|
end
|
172
175
|
|
173
|
-
# Allows preloading of +args+, in the same way that
|
176
|
+
# Allows preloading of +args+, in the same way that #includes does:
|
174
177
|
#
|
175
178
|
# User.preload(:posts)
|
176
|
-
#
|
179
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
|
177
180
|
def preload(*args)
|
178
181
|
check_if_method_has_arguments!(:preload, args)
|
179
182
|
spawn.preload!(*args)
|
180
183
|
end
|
181
184
|
|
182
185
|
def preload!(*args) # :nodoc:
|
183
|
-
self.preload_values
|
186
|
+
self.preload_values |= args
|
184
187
|
self
|
185
188
|
end
|
186
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
|
+
|
187
203
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
188
204
|
# and should therefore be JOINed in any query rather than loaded separately.
|
189
|
-
# This method only works in conjunction with
|
205
|
+
# This method only works in conjunction with #includes.
|
190
206
|
# See #includes for more details.
|
191
207
|
#
|
192
208
|
# User.includes(:posts).where("posts.name = 'foo'")
|
193
|
-
# #
|
209
|
+
# # Doesn't JOIN the posts table, resulting in an error.
|
194
210
|
#
|
195
211
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
196
|
-
# #
|
212
|
+
# # Query now knows the string references posts, so adds a JOIN
|
197
213
|
def references(*table_names)
|
198
214
|
check_if_method_has_arguments!(:references, table_names)
|
199
215
|
spawn.references!(*table_names)
|
200
216
|
end
|
201
217
|
|
202
218
|
def references!(*table_names) # :nodoc:
|
203
|
-
table_names.flatten!
|
204
|
-
table_names.map!(&:to_s)
|
205
|
-
|
206
219
|
self.references_values |= table_names
|
207
220
|
self
|
208
221
|
end
|
209
222
|
|
210
223
|
# Works in two unique ways.
|
211
224
|
#
|
212
|
-
# First: takes a block so it can be used just like Array#select
|
225
|
+
# First: takes a block so it can be used just like <tt>Array#select</tt>.
|
213
226
|
#
|
214
227
|
# Model.all.select { |m| m.field == value }
|
215
228
|
#
|
216
229
|
# This will build an array of objects from the database for the scope,
|
217
|
-
# 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>.
|
218
232
|
#
|
219
233
|
# Second: Modifies the SELECT statement for the query so that only certain
|
220
234
|
# fields are retrieved:
|
@@ -242,55 +256,75 @@ module ActiveRecord
|
|
242
256
|
# # => "value"
|
243
257
|
#
|
244
258
|
# Accessing attributes of an object that do not have fields retrieved by a select
|
245
|
-
# except +id+ will throw
|
259
|
+
# except +id+ will throw ActiveModel::MissingAttributeError:
|
246
260
|
#
|
247
261
|
# Model.select(:field).first.other_field
|
248
262
|
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
|
249
263
|
def select(*fields)
|
250
264
|
if block_given?
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
265
|
+
if fields.any?
|
266
|
+
raise ArgumentError, "`select' with block doesn't take arguments."
|
267
|
+
end
|
268
|
+
|
269
|
+
return super()
|
255
270
|
end
|
271
|
+
|
272
|
+
check_if_method_has_arguments!(:select, fields, "Call `select' with at least one field.")
|
273
|
+
spawn._select!(*fields)
|
256
274
|
end
|
257
275
|
|
258
276
|
def _select!(*fields) # :nodoc:
|
259
|
-
fields
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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
|
264
299
|
self
|
265
300
|
end
|
266
301
|
|
267
302
|
# Allows to specify a group attribute:
|
268
303
|
#
|
269
304
|
# User.group(:name)
|
270
|
-
#
|
305
|
+
# # SELECT "users".* FROM "users" GROUP BY name
|
271
306
|
#
|
272
307
|
# Returns an array with distinct records based on the +group+ attribute:
|
273
308
|
#
|
274
309
|
# User.select([:id, :name])
|
275
|
-
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
|
310
|
+
# # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
|
276
311
|
#
|
277
312
|
# User.group(:name)
|
278
|
-
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
|
313
|
+
# # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
|
279
314
|
#
|
280
315
|
# User.group('name AS grouped_name, age')
|
281
|
-
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
316
|
+
# # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
282
317
|
#
|
283
318
|
# Passing in an array of attributes to group by is also supported.
|
319
|
+
#
|
284
320
|
# User.select([:id, :first_name]).group(:id, :first_name).first(3)
|
285
|
-
# => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
321
|
+
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
286
322
|
def group(*args)
|
287
323
|
check_if_method_has_arguments!(:group, args)
|
288
324
|
spawn.group!(*args)
|
289
325
|
end
|
290
326
|
|
291
327
|
def group!(*args) # :nodoc:
|
292
|
-
args.flatten!
|
293
|
-
|
294
328
|
self.group_values += args
|
295
329
|
self
|
296
330
|
end
|
@@ -298,31 +332,33 @@ module ActiveRecord
|
|
298
332
|
# Allows to specify an order attribute:
|
299
333
|
#
|
300
334
|
# User.order(:name)
|
301
|
-
#
|
335
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
302
336
|
#
|
303
337
|
# User.order(email: :desc)
|
304
|
-
#
|
338
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
305
339
|
#
|
306
340
|
# User.order(:name, email: :desc)
|
307
|
-
#
|
341
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
308
342
|
#
|
309
343
|
# User.order('name')
|
310
|
-
#
|
344
|
+
# # SELECT "users".* FROM "users" ORDER BY name
|
311
345
|
#
|
312
346
|
# User.order('name DESC')
|
313
|
-
#
|
347
|
+
# # SELECT "users".* FROM "users" ORDER BY name DESC
|
314
348
|
#
|
315
349
|
# User.order('name DESC, email')
|
316
|
-
#
|
350
|
+
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
317
351
|
def order(*args)
|
318
|
-
check_if_method_has_arguments!(:order, args)
|
352
|
+
check_if_method_has_arguments!(:order, args) do
|
353
|
+
sanitize_order_arguments(args)
|
354
|
+
end
|
319
355
|
spawn.order!(*args)
|
320
356
|
end
|
321
357
|
|
358
|
+
# Same as #order but operates on relation in-place instead of copying.
|
322
359
|
def order!(*args) # :nodoc:
|
323
|
-
preprocess_order_args(args)
|
324
|
-
|
325
|
-
self.order_values += args
|
360
|
+
preprocess_order_args(args) unless args.empty?
|
361
|
+
self.order_values |= args
|
326
362
|
self
|
327
363
|
end
|
328
364
|
|
@@ -336,21 +372,24 @@ module ActiveRecord
|
|
336
372
|
#
|
337
373
|
# generates a query with 'ORDER BY id ASC, name ASC'.
|
338
374
|
def reorder(*args)
|
339
|
-
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
|
340
378
|
spawn.reorder!(*args)
|
341
379
|
end
|
342
380
|
|
381
|
+
# Same as #reorder but operates on relation in-place instead of copying.
|
343
382
|
def reorder!(*args) # :nodoc:
|
344
|
-
preprocess_order_args(args)
|
345
|
-
|
383
|
+
preprocess_order_args(args) unless args.all?(&:blank?)
|
384
|
+
args.uniq!
|
346
385
|
self.reordering_value = true
|
347
386
|
self.order_values = args
|
348
387
|
self
|
349
388
|
end
|
350
389
|
|
351
390
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
352
|
-
:limit, :offset, :joins, :
|
353
|
-
:readonly, :having])
|
391
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
392
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
354
393
|
|
355
394
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
356
395
|
# This is useful when passing around chains of relations and would like to
|
@@ -365,15 +404,15 @@ module ActiveRecord
|
|
365
404
|
# User.order('email DESC').select('id').where(name: "John")
|
366
405
|
# .unscope(:order, :select, :where) == User.all
|
367
406
|
#
|
368
|
-
# One can additionally pass a hash as an argument to unscope specific
|
407
|
+
# One can additionally pass a hash as an argument to unscope specific +:where+ values.
|
369
408
|
# This is done by passing a hash with a single key-value pair. The key should be
|
370
|
-
#
|
409
|
+
# +:where+ and the value should be the where value to unscope. For example:
|
371
410
|
#
|
372
411
|
# User.where(name: "John", active: true).unscope(where: :name)
|
373
412
|
# == User.where(active: true)
|
374
413
|
#
|
375
|
-
# This method is similar to
|
376
|
-
#
|
414
|
+
# This method is similar to #except, but unlike
|
415
|
+
# #except, it persists across merges:
|
377
416
|
#
|
378
417
|
# User.order('email').merge(User.except(:order))
|
379
418
|
# == User.order('email')
|
@@ -383,7 +422,7 @@ module ActiveRecord
|
|
383
422
|
#
|
384
423
|
# This means it can be used in association definitions:
|
385
424
|
#
|
386
|
-
# has_many :comments, -> { unscope
|
425
|
+
# has_many :comments, -> { unscope(where: :trashed) }
|
387
426
|
#
|
388
427
|
def unscope(*args)
|
389
428
|
check_if_method_has_arguments!(:unscope, args)
|
@@ -391,22 +430,25 @@ module ActiveRecord
|
|
391
430
|
end
|
392
431
|
|
393
432
|
def unscope!(*args) # :nodoc:
|
394
|
-
args.flatten!
|
395
433
|
self.unscope_values += args
|
396
434
|
|
397
435
|
args.each do |scope|
|
398
436
|
case scope
|
399
437
|
when Symbol
|
400
|
-
|
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)
|
401
444
|
when Hash
|
402
445
|
scope.each do |key, target_value|
|
403
446
|
if key != :where
|
404
447
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
405
448
|
end
|
406
449
|
|
407
|
-
Array(target_value)
|
408
|
-
|
409
|
-
end
|
450
|
+
target_values = resolve_arel_attributes(Array.wrap(target_value))
|
451
|
+
self.where_clause = where_clause.except(*target_values)
|
410
452
|
end
|
411
453
|
else
|
412
454
|
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
|
@@ -416,33 +458,57 @@ module ActiveRecord
|
|
416
458
|
self
|
417
459
|
end
|
418
460
|
|
419
|
-
# Performs a joins on +args
|
461
|
+
# Performs a joins on +args+. The given symbol(s) should match the name of
|
462
|
+
# the association(s).
|
420
463
|
#
|
421
464
|
# User.joins(:posts)
|
422
|
-
#
|
465
|
+
# # SELECT "users".*
|
466
|
+
# # FROM "users"
|
467
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
468
|
+
#
|
469
|
+
# Multiple joins:
|
470
|
+
#
|
471
|
+
# User.joins(:posts, :account)
|
472
|
+
# # SELECT "users".*
|
473
|
+
# # FROM "users"
|
474
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
475
|
+
# # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
|
476
|
+
#
|
477
|
+
# Nested joins:
|
478
|
+
#
|
479
|
+
# User.joins(posts: [:comments])
|
480
|
+
# # SELECT "users".*
|
481
|
+
# # FROM "users"
|
482
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
483
|
+
# # INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
|
423
484
|
#
|
424
485
|
# You can use strings in order to customize your joins:
|
425
486
|
#
|
426
487
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
427
|
-
#
|
488
|
+
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
428
489
|
def joins(*args)
|
429
490
|
check_if_method_has_arguments!(:joins, args)
|
430
491
|
spawn.joins!(*args)
|
431
492
|
end
|
432
493
|
|
433
494
|
def joins!(*args) # :nodoc:
|
434
|
-
args
|
435
|
-
args.flatten!
|
436
|
-
self.joins_values += args
|
495
|
+
self.joins_values |= args
|
437
496
|
self
|
438
497
|
end
|
439
498
|
|
440
|
-
|
441
|
-
|
499
|
+
# Performs a left outer joins on +args+:
|
500
|
+
#
|
501
|
+
# User.left_outer_joins(:posts)
|
502
|
+
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
503
|
+
#
|
504
|
+
def left_outer_joins(*args)
|
505
|
+
check_if_method_has_arguments!(__callee__, args)
|
506
|
+
spawn.left_outer_joins!(*args)
|
442
507
|
end
|
508
|
+
alias :left_joins :left_outer_joins
|
443
509
|
|
444
|
-
def
|
445
|
-
self.
|
510
|
+
def left_outer_joins!(*args) # :nodoc:
|
511
|
+
self.left_outer_joins_values |= args
|
446
512
|
self
|
447
513
|
end
|
448
514
|
|
@@ -489,7 +555,7 @@ module ActiveRecord
|
|
489
555
|
# than the previous methods; you are responsible for ensuring that the values in the template
|
490
556
|
# are properly quoted. The values are passed to the connector for quoting, but the caller
|
491
557
|
# is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
|
492
|
-
# the values are inserted using the same escapes as the Ruby core method
|
558
|
+
# the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
|
493
559
|
#
|
494
560
|
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
|
495
561
|
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
@@ -565,36 +631,105 @@ module ActiveRecord
|
|
565
631
|
#
|
566
632
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
567
633
|
# the current relation.
|
568
|
-
def where(
|
569
|
-
if
|
634
|
+
def where(*args)
|
635
|
+
if args.empty?
|
570
636
|
WhereChain.new(spawn)
|
571
|
-
elsif
|
637
|
+
elsif args.length == 1 && args.first.blank?
|
572
638
|
self
|
573
639
|
else
|
574
|
-
spawn.where!(
|
640
|
+
spawn.where!(*args)
|
575
641
|
end
|
576
642
|
end
|
577
643
|
|
578
644
|
def where!(opts, *rest) # :nodoc:
|
579
|
-
|
580
|
-
opts = sanitize_forbidden_attributes(opts)
|
581
|
-
references!(PredicateBuilder.references(opts))
|
582
|
-
end
|
583
|
-
|
584
|
-
self.where_values += build_where(opts, rest)
|
645
|
+
self.where_clause += build_where_clause(opts, rest)
|
585
646
|
self
|
586
647
|
end
|
587
648
|
|
588
649
|
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
|
589
650
|
#
|
590
|
-
# Post.where(trashed: true).where(trashed: false)
|
591
|
-
#
|
592
|
-
# Post.where(active: true).where(trashed: true).rewhere(trashed: false) # => WHERE `active` = 1 AND `trashed` = 0
|
651
|
+
# Post.where(trashed: true).where(trashed: false)
|
652
|
+
# # WHERE `trashed` = 1 AND `trashed` = 0
|
593
653
|
#
|
594
|
-
#
|
595
|
-
#
|
654
|
+
# Post.where(trashed: true).rewhere(trashed: false)
|
655
|
+
# # WHERE `trashed` = 0
|
656
|
+
#
|
657
|
+
# Post.where(active: true).where(trashed: true).rewhere(trashed: false)
|
658
|
+
# # WHERE `active` = 1 AND `trashed` = 0
|
659
|
+
#
|
660
|
+
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
661
|
+
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
596
662
|
def rewhere(conditions)
|
597
|
-
|
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
|
701
|
+
end
|
702
|
+
|
703
|
+
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
704
|
+
# argument.
|
705
|
+
#
|
706
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
707
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
708
|
+
# present).
|
709
|
+
#
|
710
|
+
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
711
|
+
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
712
|
+
#
|
713
|
+
def or(other)
|
714
|
+
if other.is_a?(Relation)
|
715
|
+
spawn.or!(other)
|
716
|
+
else
|
717
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
def or!(other) # :nodoc:
|
722
|
+
incompatible_values = structurally_incompatible_values_for(other)
|
723
|
+
|
724
|
+
unless incompatible_values.empty?
|
725
|
+
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
726
|
+
end
|
727
|
+
|
728
|
+
self.where_clause = self.where_clause.or(other.where_clause)
|
729
|
+
self.having_clause = having_clause.or(other.having_clause)
|
730
|
+
self.references_values |= other.references_values
|
731
|
+
|
732
|
+
self
|
598
733
|
end
|
599
734
|
|
600
735
|
# Allows to specify a HAVING clause. Note that you can't use HAVING
|
@@ -606,9 +741,7 @@ module ActiveRecord
|
|
606
741
|
end
|
607
742
|
|
608
743
|
def having!(opts, *rest) # :nodoc:
|
609
|
-
|
610
|
-
|
611
|
-
self.having_values += build_where(opts, rest)
|
744
|
+
self.having_clause += build_having_clause(opts, rest)
|
612
745
|
self
|
613
746
|
end
|
614
747
|
|
@@ -643,7 +776,7 @@ module ActiveRecord
|
|
643
776
|
end
|
644
777
|
|
645
778
|
# Specifies locking settings (default to +true+). For more information
|
646
|
-
# on locking, please see
|
779
|
+
# on locking, please see ActiveRecord::Locking.
|
647
780
|
def lock(locks = true)
|
648
781
|
spawn.lock!(locks)
|
649
782
|
end
|
@@ -674,7 +807,7 @@ module ActiveRecord
|
|
674
807
|
# For example:
|
675
808
|
#
|
676
809
|
# @posts = current_user.visible_posts.where(name: params[:name])
|
677
|
-
# #
|
810
|
+
# # the visible_posts method is expected to return a chainable Relation
|
678
811
|
#
|
679
812
|
# def visible_posts
|
680
813
|
# case role
|
@@ -688,7 +821,7 @@ module ActiveRecord
|
|
688
821
|
# end
|
689
822
|
#
|
690
823
|
def none
|
691
|
-
|
824
|
+
spawn.none!
|
692
825
|
end
|
693
826
|
|
694
827
|
def none! # :nodoc:
|
@@ -700,7 +833,7 @@ module ActiveRecord
|
|
700
833
|
#
|
701
834
|
# users = User.readonly
|
702
835
|
# users.first.save
|
703
|
-
# => ActiveRecord::ReadOnlyRecord:
|
836
|
+
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
704
837
|
def readonly(value = true)
|
705
838
|
spawn.readonly!(value)
|
706
839
|
end
|
@@ -710,6 +843,21 @@ module ActiveRecord
|
|
710
843
|
self
|
711
844
|
end
|
712
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
|
+
|
713
861
|
# Sets attributes to be used when creating new records from a
|
714
862
|
# relation object.
|
715
863
|
#
|
@@ -719,7 +867,7 @@ module ActiveRecord
|
|
719
867
|
# users = users.create_with(name: 'DHH')
|
720
868
|
# users.new.name # => 'DHH'
|
721
869
|
#
|
722
|
-
# You can pass +nil+ to
|
870
|
+
# You can pass +nil+ to #create_with to reset attributes:
|
723
871
|
#
|
724
872
|
# users = users.create_with(nil)
|
725
873
|
# users.new.name # => 'Oscar'
|
@@ -732,7 +880,7 @@ module ActiveRecord
|
|
732
880
|
value = sanitize_forbidden_attributes(value)
|
733
881
|
self.create_with_value = create_with_value.merge(value)
|
734
882
|
else
|
735
|
-
self.create_with_value =
|
883
|
+
self.create_with_value = FROZEN_EMPTY_HASH
|
736
884
|
end
|
737
885
|
|
738
886
|
self
|
@@ -741,49 +889,44 @@ module ActiveRecord
|
|
741
889
|
# Specifies table from which the records will be fetched. For example:
|
742
890
|
#
|
743
891
|
# Topic.select('title').from('posts')
|
744
|
-
# #
|
892
|
+
# # SELECT title FROM posts
|
745
893
|
#
|
746
894
|
# Can accept other relation objects. For example:
|
747
895
|
#
|
748
896
|
# Topic.select('title').from(Topic.approved)
|
749
|
-
# #
|
897
|
+
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
750
898
|
#
|
751
899
|
# Topic.select('a.title').from(Topic.approved, :a)
|
752
|
-
# #
|
900
|
+
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
753
901
|
#
|
754
902
|
def from(value, subquery_name = nil)
|
755
903
|
spawn.from!(value, subquery_name)
|
756
904
|
end
|
757
905
|
|
758
906
|
def from!(value, subquery_name = nil) # :nodoc:
|
759
|
-
self.
|
760
|
-
if value.is_a? Relation
|
761
|
-
self.bind_values = value.arel.bind_values + value.bind_values + bind_values
|
762
|
-
end
|
907
|
+
self.from_clause = Relation::FromClause.new(value, subquery_name)
|
763
908
|
self
|
764
909
|
end
|
765
910
|
|
766
911
|
# Specifies whether the records should be unique or not. For example:
|
767
912
|
#
|
768
913
|
# User.select(:name)
|
769
|
-
# #
|
914
|
+
# # Might return two records with the same name
|
770
915
|
#
|
771
916
|
# User.select(:name).distinct
|
772
|
-
# #
|
917
|
+
# # Returns 1 record per distinct name
|
773
918
|
#
|
774
919
|
# User.select(:name).distinct.distinct(false)
|
775
|
-
# #
|
920
|
+
# # You can also remove the uniqueness
|
776
921
|
def distinct(value = true)
|
777
922
|
spawn.distinct!(value)
|
778
923
|
end
|
779
|
-
alias uniq distinct
|
780
924
|
|
781
925
|
# Like #distinct, but modifies relation in place.
|
782
926
|
def distinct!(value = true) # :nodoc:
|
783
927
|
self.distinct_value = value
|
784
928
|
self
|
785
929
|
end
|
786
|
-
alias uniq! distinct!
|
787
930
|
|
788
931
|
# Used to extend a scope with additional methods, either through
|
789
932
|
# a module or through a block provided.
|
@@ -839,6 +982,27 @@ module ActiveRecord
|
|
839
982
|
self
|
840
983
|
end
|
841
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
|
+
|
842
1006
|
# Reverse the existing order clause on the relation.
|
843
1007
|
#
|
844
1008
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -847,332 +1011,512 @@ module ActiveRecord
|
|
847
1011
|
end
|
848
1012
|
|
849
1013
|
def reverse_order! # :nodoc:
|
850
|
-
orders = order_values.
|
851
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
852
1015
|
self.order_values = reverse_sql_order(orders)
|
853
1016
|
self
|
854
1017
|
end
|
855
1018
|
|
856
|
-
|
857
|
-
|
858
|
-
|
1019
|
+
def skip_query_cache!(value = true) # :nodoc:
|
1020
|
+
self.skip_query_cache_value = value
|
1021
|
+
self
|
859
1022
|
end
|
860
1023
|
|
861
|
-
|
1024
|
+
def skip_preloading! # :nodoc:
|
1025
|
+
self.skip_preloading_value = true
|
1026
|
+
self
|
1027
|
+
end
|
862
1028
|
|
863
|
-
|
864
|
-
|
1029
|
+
# Adds an SQL comment to queries generated from this relation. For example:
|
1030
|
+
#
|
1031
|
+
# User.annotate("selecting user names").select(:name)
|
1032
|
+
# # SELECT "users"."name" FROM "users" /* selecting user names */
|
1033
|
+
#
|
1034
|
+
# User.annotate("selecting", "user", "names").select(:name)
|
1035
|
+
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1036
|
+
#
|
1037
|
+
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1038
|
+
def annotate(*args)
|
1039
|
+
check_if_method_has_arguments!(:annotate, args)
|
1040
|
+
spawn.annotate!(*args)
|
1041
|
+
end
|
865
1042
|
|
866
|
-
|
1043
|
+
# Like #annotate, but modifies relation in place.
|
1044
|
+
def annotate!(*args) # :nodoc:
|
1045
|
+
self.annotate_values += args
|
1046
|
+
self
|
1047
|
+
end
|
867
1048
|
|
868
|
-
|
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
|
869
1056
|
|
870
|
-
|
1057
|
+
# Returns the Arel object associated with the relation.
|
1058
|
+
def arel(aliases = nil) # :nodoc:
|
1059
|
+
@arel ||= build_arel(aliases)
|
1060
|
+
end
|
871
1061
|
|
872
|
-
|
873
|
-
|
874
|
-
|
1062
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1063
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1064
|
+
klass, table, associations, join_type
|
1065
|
+
)
|
1066
|
+
end
|
875
1067
|
|
876
|
-
|
1068
|
+
protected
|
1069
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1070
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
877
1071
|
|
878
|
-
|
1072
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1073
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1074
|
+
end
|
1075
|
+
end
|
879
1076
|
|
880
|
-
|
881
|
-
|
882
|
-
arel.lock(lock_value) if lock_value
|
1077
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1078
|
+
opts = sanitize_forbidden_attributes(opts)
|
883
1079
|
|
884
|
-
|
885
|
-
|
1080
|
+
case opts
|
1081
|
+
when String, Array
|
1082
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1083
|
+
when Hash
|
1084
|
+
opts = opts.transform_keys do |key|
|
1085
|
+
key = key.to_s
|
1086
|
+
klass.attribute_aliases[key] || key
|
1087
|
+
end
|
1088
|
+
references = PredicateBuilder.references(opts)
|
1089
|
+
self.references_values |= references unless references.empty?
|
1090
|
+
|
1091
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1092
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1093
|
+
end
|
1094
|
+
when Arel::Nodes::Node
|
1095
|
+
parts = [opts]
|
1096
|
+
else
|
1097
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1098
|
+
end
|
886
1099
|
|
887
|
-
|
888
|
-
if !VALID_UNSCOPING_VALUES.include?(scope)
|
889
|
-
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
1100
|
+
Relation::WhereClause.new(parts)
|
890
1101
|
end
|
1102
|
+
alias :build_having_clause :build_where_clause
|
891
1103
|
|
892
|
-
|
893
|
-
|
1104
|
+
private
|
1105
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1106
|
+
each_join_dependencies do |join|
|
1107
|
+
return join.base_klass if table_name == join.table_name
|
1108
|
+
end
|
1109
|
+
nil
|
1110
|
+
end
|
894
1111
|
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
result = [] unless single_val_method
|
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
|
902
1118
|
end
|
903
1119
|
|
904
|
-
|
905
|
-
|
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?
|
906
1124
|
|
907
|
-
|
908
|
-
|
1125
|
+
join_dependencies = []
|
1126
|
+
join_dependencies.unshift construct_join_dependency(
|
1127
|
+
select_association_list(associations, join_dependencies), nil
|
1128
|
+
)
|
1129
|
+
end
|
909
1130
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
914
|
-
subrelation.name == target_value
|
915
|
-
end
|
1131
|
+
def assert_mutability!
|
1132
|
+
raise ImmutableRelation if @loaded
|
1133
|
+
raise ImmutableRelation if defined?(@arel) && @arel
|
916
1134
|
end
|
917
1135
|
|
918
|
-
|
919
|
-
|
1136
|
+
def build_arel(aliases = nil)
|
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)
|
1167
|
+
end
|
920
1168
|
|
921
|
-
|
922
|
-
|
1169
|
+
arel
|
1170
|
+
end
|
923
1171
|
|
924
|
-
|
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
|
925
1176
|
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
when
|
931
|
-
|
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)
|
1187
|
+
else
|
1188
|
+
opts
|
932
1189
|
end
|
933
|
-
table.create_string_join(join)
|
934
1190
|
end
|
935
|
-
end
|
936
1191
|
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
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
|
942
1205
|
end
|
943
1206
|
|
944
|
-
|
945
|
-
|
1207
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1208
|
+
end
|
946
1209
|
|
947
|
-
|
948
|
-
|
949
|
-
when String, Array
|
950
|
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
951
|
-
when Hash
|
952
|
-
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
|
1210
|
+
def build_join_buckets
|
1211
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
953
1212
|
|
954
|
-
|
955
|
-
|
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
|
956
1218
|
|
957
|
-
|
958
|
-
|
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
|
959
1227
|
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
end
|
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
|
965
1232
|
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
@klass.columns_hash.include?(column.to_s) &&
|
970
|
-
!@klass.reflect_on_aggregation(column)
|
971
|
-
end
|
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
|
972
1236
|
|
973
|
-
|
974
|
-
|
975
|
-
|
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
|
976
1245
|
|
977
|
-
|
978
|
-
|
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
|
979
1253
|
|
980
|
-
|
1254
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1255
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
981
1256
|
|
982
|
-
|
983
|
-
binds.push [@klass.columns_hash[column.to_s], value]
|
984
|
-
new_opts[column] = connection.substitute_at(column)
|
1257
|
+
return buckets, Arel::Nodes::InnerJoin
|
985
1258
|
end
|
986
1259
|
|
987
|
-
|
988
|
-
|
989
|
-
association_new_opts, association_bind = association_relation.send(:create_binds, value)
|
990
|
-
new_opts[column] = association_new_opts
|
991
|
-
binds += association_bind
|
992
|
-
end
|
1260
|
+
def build_joins(join_sources, aliases = nil)
|
1261
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
993
1262
|
|
994
|
-
|
1263
|
+
buckets, join_type = build_join_buckets
|
995
1264
|
|
996
|
-
|
997
|
-
|
1265
|
+
association_joins = buckets[:association_join]
|
1266
|
+
stashed_joins = buckets[:stashed_join]
|
1267
|
+
leading_joins = buckets[:leading_join]
|
1268
|
+
join_nodes = buckets[:join_node]
|
998
1269
|
|
999
|
-
|
1000
|
-
table_name = table_name.to_s
|
1001
|
-
@klass._reflect_on_association(table_name) ||
|
1002
|
-
@klass._reflect_on_association(table_name.singularize)
|
1003
|
-
end
|
1270
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1004
1271
|
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
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
|
1013
1280
|
end
|
1014
|
-
end
|
1015
1281
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
when Hash, Symbol, Array
|
1022
|
-
:association_join
|
1023
|
-
when ActiveRecord::Associations::JoinDependency
|
1024
|
-
:stashed_join
|
1025
|
-
when Arel::Nodes::Join
|
1026
|
-
:join_node
|
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] })
|
1027
1287
|
else
|
1028
|
-
|
1288
|
+
arel.project(table[Arel.star])
|
1029
1289
|
end
|
1030
1290
|
end
|
1031
1291
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
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
|
1307
|
+
end
|
1038
1308
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
join_list
|
1043
|
-
)
|
1309
|
+
def arel_column(field)
|
1310
|
+
field = klass.attribute_aliases[field] || field
|
1311
|
+
from = from_clause.name || from_clause.value
|
1044
1312
|
|
1045
|
-
|
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)
|
1319
|
+
end
|
1320
|
+
else
|
1321
|
+
yield field
|
1322
|
+
end
|
1323
|
+
end
|
1046
1324
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
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)
|
1050
1329
|
end
|
1051
1330
|
|
1052
|
-
|
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
|
1053
1337
|
|
1054
|
-
|
1055
|
-
|
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
|
1056
1359
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
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)
|
1364
|
+
|
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)
|
1062
1369
|
end
|
1063
|
-
end
|
1064
1370
|
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
arel_table[field]
|
1069
|
-
elsif Symbol === field
|
1070
|
-
connection.quote_table_name(field.to_s)
|
1071
|
-
else
|
1072
|
-
field
|
1073
|
-
end
|
1371
|
+
def build_order(arel)
|
1372
|
+
orders = order_values.compact_blank
|
1373
|
+
arel.order(*orders) unless orders.empty?
|
1074
1374
|
end
|
1075
|
-
end
|
1076
1375
|
|
1077
|
-
|
1078
|
-
|
1376
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1377
|
+
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
|
1079
1378
|
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
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
|
1088
1387
|
end
|
1089
|
-
else
|
1090
|
-
o
|
1091
1388
|
end
|
1092
1389
|
end
|
1093
|
-
end
|
1094
1390
|
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
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!
|
1420
|
+
end
|
1102
1421
|
|
1103
|
-
|
1104
|
-
|
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
|
1105
1429
|
|
1106
|
-
|
1107
|
-
|
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
|
1108
1435
|
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
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
|
1115
1443
|
end
|
1116
1444
|
end
|
1117
|
-
end
|
1118
1445
|
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
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
|
1126
1469
|
|
1127
|
-
#
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
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
|
1138
1491
|
else
|
1139
|
-
|
1492
|
+
args.flatten!
|
1493
|
+
args.compact_blank!
|
1140
1494
|
end
|
1141
|
-
end.flatten!
|
1142
|
-
end
|
1143
|
-
|
1144
|
-
# Checks to make sure that the arguments are not blank. Note that if some
|
1145
|
-
# blank-like object were initially passed into the query method, then this
|
1146
|
-
# method will not raise an error.
|
1147
|
-
#
|
1148
|
-
# Example:
|
1149
|
-
#
|
1150
|
-
# Post.references() # => raises an error
|
1151
|
-
# Post.references([]) # => does not raise an error
|
1152
|
-
#
|
1153
|
-
# This particular method should be called with a method_name and the args
|
1154
|
-
# passed into that method as an input. For example:
|
1155
|
-
#
|
1156
|
-
# def references(*args)
|
1157
|
-
# check_if_method_has_arguments!("references", args)
|
1158
|
-
# ...
|
1159
|
-
# end
|
1160
|
-
def check_if_method_has_arguments!(method_name, args)
|
1161
|
-
if args.blank?
|
1162
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1163
1495
|
end
|
1164
|
-
end
|
1165
1496
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
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
|
1173
1510
|
end
|
1511
|
+
v1 == v2
|
1174
1512
|
end
|
1175
1513
|
end
|
1514
|
+
end
|
1515
|
+
|
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:
|
1176
1520
|
end
|
1177
1521
|
end
|
1178
1522
|
end
|