activerecord 4.2.11.1 → 6.0.3.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +721 -1522
- data/MIT-LICENSE +4 -2
- data/README.rdoc +14 -13
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record/advisory_lock_base.rb +18 -0
- data/lib/active_record/aggregations.rb +266 -251
- data/lib/active_record/association_relation.rb +20 -13
- data/lib/active_record/associations/alias_tracker.rb +29 -36
- data/lib/active_record/associations/association.rb +128 -57
- data/lib/active_record/associations/association_scope.rb +103 -132
- data/lib/active_record/associations/belongs_to_association.rb +65 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
- data/lib/active_record/associations/builder/association.rb +27 -40
- data/lib/active_record/associations/builder/belongs_to.rb +69 -55
- data/lib/active_record/associations/builder/collection_association.rb +10 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -66
- data/lib/active_record/associations/builder/has_many.rb +8 -4
- data/lib/active_record/associations/builder/has_one.rb +46 -5
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +136 -288
- data/lib/active_record/associations/collection_proxy.rb +241 -147
- data/lib/active_record/associations/foreign_association.rb +10 -1
- data/lib/active_record/associations/has_many_association.rb +34 -98
- data/lib/active_record/associations/has_many_through_association.rb +60 -87
- data/lib/active_record/associations/has_one_association.rb +61 -49
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +38 -86
- 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 +149 -166
- data/lib/active_record/associations/preloader/association.rb +90 -123
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +90 -93
- data/lib/active_record/associations/singular_association.rb +18 -39
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/associations.rb +1737 -1597
- data/lib/active_record/attribute_assignment.rb +57 -185
- data/lib/active_record/attribute_decorators.rb +39 -17
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +174 -144
- data/lib/active_record/attribute_methods/primary_key.rb +90 -84
- data/lib/active_record/attribute_methods/query.rb +6 -5
- data/lib/active_record/attribute_methods/read.rb +20 -77
- data/lib/active_record/attribute_methods/serialization.rb +40 -21
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +57 -37
- data/lib/active_record/attribute_methods/write.rb +32 -55
- data/lib/active_record/attribute_methods.rb +120 -135
- data/lib/active_record/attributes.rb +213 -82
- data/lib/active_record/autosave_association.rb +97 -41
- data/lib/active_record/base.rb +57 -45
- data/lib/active_record/callbacks.rb +101 -76
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +23 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +804 -297
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +240 -115
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +83 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -47
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +371 -242
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +694 -256
- data/lib/active_record/connection_adapters/abstract/transaction.rb +190 -83
- data/lib/active_record/connection_adapters/abstract_adapter.rb +473 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +507 -639
- data/lib/active_record/connection_adapters/column.rb +56 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +174 -153
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -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 +81 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -181
- data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +70 -114
- 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 +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- 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/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/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 +51 -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 +9 -5
- 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 +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -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 +462 -296
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +11 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -356
- data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -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 +17 -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 +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +299 -349
- data/lib/active_record/connection_adapters/statement_pool.rb +33 -13
- data/lib/active_record/connection_handling.rb +167 -41
- data/lib/active_record/core.rb +252 -230
- data/lib/active_record/counter_cache.rb +70 -49
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -106
- data/lib/active_record/enum.rb +163 -86
- data/lib/active_record/errors.rb +188 -53
- data/lib/active_record/explain.rb +22 -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 +35 -9
- data/lib/active_record/fixture_set/model_metadata.rb +33 -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 +227 -501
- data/lib/active_record/gem_version.rb +6 -4
- data/lib/active_record/inheritance.rb +158 -115
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +123 -29
- data/lib/active_record/internal_metadata.rb +53 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +86 -96
- data/lib/active_record/locking/pessimistic.rb +18 -6
- data/lib/active_record/log_subscriber.rb +76 -33
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
- data/lib/active_record/middleware/database_selector.rb +74 -0
- data/lib/active_record/migration/command_recorder.rb +166 -91
- data/lib/active_record/migration/compatibility.rb +244 -0
- data/lib/active_record/migration/join_table.rb +8 -7
- data/lib/active_record/migration.rb +623 -305
- data/lib/active_record/model_schema.rb +313 -112
- data/lib/active_record/nested_attributes.rb +263 -223
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +557 -126
- data/lib/active_record/query_cache.rb +19 -23
- data/lib/active_record/querying.rb +44 -30
- data/lib/active_record/railtie.rb +143 -44
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +331 -185
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +430 -281
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +206 -55
- data/lib/active_record/relation/calculations.rb +268 -254
- data/lib/active_record/relation/delegation.rb +75 -84
- data/lib/active_record/relation/finder_methods.rb +285 -241
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +78 -88
- data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -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 +53 -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 +110 -119
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +603 -397
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -14
- data/lib/active_record/relation/where_clause.rb +189 -0
- data/lib/active_record/relation/where_clause_factory.rb +33 -0
- data/lib/active_record/relation.rb +530 -341
- data/lib/active_record/result.rb +79 -43
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +144 -121
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +112 -93
- data/lib/active_record/schema_migration.rb +24 -17
- data/lib/active_record/scoping/default.rb +98 -83
- data/lib/active_record/scoping/named.rb +86 -33
- data/lib/active_record/scoping.rb +45 -27
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +73 -36
- data/lib/active_record/store.rb +127 -42
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +90 -0
- data/lib/active_record/tasks/database_tasks.rb +307 -100
- data/lib/active_record/tasks/mysql_database_tasks.rb +55 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +80 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +37 -16
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +225 -0
- data/lib/active_record/timestamp.rb +86 -41
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +223 -157
- 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 +23 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -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 +77 -23
- data/lib/active_record/type_caster/connection.rb +34 -0
- data/lib/active_record/type_caster/map.rb +20 -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/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +42 -55
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +42 -22
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -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 +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -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 +18 -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 +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -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 +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -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 +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -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 +68 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +256 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors/depth_first.rb +203 -0
- data/lib/arel/visitors/dot.rb +296 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +156 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +158 -0
- data/lib/arel/visitors/oracle12.rb +65 -0
- data/lib/arel/visitors/postgresql.rb +109 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +888 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors/where_sql.rb +22 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +62 -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 +42 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
- data/lib/rails/generators/active_record/migration.rb +30 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- 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 +168 -59
- 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_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- 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_record/relation/where_clause_factory"
|
7
|
+
require "active_model/forbidden_attributes_protection"
|
4
8
|
|
5
9
|
module ActiveRecord
|
6
10
|
module QueryMethods
|
@@ -11,6 +15,8 @@ module ActiveRecord
|
|
11
15
|
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
|
12
16
|
# In this case, #where must be chained with #not to return a new relation.
|
13
17
|
class WhereChain
|
18
|
+
include ActiveModel::ForbiddenAttributesProtection
|
19
|
+
|
14
20
|
def initialize(scope)
|
15
21
|
@scope = scope
|
16
22
|
end
|
@@ -18,7 +24,7 @@ module ActiveRecord
|
|
18
24
|
# Returns a new relation expressing WHERE + NOT condition according to
|
19
25
|
# the conditions in the arguments.
|
20
26
|
#
|
21
|
-
#
|
27
|
+
# #not accepts conditions as a string, array, or hash. See QueryMethods#where for
|
22
28
|
# more details on each format.
|
23
29
|
#
|
24
30
|
# User.where.not("name = 'Jon'")
|
@@ -35,77 +41,67 @@ module ActiveRecord
|
|
35
41
|
#
|
36
42
|
# User.where.not(name: %w(Ko1 Nobu))
|
37
43
|
# # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
|
38
|
-
#
|
39
|
-
# User.where.not(name: "Jon", role: "admin")
|
40
|
-
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
|
41
44
|
def not(opts, *rest)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
raise ArgumentError, 'Invalid argument for .where.not(), got nil.'
|
46
|
-
when Arel::Nodes::In
|
47
|
-
Arel::Nodes::NotIn.new(rel.left, rel.right)
|
48
|
-
when Arel::Nodes::Equality
|
49
|
-
Arel::Nodes::NotEqual.new(rel.left, rel.right)
|
50
|
-
when String
|
51
|
-
Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel))
|
52
|
-
else
|
53
|
-
Arel::Nodes::Not.new(rel)
|
54
|
-
end
|
55
|
-
end
|
45
|
+
opts = sanitize_forbidden_attributes(opts)
|
46
|
+
|
47
|
+
where_clause = @scope.send(:where_clause_factory).build(opts, rest)
|
56
48
|
|
57
49
|
@scope.references!(PredicateBuilder.references(opts)) if Hash === opts
|
58
|
-
|
50
|
+
|
51
|
+
if not_behaves_as_nor?(opts)
|
52
|
+
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
53
|
+
NOT conditions will no longer behave as NOR in Rails 6.1.
|
54
|
+
To continue using NOR conditions, NOT each condition individually
|
55
|
+
(`#{
|
56
|
+
opts.flat_map { |key, value|
|
57
|
+
if value.is_a?(Hash) && value.size > 1
|
58
|
+
value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
|
59
|
+
else
|
60
|
+
".where.not(#{key.inspect} => ...)"
|
61
|
+
end
|
62
|
+
}.join
|
63
|
+
}`).
|
64
|
+
MSG
|
65
|
+
@scope.where_clause += where_clause.invert(:nor)
|
66
|
+
else
|
67
|
+
@scope.where_clause += where_clause.invert
|
68
|
+
end
|
69
|
+
|
59
70
|
@scope
|
60
71
|
end
|
61
|
-
end
|
62
72
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
73
|
+
private
|
74
|
+
def not_behaves_as_nor?(opts)
|
75
|
+
return false unless opts.is_a?(Hash)
|
76
|
+
|
77
|
+
opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
|
78
|
+
opts.size > 1
|
79
|
+
end
|
75
80
|
end
|
76
81
|
|
77
|
-
|
82
|
+
FROZEN_EMPTY_ARRAY = [].freeze
|
83
|
+
FROZEN_EMPTY_HASH = {}.freeze
|
84
|
+
|
85
|
+
Relation::VALUE_METHODS.each do |name|
|
86
|
+
method_name = \
|
87
|
+
case name
|
88
|
+
when *Relation::MULTI_VALUE_METHODS then "#{name}_values"
|
89
|
+
when *Relation::SINGLE_VALUE_METHODS then "#{name}_value"
|
90
|
+
when *Relation::CLAUSE_METHODS then "#{name}_clause"
|
91
|
+
end
|
78
92
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
79
|
-
def #{
|
80
|
-
|
93
|
+
def #{method_name} # def includes_values
|
94
|
+
default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
|
95
|
+
@values.fetch(:#{name}, default) # @values.fetch(:includes, default)
|
81
96
|
end # end
|
82
|
-
CODE
|
83
|
-
end
|
84
97
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
|
89
|
-
check_cached_relation
|
90
|
-
@values[:#{name}] = value # @values[:readonly] = value
|
98
|
+
def #{method_name}=(value) # def includes_values=(value)
|
99
|
+
assert_mutability! # assert_mutability!
|
100
|
+
@values[:#{name}] = value # @values[:includes] = value
|
91
101
|
end # end
|
92
102
|
CODE
|
93
103
|
end
|
94
104
|
|
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
105
|
alias extensions extending_values
|
110
106
|
|
111
107
|
# Specify relationships to be included in the result set. For
|
@@ -118,7 +114,7 @@ module ActiveRecord
|
|
118
114
|
#
|
119
115
|
# allows you to access the +address+ attribute of the +User+ model without
|
120
116
|
# firing an additional query. This will often result in a
|
121
|
-
# performance improvement over a simple
|
117
|
+
# performance improvement over a simple join.
|
122
118
|
#
|
123
119
|
# You can also specify multiple relationships, like this:
|
124
120
|
#
|
@@ -130,7 +126,7 @@ module ActiveRecord
|
|
130
126
|
#
|
131
127
|
# === conditions
|
132
128
|
#
|
133
|
-
# If you want to add conditions to your included models you'll have
|
129
|
+
# If you want to add string conditions to your included models, you'll have
|
134
130
|
# to explicitly reference them. For example:
|
135
131
|
#
|
136
132
|
# User.includes(:posts).where('posts.name = ?', 'example')
|
@@ -139,8 +135,14 @@ module ActiveRecord
|
|
139
135
|
#
|
140
136
|
# User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
|
141
137
|
#
|
142
|
-
# Note that
|
138
|
+
# Note that #includes works with association names while #references needs
|
143
139
|
# the actual table name.
|
140
|
+
#
|
141
|
+
# If you pass the conditions via hash, you don't need to call #references
|
142
|
+
# explicitly, as #where references the tables for you. For example, this
|
143
|
+
# will work correctly:
|
144
|
+
#
|
145
|
+
# User.includes(:posts).where(posts: { name: 'example' })
|
144
146
|
def includes(*args)
|
145
147
|
check_if_method_has_arguments!(:includes, args)
|
146
148
|
spawn.includes!(*args)
|
@@ -157,43 +159,56 @@ module ActiveRecord
|
|
157
159
|
# Forces eager loading by performing a LEFT OUTER JOIN on +args+:
|
158
160
|
#
|
159
161
|
# User.eager_load(:posts)
|
160
|
-
#
|
161
|
-
# FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
162
|
-
# "users"."id"
|
162
|
+
# # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ...
|
163
|
+
# # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" =
|
164
|
+
# # "users"."id"
|
163
165
|
def eager_load(*args)
|
164
166
|
check_if_method_has_arguments!(:eager_load, args)
|
165
167
|
spawn.eager_load!(*args)
|
166
168
|
end
|
167
169
|
|
168
170
|
def eager_load!(*args) # :nodoc:
|
169
|
-
self.eager_load_values
|
171
|
+
self.eager_load_values |= args
|
170
172
|
self
|
171
173
|
end
|
172
174
|
|
173
|
-
# Allows preloading of +args+, in the same way that
|
175
|
+
# Allows preloading of +args+, in the same way that #includes does:
|
174
176
|
#
|
175
177
|
# User.preload(:posts)
|
176
|
-
#
|
178
|
+
# # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3)
|
177
179
|
def preload(*args)
|
178
180
|
check_if_method_has_arguments!(:preload, args)
|
179
181
|
spawn.preload!(*args)
|
180
182
|
end
|
181
183
|
|
182
184
|
def preload!(*args) # :nodoc:
|
183
|
-
self.preload_values
|
185
|
+
self.preload_values |= args
|
184
186
|
self
|
185
187
|
end
|
186
188
|
|
189
|
+
# Extracts a named +association+ from the relation. The named association is first preloaded,
|
190
|
+
# then the individual association records are collected from the relation. Like so:
|
191
|
+
#
|
192
|
+
# account.memberships.extract_associated(:user)
|
193
|
+
# # => Returns collection of User records
|
194
|
+
#
|
195
|
+
# This is short-hand for:
|
196
|
+
#
|
197
|
+
# account.memberships.preload(:user).collect(&:user)
|
198
|
+
def extract_associated(association)
|
199
|
+
preload(association).collect(&association)
|
200
|
+
end
|
201
|
+
|
187
202
|
# Use to indicate that the given +table_names+ are referenced by an SQL string,
|
188
203
|
# and should therefore be JOINed in any query rather than loaded separately.
|
189
|
-
# This method only works in conjunction with
|
204
|
+
# This method only works in conjunction with #includes.
|
190
205
|
# See #includes for more details.
|
191
206
|
#
|
192
207
|
# User.includes(:posts).where("posts.name = 'foo'")
|
193
|
-
# #
|
208
|
+
# # Doesn't JOIN the posts table, resulting in an error.
|
194
209
|
#
|
195
210
|
# User.includes(:posts).where("posts.name = 'foo'").references(:posts)
|
196
|
-
# #
|
211
|
+
# # Query now knows the string references posts, so adds a JOIN
|
197
212
|
def references(*table_names)
|
198
213
|
check_if_method_has_arguments!(:references, table_names)
|
199
214
|
spawn.references!(*table_names)
|
@@ -209,12 +224,13 @@ module ActiveRecord
|
|
209
224
|
|
210
225
|
# Works in two unique ways.
|
211
226
|
#
|
212
|
-
# First: takes a block so it can be used just like Array#select
|
227
|
+
# First: takes a block so it can be used just like <tt>Array#select</tt>.
|
213
228
|
#
|
214
229
|
# Model.all.select { |m| m.field == value }
|
215
230
|
#
|
216
231
|
# This will build an array of objects from the database for the scope,
|
217
|
-
# converting them into an array and iterating through them using
|
232
|
+
# converting them into an array and iterating through them using
|
233
|
+
# <tt>Array#select</tt>.
|
218
234
|
#
|
219
235
|
# Second: Modifies the SELECT statement for the query so that only certain
|
220
236
|
# fields are retrieved:
|
@@ -242,47 +258,71 @@ module ActiveRecord
|
|
242
258
|
# # => "value"
|
243
259
|
#
|
244
260
|
# Accessing attributes of an object that do not have fields retrieved by a select
|
245
|
-
# except +id+ will throw
|
261
|
+
# except +id+ will throw ActiveModel::MissingAttributeError:
|
246
262
|
#
|
247
263
|
# Model.select(:field).first.other_field
|
248
264
|
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
|
249
265
|
def select(*fields)
|
250
266
|
if block_given?
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
267
|
+
if fields.any?
|
268
|
+
raise ArgumentError, "`select' with block doesn't take arguments."
|
269
|
+
end
|
270
|
+
|
271
|
+
return super()
|
255
272
|
end
|
273
|
+
|
274
|
+
raise ArgumentError, "Call `select' with at least one field" if fields.empty?
|
275
|
+
spawn._select!(*fields)
|
256
276
|
end
|
257
277
|
|
258
278
|
def _select!(*fields) # :nodoc:
|
279
|
+
fields.reject!(&:blank?)
|
259
280
|
fields.flatten!
|
260
|
-
fields.map! do |field|
|
261
|
-
klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
|
262
|
-
end
|
263
281
|
self.select_values += fields
|
264
282
|
self
|
265
283
|
end
|
266
284
|
|
285
|
+
# Allows you to change a previously set select statement.
|
286
|
+
#
|
287
|
+
# Post.select(:title, :body)
|
288
|
+
# # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
|
289
|
+
#
|
290
|
+
# Post.select(:title, :body).reselect(:created_at)
|
291
|
+
# # SELECT `posts`.`created_at` FROM `posts`
|
292
|
+
#
|
293
|
+
# This is short-hand for <tt>unscope(:select).select(fields)</tt>.
|
294
|
+
# Note that we're unscoping the entire select statement.
|
295
|
+
def reselect(*args)
|
296
|
+
check_if_method_has_arguments!(:reselect, args)
|
297
|
+
spawn.reselect!(*args)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Same as #reselect but operates on relation in-place instead of copying.
|
301
|
+
def reselect!(*args) # :nodoc:
|
302
|
+
self.select_values = args
|
303
|
+
self
|
304
|
+
end
|
305
|
+
|
267
306
|
# Allows to specify a group attribute:
|
268
307
|
#
|
269
308
|
# User.group(:name)
|
270
|
-
#
|
309
|
+
# # SELECT "users".* FROM "users" GROUP BY name
|
271
310
|
#
|
272
311
|
# Returns an array with distinct records based on the +group+ attribute:
|
273
312
|
#
|
274
313
|
# User.select([:id, :name])
|
275
|
-
# => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
|
314
|
+
# # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">]
|
276
315
|
#
|
277
316
|
# User.group(:name)
|
278
|
-
# => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
|
317
|
+
# # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
|
279
318
|
#
|
280
319
|
# 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, ...>]
|
320
|
+
# # => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
|
282
321
|
#
|
283
322
|
# Passing in an array of attributes to group by is also supported.
|
323
|
+
#
|
284
324
|
# 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">]
|
325
|
+
# # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
|
286
326
|
def group(*args)
|
287
327
|
check_if_method_has_arguments!(:group, args)
|
288
328
|
spawn.group!(*args)
|
@@ -291,34 +331,35 @@ module ActiveRecord
|
|
291
331
|
def group!(*args) # :nodoc:
|
292
332
|
args.flatten!
|
293
333
|
|
294
|
-
self.group_values
|
334
|
+
self.group_values |= args
|
295
335
|
self
|
296
336
|
end
|
297
337
|
|
298
338
|
# Allows to specify an order attribute:
|
299
339
|
#
|
300
340
|
# User.order(:name)
|
301
|
-
#
|
341
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
|
302
342
|
#
|
303
343
|
# User.order(email: :desc)
|
304
|
-
#
|
344
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC
|
305
345
|
#
|
306
346
|
# User.order(:name, email: :desc)
|
307
|
-
#
|
347
|
+
# # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
|
308
348
|
#
|
309
349
|
# User.order('name')
|
310
|
-
#
|
350
|
+
# # SELECT "users".* FROM "users" ORDER BY name
|
311
351
|
#
|
312
352
|
# User.order('name DESC')
|
313
|
-
#
|
353
|
+
# # SELECT "users".* FROM "users" ORDER BY name DESC
|
314
354
|
#
|
315
355
|
# User.order('name DESC, email')
|
316
|
-
#
|
356
|
+
# # SELECT "users".* FROM "users" ORDER BY name DESC, email
|
317
357
|
def order(*args)
|
318
358
|
check_if_method_has_arguments!(:order, args)
|
319
359
|
spawn.order!(*args)
|
320
360
|
end
|
321
361
|
|
362
|
+
# Same as #order but operates on relation in-place instead of copying.
|
322
363
|
def order!(*args) # :nodoc:
|
323
364
|
preprocess_order_args(args)
|
324
365
|
|
@@ -340,8 +381,9 @@ module ActiveRecord
|
|
340
381
|
spawn.reorder!(*args)
|
341
382
|
end
|
342
383
|
|
384
|
+
# Same as #reorder but operates on relation in-place instead of copying.
|
343
385
|
def reorder!(*args) # :nodoc:
|
344
|
-
preprocess_order_args(args)
|
386
|
+
preprocess_order_args(args) unless args.all?(&:blank?)
|
345
387
|
|
346
388
|
self.reordering_value = true
|
347
389
|
self.order_values = args
|
@@ -349,8 +391,8 @@ module ActiveRecord
|
|
349
391
|
end
|
350
392
|
|
351
393
|
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
|
352
|
-
:limit, :offset, :joins, :
|
353
|
-
:readonly, :having])
|
394
|
+
:limit, :offset, :joins, :left_outer_joins, :annotate,
|
395
|
+
:includes, :from, :readonly, :having, :optimizer_hints])
|
354
396
|
|
355
397
|
# Removes an unwanted relation that is already defined on a chain of relations.
|
356
398
|
# This is useful when passing around chains of relations and would like to
|
@@ -365,15 +407,15 @@ module ActiveRecord
|
|
365
407
|
# User.order('email DESC').select('id').where(name: "John")
|
366
408
|
# .unscope(:order, :select, :where) == User.all
|
367
409
|
#
|
368
|
-
# One can additionally pass a hash as an argument to unscope specific
|
410
|
+
# One can additionally pass a hash as an argument to unscope specific +:where+ values.
|
369
411
|
# This is done by passing a hash with a single key-value pair. The key should be
|
370
|
-
#
|
412
|
+
# +:where+ and the value should be the where value to unscope. For example:
|
371
413
|
#
|
372
414
|
# User.where(name: "John", active: true).unscope(where: :name)
|
373
415
|
# == User.where(active: true)
|
374
416
|
#
|
375
|
-
# This method is similar to
|
376
|
-
#
|
417
|
+
# This method is similar to #except, but unlike
|
418
|
+
# #except, it persists across merges:
|
377
419
|
#
|
378
420
|
# User.order('email').merge(User.except(:order))
|
379
421
|
# == User.order('email')
|
@@ -383,7 +425,7 @@ module ActiveRecord
|
|
383
425
|
#
|
384
426
|
# This means it can be used in association definitions:
|
385
427
|
#
|
386
|
-
# has_many :comments, -> { unscope
|
428
|
+
# has_many :comments, -> { unscope(where: :trashed) }
|
387
429
|
#
|
388
430
|
def unscope(*args)
|
389
431
|
check_if_method_has_arguments!(:unscope, args)
|
@@ -397,16 +439,20 @@ module ActiveRecord
|
|
397
439
|
args.each do |scope|
|
398
440
|
case scope
|
399
441
|
when Symbol
|
400
|
-
|
442
|
+
scope = :left_outer_joins if scope == :left_joins
|
443
|
+
if !VALID_UNSCOPING_VALUES.include?(scope)
|
444
|
+
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
445
|
+
end
|
446
|
+
assert_mutability!
|
447
|
+
@values[scope] = DEFAULT_VALUES[scope]
|
401
448
|
when Hash
|
402
449
|
scope.each do |key, target_value|
|
403
450
|
if key != :where
|
404
451
|
raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
|
405
452
|
end
|
406
453
|
|
407
|
-
Array(target_value).
|
408
|
-
|
409
|
-
end
|
454
|
+
target_values = Array(target_value).map(&:to_s)
|
455
|
+
self.where_clause = where_clause.except(*target_values)
|
410
456
|
end
|
411
457
|
else
|
412
458
|
raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
|
@@ -416,15 +462,35 @@ module ActiveRecord
|
|
416
462
|
self
|
417
463
|
end
|
418
464
|
|
419
|
-
# Performs a joins on +args
|
465
|
+
# Performs a joins on +args+. The given symbol(s) should match the name of
|
466
|
+
# the association(s).
|
420
467
|
#
|
421
468
|
# User.joins(:posts)
|
422
|
-
#
|
469
|
+
# # SELECT "users".*
|
470
|
+
# # FROM "users"
|
471
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
472
|
+
#
|
473
|
+
# Multiple joins:
|
474
|
+
#
|
475
|
+
# User.joins(:posts, :account)
|
476
|
+
# # SELECT "users".*
|
477
|
+
# # FROM "users"
|
478
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
479
|
+
# # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id"
|
480
|
+
#
|
481
|
+
# Nested joins:
|
482
|
+
#
|
483
|
+
# User.joins(posts: [:comments])
|
484
|
+
# # SELECT "users".*
|
485
|
+
# # FROM "users"
|
486
|
+
# # INNER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
487
|
+
# # INNER JOIN "comments" "comments_posts"
|
488
|
+
# # ON "comments_posts"."post_id" = "posts"."id"
|
423
489
|
#
|
424
490
|
# You can use strings in order to customize your joins:
|
425
491
|
#
|
426
492
|
# User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id")
|
427
|
-
#
|
493
|
+
# # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id
|
428
494
|
def joins(*args)
|
429
495
|
check_if_method_has_arguments!(:joins, args)
|
430
496
|
spawn.joins!(*args)
|
@@ -433,16 +499,25 @@ module ActiveRecord
|
|
433
499
|
def joins!(*args) # :nodoc:
|
434
500
|
args.compact!
|
435
501
|
args.flatten!
|
436
|
-
self.joins_values
|
502
|
+
self.joins_values |= args
|
437
503
|
self
|
438
504
|
end
|
439
505
|
|
440
|
-
|
441
|
-
|
506
|
+
# Performs a left outer joins on +args+:
|
507
|
+
#
|
508
|
+
# User.left_outer_joins(:posts)
|
509
|
+
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
|
510
|
+
#
|
511
|
+
def left_outer_joins(*args)
|
512
|
+
check_if_method_has_arguments!(__callee__, args)
|
513
|
+
spawn.left_outer_joins!(*args)
|
442
514
|
end
|
515
|
+
alias :left_joins :left_outer_joins
|
443
516
|
|
444
|
-
def
|
445
|
-
|
517
|
+
def left_outer_joins!(*args) # :nodoc:
|
518
|
+
args.compact!
|
519
|
+
args.flatten!
|
520
|
+
self.left_outer_joins_values |= args
|
446
521
|
self
|
447
522
|
end
|
448
523
|
|
@@ -489,7 +564,7 @@ module ActiveRecord
|
|
489
564
|
# than the previous methods; you are responsible for ensuring that the values in the template
|
490
565
|
# are properly quoted. The values are passed to the connector for quoting, but the caller
|
491
566
|
# 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
|
567
|
+
# the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+.
|
493
568
|
#
|
494
569
|
# User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
|
495
570
|
# # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
|
@@ -566,7 +641,7 @@ module ActiveRecord
|
|
566
641
|
# If the condition is any blank-ish object, then #where is a no-op and returns
|
567
642
|
# the current relation.
|
568
643
|
def where(opts = :chain, *rest)
|
569
|
-
if
|
644
|
+
if :chain == opts
|
570
645
|
WhereChain.new(spawn)
|
571
646
|
elsif opts.blank?
|
572
647
|
self
|
@@ -576,27 +651,61 @@ module ActiveRecord
|
|
576
651
|
end
|
577
652
|
|
578
653
|
def where!(opts, *rest) # :nodoc:
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
end
|
583
|
-
|
584
|
-
self.where_values += build_where(opts, rest)
|
654
|
+
opts = sanitize_forbidden_attributes(opts)
|
655
|
+
references!(PredicateBuilder.references(opts)) if Hash === opts
|
656
|
+
self.where_clause += where_clause_factory.build(opts, rest)
|
585
657
|
self
|
586
658
|
end
|
587
659
|
|
588
660
|
# Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
|
589
661
|
#
|
590
|
-
# Post.where(trashed: true).where(trashed: false)
|
591
|
-
#
|
592
|
-
#
|
662
|
+
# Post.where(trashed: true).where(trashed: false)
|
663
|
+
# # WHERE `trashed` = 1 AND `trashed` = 0
|
664
|
+
#
|
665
|
+
# Post.where(trashed: true).rewhere(trashed: false)
|
666
|
+
# # WHERE `trashed` = 0
|
593
667
|
#
|
594
|
-
#
|
595
|
-
#
|
668
|
+
# Post.where(active: true).where(trashed: true).rewhere(trashed: false)
|
669
|
+
# # WHERE `active` = 1 AND `trashed` = 0
|
670
|
+
#
|
671
|
+
# This is short-hand for <tt>unscope(where: conditions.keys).where(conditions)</tt>.
|
672
|
+
# Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement.
|
596
673
|
def rewhere(conditions)
|
597
674
|
unscope(where: conditions.keys).where(conditions)
|
598
675
|
end
|
599
676
|
|
677
|
+
# Returns a new relation, which is the logical union of this relation and the one passed as an
|
678
|
+
# argument.
|
679
|
+
#
|
680
|
+
# The two relations must be structurally compatible: they must be scoping the same model, and
|
681
|
+
# they must differ only by #where (if no #group has been defined) or #having (if a #group is
|
682
|
+
# present). Neither relation may have a #limit, #offset, or #distinct set.
|
683
|
+
#
|
684
|
+
# Post.where("id = 1").or(Post.where("author_id = 3"))
|
685
|
+
# # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
|
686
|
+
#
|
687
|
+
def or(other)
|
688
|
+
unless other.is_a? Relation
|
689
|
+
raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
|
690
|
+
end
|
691
|
+
|
692
|
+
spawn.or!(other)
|
693
|
+
end
|
694
|
+
|
695
|
+
def or!(other) # :nodoc:
|
696
|
+
incompatible_values = structurally_incompatible_values_for_or(other)
|
697
|
+
|
698
|
+
unless incompatible_values.empty?
|
699
|
+
raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
|
700
|
+
end
|
701
|
+
|
702
|
+
self.where_clause = self.where_clause.or(other.where_clause)
|
703
|
+
self.having_clause = having_clause.or(other.having_clause)
|
704
|
+
self.references_values += other.references_values
|
705
|
+
|
706
|
+
self
|
707
|
+
end
|
708
|
+
|
600
709
|
# Allows to specify a HAVING clause. Note that you can't use HAVING
|
601
710
|
# without also specifying a GROUP clause.
|
602
711
|
#
|
@@ -606,9 +715,10 @@ module ActiveRecord
|
|
606
715
|
end
|
607
716
|
|
608
717
|
def having!(opts, *rest) # :nodoc:
|
718
|
+
opts = sanitize_forbidden_attributes(opts)
|
609
719
|
references!(PredicateBuilder.references(opts)) if Hash === opts
|
610
720
|
|
611
|
-
self.
|
721
|
+
self.having_clause += having_clause_factory.build(opts, rest)
|
612
722
|
self
|
613
723
|
end
|
614
724
|
|
@@ -643,7 +753,7 @@ module ActiveRecord
|
|
643
753
|
end
|
644
754
|
|
645
755
|
# Specifies locking settings (default to +true+). For more information
|
646
|
-
# on locking, please see
|
756
|
+
# on locking, please see ActiveRecord::Locking.
|
647
757
|
def lock(locks = true)
|
648
758
|
spawn.lock!(locks)
|
649
759
|
end
|
@@ -674,7 +784,7 @@ module ActiveRecord
|
|
674
784
|
# For example:
|
675
785
|
#
|
676
786
|
# @posts = current_user.visible_posts.where(name: params[:name])
|
677
|
-
# #
|
787
|
+
# # the visible_posts method is expected to return a chainable Relation
|
678
788
|
#
|
679
789
|
# def visible_posts
|
680
790
|
# case role
|
@@ -688,7 +798,7 @@ module ActiveRecord
|
|
688
798
|
# end
|
689
799
|
#
|
690
800
|
def none
|
691
|
-
|
801
|
+
spawn.none!
|
692
802
|
end
|
693
803
|
|
694
804
|
def none! # :nodoc:
|
@@ -700,7 +810,7 @@ module ActiveRecord
|
|
700
810
|
#
|
701
811
|
# users = User.readonly
|
702
812
|
# users.first.save
|
703
|
-
# => ActiveRecord::ReadOnlyRecord:
|
813
|
+
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
704
814
|
def readonly(value = true)
|
705
815
|
spawn.readonly!(value)
|
706
816
|
end
|
@@ -719,7 +829,7 @@ module ActiveRecord
|
|
719
829
|
# users = users.create_with(name: 'DHH')
|
720
830
|
# users.new.name # => 'DHH'
|
721
831
|
#
|
722
|
-
# You can pass +nil+ to
|
832
|
+
# You can pass +nil+ to #create_with to reset attributes:
|
723
833
|
#
|
724
834
|
# users = users.create_with(nil)
|
725
835
|
# users.new.name # => 'Oscar'
|
@@ -732,7 +842,7 @@ module ActiveRecord
|
|
732
842
|
value = sanitize_forbidden_attributes(value)
|
733
843
|
self.create_with_value = create_with_value.merge(value)
|
734
844
|
else
|
735
|
-
self.create_with_value =
|
845
|
+
self.create_with_value = FROZEN_EMPTY_HASH
|
736
846
|
end
|
737
847
|
|
738
848
|
self
|
@@ -741,49 +851,44 @@ module ActiveRecord
|
|
741
851
|
# Specifies table from which the records will be fetched. For example:
|
742
852
|
#
|
743
853
|
# Topic.select('title').from('posts')
|
744
|
-
# #
|
854
|
+
# # SELECT title FROM posts
|
745
855
|
#
|
746
856
|
# Can accept other relation objects. For example:
|
747
857
|
#
|
748
858
|
# Topic.select('title').from(Topic.approved)
|
749
|
-
# #
|
859
|
+
# # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
|
750
860
|
#
|
751
861
|
# Topic.select('a.title').from(Topic.approved, :a)
|
752
|
-
# #
|
862
|
+
# # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
|
753
863
|
#
|
754
864
|
def from(value, subquery_name = nil)
|
755
865
|
spawn.from!(value, subquery_name)
|
756
866
|
end
|
757
867
|
|
758
868
|
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
|
869
|
+
self.from_clause = Relation::FromClause.new(value, subquery_name)
|
763
870
|
self
|
764
871
|
end
|
765
872
|
|
766
873
|
# Specifies whether the records should be unique or not. For example:
|
767
874
|
#
|
768
875
|
# User.select(:name)
|
769
|
-
# #
|
876
|
+
# # Might return two records with the same name
|
770
877
|
#
|
771
878
|
# User.select(:name).distinct
|
772
|
-
# #
|
879
|
+
# # Returns 1 record per distinct name
|
773
880
|
#
|
774
881
|
# User.select(:name).distinct.distinct(false)
|
775
|
-
# #
|
882
|
+
# # You can also remove the uniqueness
|
776
883
|
def distinct(value = true)
|
777
884
|
spawn.distinct!(value)
|
778
885
|
end
|
779
|
-
alias uniq distinct
|
780
886
|
|
781
887
|
# Like #distinct, but modifies relation in place.
|
782
888
|
def distinct!(value = true) # :nodoc:
|
783
889
|
self.distinct_value = value
|
784
890
|
self
|
785
891
|
end
|
786
|
-
alias uniq! distinct!
|
787
892
|
|
788
893
|
# Used to extend a scope with additional methods, either through
|
789
894
|
# a module or through a block provided.
|
@@ -839,6 +944,29 @@ module ActiveRecord
|
|
839
944
|
self
|
840
945
|
end
|
841
946
|
|
947
|
+
# Specify optimizer hints to be used in the SELECT statement.
|
948
|
+
#
|
949
|
+
# Example (for MySQL):
|
950
|
+
#
|
951
|
+
# Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
|
952
|
+
# # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
|
953
|
+
#
|
954
|
+
# Example (for PostgreSQL with pg_hint_plan):
|
955
|
+
#
|
956
|
+
# Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
|
957
|
+
# # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
|
958
|
+
def optimizer_hints(*args)
|
959
|
+
check_if_method_has_arguments!(:optimizer_hints, args)
|
960
|
+
spawn.optimizer_hints!(*args)
|
961
|
+
end
|
962
|
+
|
963
|
+
def optimizer_hints!(*args) # :nodoc:
|
964
|
+
args.flatten!
|
965
|
+
|
966
|
+
self.optimizer_hints_values |= args
|
967
|
+
self
|
968
|
+
end
|
969
|
+
|
842
970
|
# Reverse the existing order clause on the relation.
|
843
971
|
#
|
844
972
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -853,326 +981,404 @@ module ActiveRecord
|
|
853
981
|
self
|
854
982
|
end
|
855
983
|
|
856
|
-
|
857
|
-
|
858
|
-
|
984
|
+
def skip_query_cache!(value = true) # :nodoc:
|
985
|
+
self.skip_query_cache_value = value
|
986
|
+
self
|
859
987
|
end
|
860
988
|
|
861
|
-
|
989
|
+
def skip_preloading! # :nodoc:
|
990
|
+
self.skip_preloading_value = true
|
991
|
+
self
|
992
|
+
end
|
862
993
|
|
863
|
-
|
864
|
-
|
994
|
+
# Adds an SQL comment to queries generated from this relation. For example:
|
995
|
+
#
|
996
|
+
# User.annotate("selecting user names").select(:name)
|
997
|
+
# # SELECT "users"."name" FROM "users" /* selecting user names */
|
998
|
+
#
|
999
|
+
# User.annotate("selecting", "user", "names").select(:name)
|
1000
|
+
# # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
|
1001
|
+
#
|
1002
|
+
# The SQL block comment delimiters, "/*" and "*/", will be added automatically.
|
1003
|
+
def annotate(*args)
|
1004
|
+
check_if_method_has_arguments!(:annotate, args)
|
1005
|
+
spawn.annotate!(*args)
|
1006
|
+
end
|
865
1007
|
|
866
|
-
|
1008
|
+
# Like #annotate, but modifies relation in place.
|
1009
|
+
def annotate!(*args) # :nodoc:
|
1010
|
+
self.annotate_values += args
|
1011
|
+
self
|
1012
|
+
end
|
867
1013
|
|
868
|
-
|
1014
|
+
# Returns the Arel object associated with the relation.
|
1015
|
+
def arel(aliases = nil) # :nodoc:
|
1016
|
+
@arel ||= build_arel(aliases)
|
1017
|
+
end
|
869
1018
|
|
870
|
-
|
1019
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1020
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1021
|
+
klass, table, associations, join_type
|
1022
|
+
)
|
1023
|
+
end
|
871
1024
|
|
872
|
-
|
873
|
-
|
874
|
-
|
1025
|
+
protected
|
1026
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1027
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
875
1028
|
|
876
|
-
|
1029
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1030
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1031
|
+
end
|
1032
|
+
end
|
877
1033
|
|
878
|
-
|
1034
|
+
private
|
1035
|
+
def assert_mutability!
|
1036
|
+
raise ImmutableRelation if @loaded
|
1037
|
+
raise ImmutableRelation if defined?(@arel) && @arel
|
1038
|
+
end
|
879
1039
|
|
880
|
-
|
881
|
-
|
882
|
-
arel.lock(lock_value) if lock_value
|
1040
|
+
def build_arel(aliases)
|
1041
|
+
arel = Arel::SelectManager.new(table)
|
883
1042
|
|
884
|
-
|
885
|
-
|
1043
|
+
if !joins_values.empty?
|
1044
|
+
build_joins(arel, joins_values.flatten, aliases)
|
1045
|
+
elsif !left_outer_joins_values.empty?
|
1046
|
+
build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
|
1047
|
+
end
|
886
1048
|
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
1049
|
+
arel.where(where_clause.ast) unless where_clause.empty?
|
1050
|
+
arel.having(having_clause.ast) unless having_clause.empty?
|
1051
|
+
if limit_value
|
1052
|
+
limit_attribute = ActiveModel::Attribute.with_cast_value(
|
1053
|
+
"LIMIT",
|
1054
|
+
connection.sanitize_limit(limit_value),
|
1055
|
+
Type.default_value,
|
1056
|
+
)
|
1057
|
+
arel.take(Arel::Nodes::BindParam.new(limit_attribute))
|
1058
|
+
end
|
1059
|
+
if offset_value
|
1060
|
+
offset_attribute = ActiveModel::Attribute.with_cast_value(
|
1061
|
+
"OFFSET",
|
1062
|
+
offset_value.to_i,
|
1063
|
+
Type.default_value,
|
1064
|
+
)
|
1065
|
+
arel.skip(Arel::Nodes::BindParam.new(offset_attribute))
|
1066
|
+
end
|
1067
|
+
arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty?
|
891
1068
|
|
892
|
-
|
893
|
-
unscope_code = "#{scope}_value#{'s' unless single_val_method}="
|
1069
|
+
build_order(arel)
|
894
1070
|
|
895
|
-
|
896
|
-
when :order
|
897
|
-
result = []
|
898
|
-
when :where
|
899
|
-
self.bind_values = []
|
900
|
-
else
|
901
|
-
result = [] unless single_val_method
|
902
|
-
end
|
1071
|
+
build_select(arel)
|
903
1072
|
|
904
|
-
|
905
|
-
|
1073
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
1074
|
+
arel.distinct(distinct_value)
|
1075
|
+
arel.from(build_from) unless from_clause.empty?
|
1076
|
+
arel.lock(lock_value) if lock_value
|
1077
|
+
arel.comment(*annotate_values) unless annotate_values.empty?
|
906
1078
|
|
907
|
-
|
908
|
-
|
1079
|
+
arel
|
1080
|
+
end
|
909
1081
|
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
1082
|
+
def build_from
|
1083
|
+
opts = from_clause.value
|
1084
|
+
name = from_clause.name
|
1085
|
+
case opts
|
1086
|
+
when Relation
|
1087
|
+
if opts.eager_loading?
|
1088
|
+
opts = opts.send(:apply_join_dependency)
|
1089
|
+
end
|
1090
|
+
name ||= "subquery"
|
1091
|
+
opts.arel.as(name.to_s)
|
1092
|
+
else
|
1093
|
+
opts
|
915
1094
|
end
|
916
1095
|
end
|
917
1096
|
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
1097
|
+
def select_association_list(associations)
|
1098
|
+
result = []
|
1099
|
+
associations.each do |association|
|
1100
|
+
case association
|
1101
|
+
when Hash, Symbol, Array
|
1102
|
+
result << association
|
1103
|
+
else
|
1104
|
+
yield if block_given?
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
result
|
1108
|
+
end
|
925
1109
|
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
|
930
|
-
when String
|
931
|
-
join = Arel.sql(join)
|
1110
|
+
def valid_association_list(associations)
|
1111
|
+
select_association_list(associations) do
|
1112
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
932
1113
|
end
|
933
|
-
table.create_string_join(join)
|
934
1114
|
end
|
935
|
-
end
|
936
1115
|
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
Arel::Nodes::Grouping.new(where)
|
1116
|
+
def build_left_outer_joins(manager, outer_joins, aliases)
|
1117
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1118
|
+
buckets[:association_join] = valid_association_list(outer_joins)
|
1119
|
+
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
942
1120
|
end
|
943
1121
|
|
944
|
-
|
945
|
-
|
1122
|
+
def build_joins(manager, joins, aliases)
|
1123
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
946
1124
|
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
when Hash
|
952
|
-
opts = PredicateBuilder.resolve_column_aliases(klass, opts)
|
1125
|
+
unless left_outer_joins_values.empty?
|
1126
|
+
left_joins = valid_association_list(left_outer_joins_values.flatten)
|
1127
|
+
buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1128
|
+
end
|
953
1129
|
|
954
|
-
|
955
|
-
|
1130
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1131
|
+
buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
|
1132
|
+
end
|
956
1133
|
|
957
|
-
|
958
|
-
|
1134
|
+
joins.map! do |join|
|
1135
|
+
if join.is_a?(String)
|
1136
|
+
table.create_string_join(Arel.sql(join.strip)) unless join.blank?
|
1137
|
+
else
|
1138
|
+
join
|
1139
|
+
end
|
1140
|
+
end.delete_if(&:blank?).uniq!
|
959
1141
|
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
1142
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1143
|
+
join_node = joins.shift
|
1144
|
+
if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
|
1145
|
+
buckets[:join_node] << join_node
|
1146
|
+
else
|
1147
|
+
buckets[:leading_join] << join_node
|
1148
|
+
end
|
1149
|
+
end
|
965
1150
|
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
1151
|
+
joins.each do |join|
|
1152
|
+
case join
|
1153
|
+
when Hash, Symbol, Array
|
1154
|
+
buckets[:association_join] << join
|
1155
|
+
when ActiveRecord::Associations::JoinDependency
|
1156
|
+
buckets[:stashed_join] << join
|
1157
|
+
when Arel::Nodes::Join
|
1158
|
+
buckets[:join_node] << join
|
1159
|
+
else
|
1160
|
+
raise "unknown class: %s" % join.class.name
|
1161
|
+
end
|
1162
|
+
end
|
972
1163
|
|
973
|
-
|
974
|
-
value.is_a?(Hash) && association_for_table(column)
|
1164
|
+
build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
|
975
1165
|
end
|
976
1166
|
|
977
|
-
|
978
|
-
|
1167
|
+
def build_join_query(manager, buckets, join_type, aliases)
|
1168
|
+
association_joins = buckets[:association_join]
|
1169
|
+
stashed_joins = buckets[:stashed_join]
|
1170
|
+
leading_joins = buckets[:leading_join]
|
1171
|
+
join_nodes = buckets[:join_node]
|
979
1172
|
|
980
|
-
|
1173
|
+
join_sources = manager.join_sources
|
1174
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
981
1175
|
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
1176
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1177
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1178
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1179
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
1180
|
+
end
|
986
1181
|
|
987
|
-
|
988
|
-
association_relation = association_for_table(column).klass.send(:relation)
|
989
|
-
association_new_opts, association_bind = association_relation.send(:create_binds, value)
|
990
|
-
new_opts[column] = association_new_opts
|
991
|
-
binds += association_bind
|
1182
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
992
1183
|
end
|
993
1184
|
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
end
|
1185
|
+
def build_select(arel)
|
1186
|
+
if select_values.any?
|
1187
|
+
arel.project(*arel_columns(select_values.uniq))
|
1188
|
+
elsif klass.ignored_columns.any?
|
1189
|
+
arel.project(*klass.column_names.map { |field| arel_attribute(field) })
|
1190
|
+
else
|
1191
|
+
arel.project(table[Arel.star])
|
1192
|
+
end
|
1193
|
+
end
|
1004
1194
|
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1195
|
+
def arel_columns(columns)
|
1196
|
+
columns.flat_map do |field|
|
1197
|
+
case field
|
1198
|
+
when Symbol
|
1199
|
+
arel_column(field.to_s) do |attr_name|
|
1200
|
+
connection.quote_table_name(attr_name)
|
1201
|
+
end
|
1202
|
+
when String
|
1203
|
+
arel_column(field, &:itself)
|
1204
|
+
when Proc
|
1205
|
+
field.call
|
1206
|
+
else
|
1207
|
+
field
|
1208
|
+
end
|
1209
|
+
end
|
1013
1210
|
end
|
1014
|
-
end
|
1015
1211
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
:association_join
|
1023
|
-
when ActiveRecord::Associations::JoinDependency
|
1024
|
-
:stashed_join
|
1025
|
-
when Arel::Nodes::Join
|
1026
|
-
:join_node
|
1212
|
+
def arel_column(field)
|
1213
|
+
field = klass.attribute_aliases[field] || field
|
1214
|
+
from = from_clause.name || from_clause.value
|
1215
|
+
|
1216
|
+
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1217
|
+
arel_attribute(field)
|
1027
1218
|
else
|
1028
|
-
|
1219
|
+
yield field
|
1029
1220
|
end
|
1030
1221
|
end
|
1031
1222
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1223
|
+
def table_name_matches?(from)
|
1224
|
+
table_name = Regexp.escape(table.name)
|
1225
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1226
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1227
|
+
end
|
1036
1228
|
|
1037
|
-
|
1229
|
+
def reverse_sql_order(order_query)
|
1230
|
+
if order_query.empty?
|
1231
|
+
return [arel_attribute(primary_key).desc] if primary_key
|
1232
|
+
raise IrreversibleOrderError,
|
1233
|
+
"Relation has no current order and table has no primary key to be used as default order"
|
1234
|
+
end
|
1038
1235
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1236
|
+
order_query.flat_map do |o|
|
1237
|
+
case o
|
1238
|
+
when Arel::Attribute
|
1239
|
+
o.desc
|
1240
|
+
when Arel::Nodes::Ordering
|
1241
|
+
o.reverse
|
1242
|
+
when String
|
1243
|
+
if does_not_support_reverse?(o)
|
1244
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1245
|
+
end
|
1246
|
+
o.split(",").map! do |s|
|
1247
|
+
s.strip!
|
1248
|
+
s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
|
1249
|
+
end
|
1250
|
+
else
|
1251
|
+
o
|
1252
|
+
end
|
1253
|
+
end
|
1254
|
+
end
|
1044
1255
|
|
1045
|
-
|
1256
|
+
def does_not_support_reverse?(order)
|
1257
|
+
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
1258
|
+
# override methods like #count.
|
1259
|
+
order = String.new(order) unless order.instance_of?(String)
|
1046
1260
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1261
|
+
# Uses SQL function with multiple arguments.
|
1262
|
+
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1263
|
+
# Uses "nulls first" like construction.
|
1264
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1050
1265
|
end
|
1051
1266
|
|
1052
|
-
|
1267
|
+
def build_order(arel)
|
1268
|
+
orders = order_values.uniq
|
1269
|
+
orders.reject!(&:blank?)
|
1053
1270
|
|
1054
|
-
|
1055
|
-
|
1271
|
+
arel.order(*orders) unless orders.empty?
|
1272
|
+
end
|
1056
1273
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1274
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1275
|
+
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
|
1276
|
+
|
1277
|
+
def validate_order_args(args)
|
1278
|
+
args.each do |arg|
|
1279
|
+
next unless arg.is_a?(Hash)
|
1280
|
+
arg.each do |_key, value|
|
1281
|
+
unless VALID_DIRECTIONS.include?(value)
|
1282
|
+
raise ArgumentError,
|
1283
|
+
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
end
|
1062
1287
|
end
|
1063
|
-
end
|
1064
1288
|
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
elsif Symbol === field
|
1070
|
-
connection.quote_table_name(field.to_s)
|
1071
|
-
else
|
1072
|
-
field
|
1289
|
+
def preprocess_order_args(order_args)
|
1290
|
+
order_args.reject!(&:blank?)
|
1291
|
+
order_args.map! do |arg|
|
1292
|
+
klass.sanitize_sql_for_order(arg)
|
1073
1293
|
end
|
1294
|
+
order_args.flatten!
|
1295
|
+
|
1296
|
+
@klass.disallow_raw_sql!(
|
1297
|
+
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1298
|
+
permit: connection.column_name_with_order_matcher
|
1299
|
+
)
|
1300
|
+
|
1301
|
+
validate_order_args(order_args)
|
1302
|
+
|
1303
|
+
references = order_args.grep(String)
|
1304
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1305
|
+
references!(references) if references.any?
|
1306
|
+
|
1307
|
+
# if a symbol is given we prepend the quoted table name
|
1308
|
+
order_args.map! do |arg|
|
1309
|
+
case arg
|
1310
|
+
when Symbol
|
1311
|
+
order_column(arg.to_s).asc
|
1312
|
+
when Hash
|
1313
|
+
arg.map { |field, dir|
|
1314
|
+
case field
|
1315
|
+
when Arel::Nodes::SqlLiteral
|
1316
|
+
field.send(dir.downcase)
|
1317
|
+
else
|
1318
|
+
order_column(field.to_s).send(dir.downcase)
|
1319
|
+
end
|
1320
|
+
}
|
1321
|
+
else
|
1322
|
+
arg
|
1323
|
+
end
|
1324
|
+
end.flatten!
|
1074
1325
|
end
|
1075
|
-
end
|
1076
1326
|
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
o.reverse
|
1084
|
-
when String
|
1085
|
-
o.to_s.split(',').map! do |s|
|
1086
|
-
s.strip!
|
1087
|
-
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
1327
|
+
def order_column(field)
|
1328
|
+
arel_column(field) do |attr_name|
|
1329
|
+
if attr_name == "count" && !group_values.empty?
|
1330
|
+
arel_attribute(attr_name)
|
1331
|
+
else
|
1332
|
+
Arel.sql(connection.quote_table_name(attr_name))
|
1088
1333
|
end
|
1089
|
-
else
|
1090
|
-
o
|
1091
1334
|
end
|
1092
1335
|
end
|
1093
|
-
end
|
1094
1336
|
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
raise ArgumentError, "
|
1114
|
-
"directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
|
1337
|
+
# Checks to make sure that the arguments are not blank. Note that if some
|
1338
|
+
# blank-like object were initially passed into the query method, then this
|
1339
|
+
# method will not raise an error.
|
1340
|
+
#
|
1341
|
+
# Example:
|
1342
|
+
#
|
1343
|
+
# Post.references() # raises an error
|
1344
|
+
# Post.references([]) # does not raise an error
|
1345
|
+
#
|
1346
|
+
# This particular method should be called with a method_name and the args
|
1347
|
+
# passed into that method as an input. For example:
|
1348
|
+
#
|
1349
|
+
# def references(*args)
|
1350
|
+
# check_if_method_has_arguments!("references", args)
|
1351
|
+
# ...
|
1352
|
+
# end
|
1353
|
+
def check_if_method_has_arguments!(method_name, args)
|
1354
|
+
if args.blank?
|
1355
|
+
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1115
1356
|
end
|
1116
1357
|
end
|
1117
|
-
end
|
1118
|
-
|
1119
|
-
def preprocess_order_args(order_args)
|
1120
|
-
order_args.flatten!
|
1121
|
-
validate_order_args(order_args)
|
1122
1358
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
case arg
|
1130
|
-
when Symbol
|
1131
|
-
arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
|
1132
|
-
table[arg].asc
|
1133
|
-
when Hash
|
1134
|
-
arg.map { |field, dir|
|
1135
|
-
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
|
1136
|
-
table[field].send(dir.downcase)
|
1137
|
-
}
|
1138
|
-
else
|
1139
|
-
arg
|
1359
|
+
STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references]
|
1360
|
+
def structurally_incompatible_values_for_or(other)
|
1361
|
+
values = other.values
|
1362
|
+
STRUCTURAL_OR_METHODS.reject do |method|
|
1363
|
+
default = DEFAULT_VALUES[method]
|
1364
|
+
@values.fetch(method, default) == values.fetch(method, default)
|
1140
1365
|
end
|
1141
|
-
end
|
1142
|
-
end
|
1366
|
+
end
|
1143
1367
|
|
1144
|
-
|
1145
|
-
|
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."
|
1368
|
+
def where_clause_factory
|
1369
|
+
@where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
|
1163
1370
|
end
|
1164
|
-
|
1371
|
+
alias having_clause_factory where_clause_factory
|
1165
1372
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1373
|
+
DEFAULT_VALUES = {
|
1374
|
+
create_with: FROZEN_EMPTY_HASH,
|
1375
|
+
where: Relation::WhereClause.empty,
|
1376
|
+
having: Relation::WhereClause.empty,
|
1377
|
+
from: Relation::FromClause.empty
|
1378
|
+
}
|
1379
|
+
|
1380
|
+
Relation::MULTI_VALUE_METHODS.each do |value|
|
1381
|
+
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1175
1382
|
end
|
1176
|
-
end
|
1177
1383
|
end
|
1178
1384
|
end
|