activerecord 4.2.0 → 6.1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1221 -796
- 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 +267 -249
- data/lib/active_record/association_relation.rb +45 -7
- data/lib/active_record/associations/alias_tracker.rb +40 -43
- data/lib/active_record/associations/association.rb +172 -67
- data/lib/active_record/associations/association_scope.rb +105 -129
- data/lib/active_record/associations/belongs_to_association.rb +85 -59
- 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 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -70
- 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 +168 -279
- data/lib/active_record/associations/collection_proxy.rb +263 -155
- data/lib/active_record/associations/foreign_association.rb +33 -0
- data/lib/active_record/associations/has_many_association.rb +57 -84
- data/lib/active_record/associations/has_many_through_association.rb +70 -82
- data/lib/active_record/associations/has_one_association.rb +74 -47
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +54 -73
- 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 +175 -164
- data/lib/active_record/associations/preloader/association.rb +107 -112
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +99 -96
- data/lib/active_record/associations/singular_association.rb +18 -45
- data/lib/active_record/associations/through_association.rb +49 -24
- data/lib/active_record/associations.rb +1845 -1597
- data/lib/active_record/attribute_assignment.rb +59 -185
- data/lib/active_record/attribute_methods/before_type_cast.rb +20 -7
- data/lib/active_record/attribute_methods/dirty.rb +168 -138
- 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 +59 -36
- data/lib/active_record/attribute_methods/write.rb +25 -56
- data/lib/active_record/attribute_methods.rb +153 -162
- data/lib/active_record/attributes.rb +234 -70
- data/lib/active_record/autosave_association.rb +157 -69
- data/lib/active_record/base.rb +49 -50
- 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 +46 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +887 -317
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -41
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +301 -113
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +187 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +9 -7
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +157 -93
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +485 -253
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +909 -263
- data/lib/active_record/connection_adapters/abstract/transaction.rb +254 -92
- data/lib/active_record/connection_adapters/abstract_adapter.rb +492 -221
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +580 -608
- 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 +196 -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 +271 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +81 -199
- 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 +78 -161
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +49 -57
- 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 +5 -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 +17 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +6 -3
- 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 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +67 -51
- 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 +171 -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 +499 -293
- 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 +595 -382
- data/lib/active_record/connection_adapters/schema_cache.rb +191 -29
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +146 -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 -389
- 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 +488 -243
- data/lib/active_record/counter_cache.rb +71 -50
- data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/database_configurations.rb +273 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +87 -106
- data/lib/active_record/enum.rb +212 -94
- data/lib/active_record/errors.rb +225 -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 +11 -6
- 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 +273 -496
- 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 +64 -0
- data/lib/active_record/legacy_yaml_adapter.rb +52 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +103 -95
- 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 +298 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +685 -309
- data/lib/active_record/model_schema.rb +420 -113
- data/lib/active_record/nested_attributes.rb +265 -216
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +574 -135
- data/lib/active_record/query_cache.rb +29 -23
- data/lib/active_record/querying.rb +50 -31
- data/lib/active_record/railtie.rb +175 -54
- 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 +533 -216
- data/lib/active_record/readonly_attributes.rb +9 -4
- data/lib/active_record/reflection.rb +485 -310
- 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 +326 -244
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +318 -256
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +99 -84
- data/lib/active_record/relation/predicate_builder/array_handler.rb +26 -25
- 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 +139 -96
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +757 -409
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +23 -21
- data/lib/active_record/relation/where_clause.rb +239 -0
- data/lib/active_record/relation.rb +554 -342
- 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 -26
- data/lib/active_record/scoping/default.rb +96 -82
- 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 +133 -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 +366 -129
- data/lib/active_record/tasks/mysql_database_tasks.rb +68 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +87 -39
- 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 +291 -0
- data/lib/active_record/timestamp.rb +86 -43
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +181 -152
- 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 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +12 -5
- 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 +33 -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 +65 -48
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +44 -28
- 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 -10
- 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.rb +7 -5
- metadata +175 -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 -149
- data/lib/active_record/attribute_decorators.rb +0 -66
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- 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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- 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 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -22
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
- /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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
|
-
#
|
651
|
+
# Post.where(trashed: true).where(trashed: false)
|
652
|
+
# # WHERE `trashed` = 1 AND `trashed` = 0
|
653
|
+
#
|
654
|
+
# Post.where(trashed: true).rewhere(trashed: false)
|
655
|
+
# # WHERE `trashed` = 0
|
593
656
|
#
|
594
|
-
#
|
595
|
-
#
|
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,46 +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.
|
907
|
+
self.from_clause = Relation::FromClause.new(value, subquery_name)
|
760
908
|
self
|
761
909
|
end
|
762
910
|
|
763
911
|
# Specifies whether the records should be unique or not. For example:
|
764
912
|
#
|
765
913
|
# User.select(:name)
|
766
|
-
# #
|
914
|
+
# # Might return two records with the same name
|
767
915
|
#
|
768
916
|
# User.select(:name).distinct
|
769
|
-
# #
|
917
|
+
# # Returns 1 record per distinct name
|
770
918
|
#
|
771
919
|
# User.select(:name).distinct.distinct(false)
|
772
|
-
# #
|
920
|
+
# # You can also remove the uniqueness
|
773
921
|
def distinct(value = true)
|
774
922
|
spawn.distinct!(value)
|
775
923
|
end
|
776
|
-
alias uniq distinct
|
777
924
|
|
778
925
|
# Like #distinct, but modifies relation in place.
|
779
926
|
def distinct!(value = true) # :nodoc:
|
780
927
|
self.distinct_value = value
|
781
928
|
self
|
782
929
|
end
|
783
|
-
alias uniq! distinct!
|
784
930
|
|
785
931
|
# Used to extend a scope with additional methods, either through
|
786
932
|
# a module or through a block provided.
|
@@ -836,6 +982,27 @@ module ActiveRecord
|
|
836
982
|
self
|
837
983
|
end
|
838
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
|
+
|
839
1006
|
# Reverse the existing order clause on the relation.
|
840
1007
|
#
|
841
1008
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -844,333 +1011,514 @@ module ActiveRecord
|
|
844
1011
|
end
|
845
1012
|
|
846
1013
|
def reverse_order! # :nodoc:
|
847
|
-
orders = order_values.
|
848
|
-
orders.reject!(&:blank?)
|
1014
|
+
orders = order_values.compact_blank
|
849
1015
|
self.order_values = reverse_sql_order(orders)
|
850
1016
|
self
|
851
1017
|
end
|
852
1018
|
|
853
|
-
|
854
|
-
|
855
|
-
|
1019
|
+
def skip_query_cache!(value = true) # :nodoc:
|
1020
|
+
self.skip_query_cache_value = value
|
1021
|
+
self
|
856
1022
|
end
|
857
1023
|
|
858
|
-
|
1024
|
+
def skip_preloading! # :nodoc:
|
1025
|
+
self.skip_preloading_value = true
|
1026
|
+
self
|
1027
|
+
end
|
859
1028
|
|
860
|
-
|
861
|
-
|
1029
|
+
# Adds an SQL comment to queries generated from this relation. For example:
|
1030
|
+
#
|
1031
|
+
# User.annotate("selecting user names").select(:name)
|
1032
|
+
# # SELECT "users"."name" FROM "users" /* selecting user names */
|
1033
|
+
#
|
1034
|
+
# User.annotate("selecting", "user", "names").select(:name)
|
1035
|
+
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1036
|
+
#
|
1037
|
+
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1038
|
+
#
|
1039
|
+
# Some escaping is performed, however untrusted user input should not be used.
|
1040
|
+
def annotate(*args)
|
1041
|
+
check_if_method_has_arguments!(:annotate, args)
|
1042
|
+
spawn.annotate!(*args)
|
1043
|
+
end
|
862
1044
|
|
863
|
-
|
1045
|
+
# Like #annotate, but modifies relation in place.
|
1046
|
+
def annotate!(*args) # :nodoc:
|
1047
|
+
self.annotate_values += args
|
1048
|
+
self
|
1049
|
+
end
|
864
1050
|
|
865
|
-
|
1051
|
+
# Deduplicate multiple values.
|
1052
|
+
def uniq!(name)
|
1053
|
+
if values = @values[name]
|
1054
|
+
values.uniq! if values.is_a?(Array) && !values.empty?
|
1055
|
+
end
|
1056
|
+
self
|
1057
|
+
end
|
866
1058
|
|
867
|
-
|
1059
|
+
# Returns the Arel object associated with the relation.
|
1060
|
+
def arel(aliases = nil) # :nodoc:
|
1061
|
+
@arel ||= build_arel(aliases)
|
1062
|
+
end
|
868
1063
|
|
869
|
-
|
870
|
-
|
1064
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1065
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1066
|
+
klass, table, associations, join_type
|
1067
|
+
)
|
1068
|
+
end
|
871
1069
|
|
872
|
-
|
1070
|
+
protected
|
1071
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1072
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
873
1073
|
|
874
|
-
|
1074
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1075
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1076
|
+
end
|
1077
|
+
end
|
875
1078
|
|
876
|
-
|
1079
|
+
def build_where_clause(opts, rest = []) # :nodoc:
|
1080
|
+
opts = sanitize_forbidden_attributes(opts)
|
877
1081
|
|
878
|
-
|
879
|
-
|
880
|
-
|
1082
|
+
case opts
|
1083
|
+
when String, Array
|
1084
|
+
parts = [klass.sanitize_sql(rest.empty? ? opts : [opts, *rest])]
|
1085
|
+
when Hash
|
1086
|
+
opts = opts.transform_keys do |key|
|
1087
|
+
key = key.to_s
|
1088
|
+
klass.attribute_aliases[key] || key
|
1089
|
+
end
|
1090
|
+
references = PredicateBuilder.references(opts)
|
1091
|
+
self.references_values |= references unless references.empty?
|
881
1092
|
|
882
|
-
|
883
|
-
|
1093
|
+
parts = predicate_builder.build_from_hash(opts) do |table_name|
|
1094
|
+
lookup_table_klass_from_join_dependencies(table_name)
|
1095
|
+
end
|
1096
|
+
when Arel::Nodes::Node
|
1097
|
+
parts = [opts]
|
1098
|
+
else
|
1099
|
+
raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
|
1100
|
+
end
|
884
1101
|
|
885
|
-
|
886
|
-
if !VALID_UNSCOPING_VALUES.include?(scope)
|
887
|
-
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
1102
|
+
Relation::WhereClause.new(parts)
|
888
1103
|
end
|
1104
|
+
alias :build_having_clause :build_where_clause
|
889
1105
|
|
890
|
-
|
891
|
-
|
1106
|
+
private
|
1107
|
+
def lookup_table_klass_from_join_dependencies(table_name)
|
1108
|
+
each_join_dependencies do |join|
|
1109
|
+
return join.base_klass if table_name == join.table_name
|
1110
|
+
end
|
1111
|
+
nil
|
1112
|
+
end
|
892
1113
|
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
result = [] unless single_val_method
|
1114
|
+
def each_join_dependencies(join_dependencies = build_join_dependencies)
|
1115
|
+
join_dependencies.each do |join_dependency|
|
1116
|
+
join_dependency.each do |join|
|
1117
|
+
yield join
|
1118
|
+
end
|
1119
|
+
end
|
900
1120
|
end
|
901
1121
|
|
902
|
-
|
903
|
-
|
1122
|
+
def build_join_dependencies
|
1123
|
+
associations = joins_values | left_outer_joins_values
|
1124
|
+
associations |= eager_load_values unless eager_load_values.empty?
|
1125
|
+
associations |= includes_values unless includes_values.empty?
|
904
1126
|
|
905
|
-
|
906
|
-
|
1127
|
+
join_dependencies = []
|
1128
|
+
join_dependencies.unshift construct_join_dependency(
|
1129
|
+
select_association_list(associations, join_dependencies), nil
|
1130
|
+
)
|
1131
|
+
end
|
907
1132
|
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
912
|
-
subrelation.name == target_value
|
913
|
-
end
|
1133
|
+
def assert_mutability!
|
1134
|
+
raise ImmutableRelation if @loaded
|
1135
|
+
raise ImmutableRelation if defined?(@arel) && @arel
|
914
1136
|
end
|
915
1137
|
|
916
|
-
|
917
|
-
|
1138
|
+
def build_arel(aliases = nil)
|
1139
|
+
arel = Arel::SelectManager.new(table)
|
1140
|
+
|
1141
|
+
build_joins(arel.join_sources, aliases)
|
1142
|
+
|
1143
|
+
arel.where(where_clause.ast) unless where_clause.empty?
|
1144
|
+
arel.having(having_clause.ast) unless having_clause.empty?
|
1145
|
+
arel.take(build_cast_value("LIMIT", connection.sanitize_limit(limit_value))) if limit_value
|
1146
|
+
arel.skip(build_cast_value("OFFSET", offset_value.to_i)) if offset_value
|
1147
|
+
arel.group(*arel_columns(group_values.uniq)) unless group_values.empty?
|
1148
|
+
|
1149
|
+
build_order(arel)
|
1150
|
+
build_select(arel)
|
1151
|
+
|
1152
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1153
|
+
arel.distinct(distinct_value)
|
1154
|
+
arel.from(build_from) unless from_clause.empty?
|
1155
|
+
arel.lock(lock_value) if lock_value
|
1156
|
+
|
1157
|
+
unless annotate_values.empty?
|
1158
|
+
annotates = annotate_values
|
1159
|
+
annotates = annotates.uniq if annotates.size > 1
|
1160
|
+
unless annotates == annotate_values
|
1161
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
1162
|
+
Duplicated query annotations are no longer shown in queries in Rails 7.0.
|
1163
|
+
To migrate to Rails 7.0's behavior, use `uniq!(:annotate)` to deduplicate query annotations
|
1164
|
+
(`#{klass.name&.tableize || klass.table_name}.uniq!(:annotate)`).
|
1165
|
+
MSG
|
1166
|
+
annotates = annotate_values
|
1167
|
+
end
|
1168
|
+
arel.comment(*annotates)
|
1169
|
+
end
|
918
1170
|
|
919
|
-
|
920
|
-
|
1171
|
+
arel
|
1172
|
+
end
|
921
1173
|
|
922
|
-
|
1174
|
+
def build_cast_value(name, value)
|
1175
|
+
cast_value = ActiveModel::Attribute.with_cast_value(name, value, Type.default_value)
|
1176
|
+
Arel::Nodes::BindParam.new(cast_value)
|
1177
|
+
end
|
923
1178
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
when
|
929
|
-
|
1179
|
+
def build_from
|
1180
|
+
opts = from_clause.value
|
1181
|
+
name = from_clause.name
|
1182
|
+
case opts
|
1183
|
+
when Relation
|
1184
|
+
if opts.eager_loading?
|
1185
|
+
opts = opts.send(:apply_join_dependency)
|
1186
|
+
end
|
1187
|
+
name ||= "subquery"
|
1188
|
+
opts.arel.as(name.to_s)
|
1189
|
+
else
|
1190
|
+
opts
|
930
1191
|
end
|
931
|
-
table.create_string_join(join)
|
932
1192
|
end
|
933
|
-
end
|
934
1193
|
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
1194
|
+
def select_association_list(associations, stashed_joins = nil)
|
1195
|
+
result = []
|
1196
|
+
associations.each do |association|
|
1197
|
+
case association
|
1198
|
+
when Hash, Symbol, Array
|
1199
|
+
result << association
|
1200
|
+
when ActiveRecord::Associations::JoinDependency
|
1201
|
+
stashed_joins&.<< association
|
1202
|
+
else
|
1203
|
+
yield association if block_given?
|
1204
|
+
end
|
1205
|
+
end
|
1206
|
+
result
|
940
1207
|
end
|
941
1208
|
|
942
|
-
|
943
|
-
|
1209
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
1210
|
+
end
|
944
1211
|
|
945
|
-
|
946
|
-
|
947
|
-
when String, Array
|
948
|
-
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
949
|
-
when Hash
|
950
|
-
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
|
1212
|
+
def build_join_buckets
|
1213
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
951
1214
|
|
952
|
-
|
953
|
-
|
1215
|
+
unless left_outer_joins_values.empty?
|
1216
|
+
stashed_left_joins = []
|
1217
|
+
left_joins = select_association_list(left_outer_joins_values, stashed_left_joins) do
|
1218
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1219
|
+
end
|
954
1220
|
|
955
|
-
|
956
|
-
|
1221
|
+
if joins_values.empty?
|
1222
|
+
buckets[:association_join] = left_joins
|
1223
|
+
buckets[:stashed_join] = stashed_left_joins
|
1224
|
+
return buckets, Arel::Nodes::OuterJoin
|
1225
|
+
else
|
1226
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1227
|
+
end
|
1228
|
+
end
|
957
1229
|
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
end
|
1230
|
+
joins = joins_values.dup
|
1231
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1232
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1233
|
+
end
|
963
1234
|
|
964
|
-
|
965
|
-
|
966
|
-
case value
|
967
|
-
when String, Integer, ActiveRecord::StatementCache::Substitute
|
968
|
-
@klass.columns_hash.include? column.to_s
|
969
|
-
else
|
970
|
-
false
|
1235
|
+
joins.each_with_index do |join, i|
|
1236
|
+
joins[i] = Arel::Nodes::StringJoin.new(Arel.sql(join.strip)) if join.is_a?(String)
|
971
1237
|
end
|
972
|
-
end
|
973
1238
|
|
974
|
-
|
975
|
-
|
976
|
-
|
1239
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1240
|
+
join_node = joins.shift
|
1241
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1242
|
+
buckets[:join_node] << join_node
|
1243
|
+
else
|
1244
|
+
buckets[:leading_join] << join_node
|
1245
|
+
end
|
1246
|
+
end
|
977
1247
|
|
978
|
-
|
979
|
-
|
1248
|
+
buckets[:association_join] = select_association_list(joins, buckets[:stashed_join]) do |join|
|
1249
|
+
if join.is_a?(Arel::Nodes::Join)
|
1250
|
+
buckets[:join_node] << join
|
1251
|
+
else
|
1252
|
+
raise "unknown class: %s" % join.class.name
|
1253
|
+
end
|
1254
|
+
end
|
980
1255
|
|
981
|
-
|
982
|
-
|
983
|
-
new_opts[column] = connection.substitute_at(column)
|
984
|
-
end
|
1256
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1257
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
985
1258
|
|
986
|
-
|
987
|
-
association_relation = association_for_table(column).klass.send(:relation)
|
988
|
-
association_new_opts, association_bind = association_relation.send(:create_binds, value)
|
989
|
-
new_opts[column] = association_new_opts
|
990
|
-
binds += association_bind
|
1259
|
+
return buckets, Arel::Nodes::InnerJoin
|
991
1260
|
end
|
992
1261
|
|
993
|
-
|
1262
|
+
def build_joins(join_sources, aliases = nil)
|
1263
|
+
return join_sources if joins_values.empty? && left_outer_joins_values.empty?
|
994
1264
|
|
995
|
-
|
996
|
-
end
|
1265
|
+
buckets, join_type = build_join_buckets
|
997
1266
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
end
|
1267
|
+
association_joins = buckets[:association_join]
|
1268
|
+
stashed_joins = buckets[:stashed_join]
|
1269
|
+
leading_joins = buckets[:leading_join]
|
1270
|
+
join_nodes = buckets[:join_node]
|
1003
1271
|
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1272
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1273
|
+
|
1274
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1275
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1276
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1277
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker, references_values))
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1281
|
+
join_sources
|
1013
1282
|
end
|
1014
|
-
end
|
1015
1283
|
|
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
|
1284
|
+
def build_select(arel)
|
1285
|
+
if select_values.any?
|
1286
|
+
arel.project(*arel_columns(select_values))
|
1287
|
+
elsif klass.ignored_columns.any?
|
1288
|
+
arel.project(*klass.column_names.map { |field| table[field] })
|
1027
1289
|
else
|
1028
|
-
|
1290
|
+
arel.project(table[Arel.star])
|
1029
1291
|
end
|
1030
1292
|
end
|
1031
1293
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1294
|
+
def arel_columns(columns)
|
1295
|
+
columns.flat_map do |field|
|
1296
|
+
case field
|
1297
|
+
when Symbol
|
1298
|
+
arel_column(field.to_s) do |attr_name|
|
1299
|
+
connection.quote_table_name(attr_name)
|
1300
|
+
end
|
1301
|
+
when String
|
1302
|
+
arel_column(field, &:itself)
|
1303
|
+
when Proc
|
1304
|
+
field.call
|
1305
|
+
else
|
1306
|
+
field
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
end
|
1044
1310
|
|
1045
|
-
|
1311
|
+
def arel_column(field)
|
1312
|
+
field = klass.attribute_aliases[field] || field
|
1313
|
+
from = from_clause.name || from_clause.value
|
1046
1314
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1315
|
+
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1316
|
+
table[field]
|
1317
|
+
elsif field.match?(/\A\w+\.\w+\z/)
|
1318
|
+
table, column = field.split(".")
|
1319
|
+
predicate_builder.resolve_arel_attribute(table, column) do
|
1320
|
+
lookup_table_klass_from_join_dependencies(table)
|
1321
|
+
end
|
1322
|
+
else
|
1323
|
+
yield field
|
1324
|
+
end
|
1050
1325
|
end
|
1051
1326
|
|
1052
|
-
|
1327
|
+
def table_name_matches?(from)
|
1328
|
+
table_name = Regexp.escape(table.name)
|
1329
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1330
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1331
|
+
end
|
1053
1332
|
|
1054
|
-
|
1055
|
-
|
1333
|
+
def reverse_sql_order(order_query)
|
1334
|
+
if order_query.empty?
|
1335
|
+
return [table[primary_key].desc] if primary_key
|
1336
|
+
raise IrreversibleOrderError,
|
1337
|
+
"Relation has no current order and table has no primary key to be used as default order"
|
1338
|
+
end
|
1056
1339
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1340
|
+
order_query.flat_map do |o|
|
1341
|
+
case o
|
1342
|
+
when Arel::Attribute
|
1343
|
+
o.desc
|
1344
|
+
when Arel::Nodes::Ordering
|
1345
|
+
o.reverse
|
1346
|
+
when Arel::Nodes::NodeExpression
|
1347
|
+
o.desc
|
1348
|
+
when String
|
1349
|
+
if does_not_support_reverse?(o)
|
1350
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1351
|
+
end
|
1352
|
+
o.split(",").map! do |s|
|
1353
|
+
s.strip!
|
1354
|
+
s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
|
1355
|
+
end
|
1062
1356
|
else
|
1063
|
-
|
1357
|
+
o
|
1064
1358
|
end
|
1065
1359
|
end
|
1360
|
+
end
|
1066
1361
|
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1362
|
+
def does_not_support_reverse?(order)
|
1363
|
+
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
1364
|
+
# override methods like #count.
|
1365
|
+
order = String.new(order) unless order.instance_of?(String)
|
1366
|
+
|
1367
|
+
# Uses SQL function with multiple arguments.
|
1368
|
+
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1369
|
+
# Uses "nulls first" like construction.
|
1370
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
def build_order(arel)
|
1374
|
+
orders = order_values.compact_blank
|
1375
|
+
arel.order(*orders) unless orders.empty?
|
1070
1376
|
end
|
1071
|
-
end
|
1072
1377
|
|
1073
|
-
|
1074
|
-
|
1378
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1379
|
+
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
|
1075
1380
|
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1381
|
+
def validate_order_args(args)
|
1382
|
+
args.each do |arg|
|
1383
|
+
next unless arg.is_a?(Hash)
|
1384
|
+
arg.each do |_key, value|
|
1385
|
+
unless VALID_DIRECTIONS.include?(value)
|
1386
|
+
raise ArgumentError,
|
1387
|
+
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1388
|
+
end
|
1084
1389
|
end
|
1085
|
-
else
|
1086
|
-
o
|
1087
1390
|
end
|
1088
1391
|
end
|
1089
|
-
end
|
1090
1392
|
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1393
|
+
def preprocess_order_args(order_args)
|
1394
|
+
@klass.disallow_raw_sql!(
|
1395
|
+
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1396
|
+
permit: connection.column_name_with_order_matcher
|
1397
|
+
)
|
1398
|
+
|
1399
|
+
validate_order_args(order_args)
|
1400
|
+
|
1401
|
+
references = column_references(order_args)
|
1402
|
+
self.references_values |= references unless references.empty?
|
1403
|
+
|
1404
|
+
# if a symbol is given we prepend the quoted table name
|
1405
|
+
order_args.map! do |arg|
|
1406
|
+
case arg
|
1407
|
+
when Symbol
|
1408
|
+
order_column(arg.to_s).asc
|
1409
|
+
when Hash
|
1410
|
+
arg.map { |field, dir|
|
1411
|
+
case field
|
1412
|
+
when Arel::Nodes::SqlLiteral
|
1413
|
+
field.public_send(dir.downcase)
|
1414
|
+
else
|
1415
|
+
order_column(field.to_s).public_send(dir.downcase)
|
1416
|
+
end
|
1417
|
+
}
|
1418
|
+
else
|
1419
|
+
arg
|
1420
|
+
end
|
1421
|
+
end.flatten!
|
1422
|
+
end
|
1098
1423
|
|
1099
|
-
|
1100
|
-
|
1424
|
+
def sanitize_order_arguments(order_args)
|
1425
|
+
order_args.map! do |arg|
|
1426
|
+
klass.sanitize_sql_for_order(arg)
|
1427
|
+
end
|
1428
|
+
order_args.flatten!
|
1429
|
+
order_args.compact_blank!
|
1430
|
+
end
|
1101
1431
|
|
1102
|
-
|
1103
|
-
|
1432
|
+
def column_references(order_args)
|
1433
|
+
references = order_args.grep(String)
|
1434
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1435
|
+
references
|
1436
|
+
end
|
1104
1437
|
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1438
|
+
def order_column(field)
|
1439
|
+
arel_column(field) do |attr_name|
|
1440
|
+
if attr_name == "count" && !group_values.empty?
|
1441
|
+
table[attr_name]
|
1442
|
+
else
|
1443
|
+
Arel.sql(connection.quote_table_name(attr_name))
|
1444
|
+
end
|
1111
1445
|
end
|
1112
1446
|
end
|
1113
|
-
end
|
1114
1447
|
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1448
|
+
def resolve_arel_attributes(attrs)
|
1449
|
+
attrs.flat_map do |attr|
|
1450
|
+
case attr
|
1451
|
+
when Arel::Predications
|
1452
|
+
attr
|
1453
|
+
when Hash
|
1454
|
+
attr.flat_map do |table, columns|
|
1455
|
+
table = table.to_s
|
1456
|
+
Array(columns).map do |column|
|
1457
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1458
|
+
end
|
1459
|
+
end
|
1460
|
+
else
|
1461
|
+
attr = attr.to_s
|
1462
|
+
if attr.include?(".")
|
1463
|
+
table, column = attr.split(".", 2)
|
1464
|
+
predicate_builder.resolve_arel_attribute(table, column)
|
1465
|
+
else
|
1466
|
+
attr
|
1467
|
+
end
|
1468
|
+
end
|
1469
|
+
end
|
1470
|
+
end
|
1122
1471
|
|
1123
|
-
#
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1472
|
+
# Checks to make sure that the arguments are not blank. Note that if some
|
1473
|
+
# blank-like object were initially passed into the query method, then this
|
1474
|
+
# method will not raise an error.
|
1475
|
+
#
|
1476
|
+
# Example:
|
1477
|
+
#
|
1478
|
+
# Post.references() # raises an error
|
1479
|
+
# Post.references([]) # does not raise an error
|
1480
|
+
#
|
1481
|
+
# This particular method should be called with a method_name and the args
|
1482
|
+
# passed into that method as an input. For example:
|
1483
|
+
#
|
1484
|
+
# def references(*args)
|
1485
|
+
# check_if_method_has_arguments!("references", args)
|
1486
|
+
# ...
|
1487
|
+
# end
|
1488
|
+
def check_if_method_has_arguments!(method_name, args, message = nil)
|
1489
|
+
if args.blank?
|
1490
|
+
raise ArgumentError, message || "The method .#{method_name}() must contain arguments."
|
1491
|
+
elsif block_given?
|
1492
|
+
yield args
|
1134
1493
|
else
|
1135
|
-
|
1494
|
+
args.flatten!
|
1495
|
+
args.compact_blank!
|
1136
1496
|
end
|
1137
|
-
end.flatten!
|
1138
|
-
end
|
1139
|
-
|
1140
|
-
# Checks to make sure that the arguments are not blank. Note that if some
|
1141
|
-
# blank-like object were initially passed into the query method, then this
|
1142
|
-
# method will not raise an error.
|
1143
|
-
#
|
1144
|
-
# Example:
|
1145
|
-
#
|
1146
|
-
# Post.references() # => raises an error
|
1147
|
-
# Post.references([]) # => does not raise an error
|
1148
|
-
#
|
1149
|
-
# This particular method should be called with a method_name and the args
|
1150
|
-
# passed into that method as an input. For example:
|
1151
|
-
#
|
1152
|
-
# def references(*args)
|
1153
|
-
# check_if_method_has_arguments!("references", args)
|
1154
|
-
# ...
|
1155
|
-
# end
|
1156
|
-
def check_if_method_has_arguments!(method_name, args)
|
1157
|
-
if args.blank?
|
1158
|
-
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1159
1497
|
end
|
1160
|
-
end
|
1161
1498
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1499
|
+
STRUCTURAL_VALUE_METHODS = (
|
1500
|
+
Relation::VALUE_METHODS -
|
1501
|
+
[:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1502
|
+
).freeze # :nodoc:
|
1503
|
+
|
1504
|
+
def structurally_incompatible_values_for(other)
|
1505
|
+
values = other.values
|
1506
|
+
STRUCTURAL_VALUE_METHODS.reject do |method|
|
1507
|
+
v1, v2 = @values[method], values[method]
|
1508
|
+
if v1.is_a?(Array)
|
1509
|
+
next true unless v2.is_a?(Array)
|
1510
|
+
v1 = v1.uniq
|
1511
|
+
v2 = v2.uniq
|
1171
1512
|
end
|
1513
|
+
v1 == v2
|
1172
1514
|
end
|
1173
1515
|
end
|
1516
|
+
end
|
1517
|
+
|
1518
|
+
class Relation # :nodoc:
|
1519
|
+
# No-op WhereClauseFactory to work Mashal.load(File.read("legacy_relation.dump")).
|
1520
|
+
# TODO: Remove the class once Rails 6.1 has released.
|
1521
|
+
class WhereClauseFactory # :nodoc:
|
1174
1522
|
end
|
1175
1523
|
end
|
1176
1524
|
end
|