activerecord 4.2.0 → 6.0.5.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +852 -801
- 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 +267 -249
- data/lib/active_record/association_relation.rb +26 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -36
- data/lib/active_record/associations/association.rb +137 -55
- data/lib/active_record/associations/association_scope.rb +110 -132
- data/lib/active_record/associations/belongs_to_association.rb +67 -54
- 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 -29
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +58 -70
- 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 +150 -275
- data/lib/active_record/associations/collection_proxy.rb +253 -152
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +35 -84
- data/lib/active_record/associations/has_many_through_association.rb +62 -80
- data/lib/active_record/associations/has_one_association.rb +62 -49
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -78
- 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 +159 -162
- data/lib/active_record/associations/preloader/association.rb +102 -113
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/preloader.rb +96 -95
- 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 +1737 -1596
- 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 +14 -5
- data/lib/active_record/attribute_methods/dirty.rb +174 -134
- 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 +61 -37
- data/lib/active_record/attribute_methods/write.rb +33 -56
- data/lib/active_record/attribute_methods.rb +124 -143
- data/lib/active_record/attributes.rb +213 -74
- data/lib/active_record/autosave_association.rb +125 -54
- data/lib/active_record/base.rb +60 -49
- 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 +36 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +810 -291
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +253 -108
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +83 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +171 -53
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -47
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +383 -239
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +736 -235
- data/lib/active_record/connection_adapters/abstract/transaction.rb +190 -87
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -192
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +536 -600
- 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 +268 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +71 -115
- 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 +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 +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/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 +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 +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 +465 -291
- 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 +565 -363
- 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 -364
- 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 +277 -233
- data/lib/active_record/counter_cache.rb +71 -50
- 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 +172 -89
- data/lib/active_record/errors.rb +189 -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 +11 -6
- 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 +225 -497
- 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 +48 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +99 -98
- 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 +636 -290
- data/lib/active_record/model_schema.rb +344 -112
- data/lib/active_record/nested_attributes.rb +265 -215
- data/lib/active_record/no_touching.rb +15 -2
- data/lib/active_record/null_relation.rb +24 -38
- data/lib/active_record/persistence.rb +559 -125
- data/lib/active_record/query_cache.rb +19 -23
- data/lib/active_record/querying.rb +44 -30
- data/lib/active_record/railtie.rb +166 -47
- 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 +341 -202
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +461 -302
- 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 +270 -249
- data/lib/active_record/relation/delegation.rb +76 -84
- data/lib/active_record/relation/finder_methods.rb +287 -255
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +86 -68
- data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -25
- 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 +112 -92
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +612 -392
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -17
- 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 +533 -340
- 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 -20
- data/lib/active_record/scoping/default.rb +98 -82
- data/lib/active_record/scoping/named.rb +91 -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 +309 -99
- data/lib/active_record/tasks/mysql_database_tasks.rb +58 -89
- data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -31
- 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 +243 -0
- data/lib/active_record/timestamp.rb +86 -41
- data/lib/active_record/touch_later.rb +65 -0
- data/lib/active_record/transactions.rb +222 -146
- 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 +29 -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 +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 +43 -46
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +44 -21
- 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 -8
- 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.rb +7 -5
- metadata +174 -63
- 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_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- 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_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) : 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)
|
@@ -298,27 +338,28 @@ module ActiveRecord
|
|
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,46 +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.
|
869
|
+
self.from_clause = Relation::FromClause.new(value, subquery_name)
|
760
870
|
self
|
761
871
|
end
|
762
872
|
|
763
873
|
# Specifies whether the records should be unique or not. For example:
|
764
874
|
#
|
765
875
|
# User.select(:name)
|
766
|
-
# #
|
876
|
+
# # Might return two records with the same name
|
767
877
|
#
|
768
878
|
# User.select(:name).distinct
|
769
|
-
# #
|
879
|
+
# # Returns 1 record per distinct name
|
770
880
|
#
|
771
881
|
# User.select(:name).distinct.distinct(false)
|
772
|
-
# #
|
882
|
+
# # You can also remove the uniqueness
|
773
883
|
def distinct(value = true)
|
774
884
|
spawn.distinct!(value)
|
775
885
|
end
|
776
|
-
alias uniq distinct
|
777
886
|
|
778
887
|
# Like #distinct, but modifies relation in place.
|
779
888
|
def distinct!(value = true) # :nodoc:
|
780
889
|
self.distinct_value = value
|
781
890
|
self
|
782
891
|
end
|
783
|
-
alias uniq! distinct!
|
784
892
|
|
785
893
|
# Used to extend a scope with additional methods, either through
|
786
894
|
# a module or through a block provided.
|
@@ -836,6 +944,29 @@ module ActiveRecord
|
|
836
944
|
self
|
837
945
|
end
|
838
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
|
+
|
839
970
|
# Reverse the existing order clause on the relation.
|
840
971
|
#
|
841
972
|
# User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
|
@@ -850,327 +981,416 @@ module ActiveRecord
|
|
850
981
|
self
|
851
982
|
end
|
852
983
|
|
853
|
-
|
854
|
-
|
855
|
-
|
984
|
+
def skip_query_cache!(value = true) # :nodoc:
|
985
|
+
self.skip_query_cache_value = value
|
986
|
+
self
|
856
987
|
end
|
857
988
|
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
arel = Arel::SelectManager.new(table.engine, table)
|
862
|
-
|
863
|
-
build_joins(arel, joins_values.flatten) unless joins_values.empty?
|
864
|
-
|
865
|
-
collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds
|
866
|
-
|
867
|
-
arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty?
|
868
|
-
|
869
|
-
arel.take(connection.sanitize_limit(limit_value)) if limit_value
|
870
|
-
arel.skip(offset_value.to_i) if offset_value
|
871
|
-
|
872
|
-
arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty?
|
873
|
-
|
874
|
-
build_order(arel)
|
875
|
-
|
876
|
-
build_select(arel, select_values.uniq)
|
877
|
-
|
878
|
-
arel.distinct(distinct_value)
|
879
|
-
arel.from(build_from) if from_value
|
880
|
-
arel.lock(lock_value) if lock_value
|
881
|
-
|
882
|
-
arel
|
989
|
+
def skip_preloading! # :nodoc:
|
990
|
+
self.skip_preloading_value = true
|
991
|
+
self
|
883
992
|
end
|
884
993
|
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
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
|
889
1007
|
|
890
|
-
|
891
|
-
|
1008
|
+
# Like #annotate, but modifies relation in place.
|
1009
|
+
def annotate!(*args) # :nodoc:
|
1010
|
+
self.annotate_values += args
|
1011
|
+
self
|
1012
|
+
end
|
892
1013
|
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
self.bind_values = []
|
898
|
-
else
|
899
|
-
result = [] unless single_val_method
|
900
|
-
end
|
1014
|
+
# Returns the Arel object associated with the relation.
|
1015
|
+
def arel(aliases = nil) # :nodoc:
|
1016
|
+
@arel ||= build_arel(aliases)
|
1017
|
+
end
|
901
1018
|
|
902
|
-
|
1019
|
+
def construct_join_dependency(associations, join_type) # :nodoc:
|
1020
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1021
|
+
klass, table, associations, join_type
|
1022
|
+
)
|
903
1023
|
end
|
904
1024
|
|
905
|
-
|
906
|
-
|
1025
|
+
protected
|
1026
|
+
def build_subquery(subquery_alias, select_value) # :nodoc:
|
1027
|
+
subquery = except(:optimizer_hints).arel.as(subquery_alias)
|
907
1028
|
|
908
|
-
|
909
|
-
|
910
|
-
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual
|
911
|
-
subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
|
912
|
-
subrelation.name == target_value
|
1029
|
+
Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
|
1030
|
+
arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
|
913
1031
|
end
|
914
1032
|
end
|
915
1033
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
1034
|
+
private
|
1035
|
+
def assert_mutability!
|
1036
|
+
raise ImmutableRelation if @loaded
|
1037
|
+
raise ImmutableRelation if defined?(@arel) && @arel
|
1038
|
+
end
|
921
1039
|
|
922
|
-
|
1040
|
+
def build_arel(aliases)
|
1041
|
+
arel = Arel::SelectManager.new(table)
|
923
1042
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
when String
|
929
|
-
join = Arel.sql(join)
|
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)
|
930
1047
|
end
|
931
|
-
table.create_string_join(join)
|
932
|
-
end
|
933
|
-
end
|
934
|
-
|
935
|
-
def collapse_wheres(arel, wheres)
|
936
|
-
predicates = wheres.map do |where|
|
937
|
-
next where if ::Arel::Nodes::Equality === where
|
938
|
-
where = Arel.sql(where) if String === where
|
939
|
-
Arel::Nodes::Grouping.new(where)
|
940
|
-
end
|
941
1048
|
|
942
|
-
|
943
|
-
|
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?
|
944
1068
|
|
945
|
-
|
946
|
-
case opts
|
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)
|
1069
|
+
build_order(arel)
|
951
1070
|
|
952
|
-
|
953
|
-
self.bind_values += bind_values
|
1071
|
+
build_select(arel)
|
954
1072
|
|
955
|
-
|
956
|
-
|
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?
|
957
1078
|
|
958
|
-
|
959
|
-
else
|
960
|
-
[opts]
|
1079
|
+
arel
|
961
1080
|
end
|
962
|
-
end
|
963
1081
|
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
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)
|
969
1092
|
else
|
970
|
-
|
1093
|
+
opts
|
971
1094
|
end
|
972
1095
|
end
|
973
1096
|
|
974
|
-
|
975
|
-
|
1097
|
+
def select_association_list(associations, stashed_joins = nil)
|
1098
|
+
result = []
|
1099
|
+
associations.each do |association|
|
1100
|
+
case association
|
1101
|
+
when Hash, Symbol, Array
|
1102
|
+
result << association
|
1103
|
+
when ActiveRecord::Associations::JoinDependency
|
1104
|
+
stashed_joins&.<< association
|
1105
|
+
else
|
1106
|
+
yield if block_given?
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
result
|
976
1110
|
end
|
977
1111
|
|
978
|
-
|
979
|
-
|
1112
|
+
def valid_association_list(associations, stashed_joins)
|
1113
|
+
select_association_list(associations, stashed_joins) do
|
1114
|
+
raise ArgumentError, "only Hash, Symbol and Array are allowed"
|
1115
|
+
end
|
1116
|
+
end
|
980
1117
|
|
981
|
-
|
982
|
-
|
983
|
-
|
1118
|
+
def build_left_outer_joins(manager, outer_joins, aliases)
|
1119
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
1120
|
+
buckets[:association_join] = valid_association_list(outer_joins, buckets[:stashed_join])
|
1121
|
+
build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
|
984
1122
|
end
|
985
1123
|
|
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
|
1124
|
+
class ::Arel::Nodes::LeadingJoin < Arel::Nodes::InnerJoin # :nodoc:
|
991
1125
|
end
|
992
1126
|
|
993
|
-
|
1127
|
+
def build_joins(manager, joins, aliases)
|
1128
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
994
1129
|
|
995
|
-
|
996
|
-
|
1130
|
+
unless left_outer_joins_values.empty?
|
1131
|
+
stashed_left_joins = []
|
1132
|
+
left_joins = valid_association_list(left_outer_joins_values.flatten, stashed_left_joins)
|
1133
|
+
stashed_left_joins.unshift construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
|
1134
|
+
end
|
997
1135
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
@klass._reflect_on_association(table_name.singularize)
|
1002
|
-
end
|
1136
|
+
if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
|
1137
|
+
stashed_eager_load = joins.pop if joins.last.base_klass == klass
|
1138
|
+
end
|
1003
1139
|
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
else
|
1012
|
-
opts
|
1013
|
-
end
|
1014
|
-
end
|
1140
|
+
joins.map! do |join|
|
1141
|
+
if join.is_a?(String)
|
1142
|
+
table.create_string_join(Arel.sql(join.strip)) unless join.blank?
|
1143
|
+
else
|
1144
|
+
join
|
1145
|
+
end
|
1146
|
+
end.delete_if(&:blank?).uniq!
|
1015
1147
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
when ActiveRecord::Associations::JoinDependency
|
1024
|
-
:stashed_join
|
1025
|
-
when Arel::Nodes::Join
|
1026
|
-
:join_node
|
1027
|
-
else
|
1028
|
-
raise 'unknown class: %s' % join.class.name
|
1148
|
+
while joins.first.is_a?(Arel::Nodes::Join)
|
1149
|
+
join_node = joins.shift
|
1150
|
+
if !join_node.is_a?(Arel::Nodes::LeadingJoin) && (stashed_eager_load || stashed_left_joins)
|
1151
|
+
buckets[:join_node] << join_node
|
1152
|
+
else
|
1153
|
+
buckets[:leading_join] << join_node
|
1154
|
+
end
|
1029
1155
|
end
|
1030
|
-
end
|
1031
1156
|
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1157
|
+
joins.each do |join|
|
1158
|
+
case join
|
1159
|
+
when Hash, Symbol, Array
|
1160
|
+
buckets[:association_join] << join
|
1161
|
+
when ActiveRecord::Associations::JoinDependency
|
1162
|
+
buckets[:stashed_join] << join
|
1163
|
+
when Arel::Nodes::Join
|
1164
|
+
buckets[:join_node] << join
|
1165
|
+
else
|
1166
|
+
raise "unknown class: %s" % join.class.name
|
1167
|
+
end
|
1168
|
+
end
|
1036
1169
|
|
1037
|
-
|
1170
|
+
buckets[:stashed_join].concat stashed_left_joins if stashed_left_joins
|
1171
|
+
buckets[:stashed_join] << stashed_eager_load if stashed_eager_load
|
1038
1172
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
association_joins,
|
1042
|
-
join_list
|
1043
|
-
)
|
1173
|
+
build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
|
1174
|
+
end
|
1044
1175
|
|
1045
|
-
|
1176
|
+
def build_join_query(manager, buckets, join_type, aliases)
|
1177
|
+
association_joins = buckets[:association_join]
|
1178
|
+
stashed_joins = buckets[:stashed_join]
|
1179
|
+
leading_joins = buckets[:leading_join]
|
1180
|
+
join_nodes = buckets[:join_node]
|
1046
1181
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1182
|
+
join_sources = manager.join_sources
|
1183
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1184
|
+
|
1185
|
+
unless association_joins.empty? && stashed_joins.empty?
|
1186
|
+
alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
|
1187
|
+
join_dependency = construct_join_dependency(association_joins, join_type)
|
1188
|
+
join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
|
1189
|
+
end
|
1051
1190
|
|
1052
|
-
|
1191
|
+
join_sources.concat(join_nodes) unless join_nodes.empty?
|
1192
|
+
end
|
1053
1193
|
|
1054
|
-
|
1055
|
-
|
1194
|
+
def build_select(arel)
|
1195
|
+
if select_values.any?
|
1196
|
+
arel.project(*arel_columns(select_values.uniq))
|
1197
|
+
elsif klass.ignored_columns.any?
|
1198
|
+
arel.project(*klass.column_names.map { |field| arel_attribute(field) })
|
1199
|
+
else
|
1200
|
+
arel.project(table[Arel.star])
|
1201
|
+
end
|
1202
|
+
end
|
1056
1203
|
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1204
|
+
def arel_columns(columns)
|
1205
|
+
columns.flat_map do |field|
|
1206
|
+
case field
|
1207
|
+
when Symbol
|
1208
|
+
arel_column(field.to_s) do |attr_name|
|
1209
|
+
connection.quote_table_name(attr_name)
|
1210
|
+
end
|
1211
|
+
when String
|
1212
|
+
arel_column(field, &:itself)
|
1213
|
+
when Proc
|
1214
|
+
field.call
|
1062
1215
|
else
|
1063
1216
|
field
|
1064
1217
|
end
|
1065
1218
|
end
|
1066
|
-
|
1067
|
-
arel.project(*expanded_select)
|
1068
|
-
else
|
1069
|
-
arel.project(@klass.arel_table[Arel.star])
|
1070
1219
|
end
|
1071
|
-
end
|
1072
1220
|
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
o.reverse
|
1080
|
-
when String
|
1081
|
-
o.to_s.split(',').map! do |s|
|
1082
|
-
s.strip!
|
1083
|
-
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
1084
|
-
end
|
1221
|
+
def arel_column(field)
|
1222
|
+
field = klass.attribute_aliases[field] || field
|
1223
|
+
from = from_clause.name || from_clause.value
|
1224
|
+
|
1225
|
+
if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
|
1226
|
+
arel_attribute(field)
|
1085
1227
|
else
|
1086
|
-
|
1228
|
+
yield field
|
1087
1229
|
end
|
1088
1230
|
end
|
1089
|
-
end
|
1090
1231
|
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1232
|
+
def table_name_matches?(from)
|
1233
|
+
table_name = Regexp.escape(table.name)
|
1234
|
+
quoted_table_name = Regexp.escape(connection.quote_table_name(table.name))
|
1235
|
+
/(?:\A|(?<!FROM)\s)(?:\b#{table_name}\b|#{quoted_table_name})(?!\.)/i.match?(from.to_s)
|
1236
|
+
end
|
1094
1237
|
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1238
|
+
def reverse_sql_order(order_query)
|
1239
|
+
if order_query.empty?
|
1240
|
+
return [arel_attribute(primary_key).desc] if primary_key
|
1241
|
+
raise IrreversibleOrderError,
|
1242
|
+
"Relation has no current order and table has no primary key to be used as default order"
|
1243
|
+
end
|
1098
1244
|
|
1099
|
-
|
1100
|
-
|
1245
|
+
order_query.flat_map do |o|
|
1246
|
+
case o
|
1247
|
+
when Arel::Attribute
|
1248
|
+
o.desc
|
1249
|
+
when Arel::Nodes::Ordering
|
1250
|
+
o.reverse
|
1251
|
+
when String
|
1252
|
+
if does_not_support_reverse?(o)
|
1253
|
+
raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
|
1254
|
+
end
|
1255
|
+
o.split(",").map! do |s|
|
1256
|
+
s.strip!
|
1257
|
+
s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
|
1258
|
+
end
|
1259
|
+
else
|
1260
|
+
o
|
1261
|
+
end
|
1262
|
+
end
|
1263
|
+
end
|
1101
1264
|
|
1102
|
-
|
1103
|
-
|
1265
|
+
def does_not_support_reverse?(order)
|
1266
|
+
# Account for String subclasses like Arel::Nodes::SqlLiteral that
|
1267
|
+
# override methods like #count.
|
1268
|
+
order = String.new(order) unless order.instance_of?(String)
|
1104
1269
|
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
|
1110
|
-
"directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
|
1111
|
-
end
|
1270
|
+
# Uses SQL function with multiple arguments.
|
1271
|
+
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
|
1272
|
+
# Uses "nulls first" like construction.
|
1273
|
+
/\bnulls\s+(?:first|last)\b/i.match?(order)
|
1112
1274
|
end
|
1113
|
-
end
|
1114
1275
|
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1276
|
+
def build_order(arel)
|
1277
|
+
orders = order_values.uniq
|
1278
|
+
orders.reject!(&:blank?)
|
1118
1279
|
|
1119
|
-
|
1120
|
-
|
1121
|
-
references!(references) if references.any?
|
1280
|
+
arel.order(*orders) unless orders.empty?
|
1281
|
+
end
|
1122
1282
|
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
arg
|
1283
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
|
1284
|
+
"asc", "desc", "ASC", "DESC"].to_set # :nodoc:
|
1285
|
+
|
1286
|
+
def validate_order_args(args)
|
1287
|
+
args.each do |arg|
|
1288
|
+
next unless arg.is_a?(Hash)
|
1289
|
+
arg.each do |_key, value|
|
1290
|
+
unless VALID_DIRECTIONS.include?(value)
|
1291
|
+
raise ArgumentError,
|
1292
|
+
"Direction \"#{value}\" is invalid. Valid directions are: #{VALID_DIRECTIONS.to_a.inspect}"
|
1293
|
+
end
|
1294
|
+
end
|
1136
1295
|
end
|
1137
|
-
end
|
1138
|
-
end
|
1296
|
+
end
|
1139
1297
|
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1298
|
+
def preprocess_order_args(order_args)
|
1299
|
+
order_args.reject!(&:blank?)
|
1300
|
+
order_args.map! do |arg|
|
1301
|
+
klass.sanitize_sql_for_order(arg)
|
1302
|
+
end
|
1303
|
+
order_args.flatten!
|
1304
|
+
|
1305
|
+
@klass.disallow_raw_sql!(
|
1306
|
+
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
|
1307
|
+
permit: connection.column_name_with_order_matcher
|
1308
|
+
)
|
1309
|
+
|
1310
|
+
validate_order_args(order_args)
|
1311
|
+
|
1312
|
+
references = order_args.grep(String)
|
1313
|
+
references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact!
|
1314
|
+
references!(references) if references.any?
|
1315
|
+
|
1316
|
+
# if a symbol is given we prepend the quoted table name
|
1317
|
+
order_args.map! do |arg|
|
1318
|
+
case arg
|
1319
|
+
when Symbol
|
1320
|
+
order_column(arg.to_s).asc
|
1321
|
+
when Hash
|
1322
|
+
arg.map { |field, dir|
|
1323
|
+
case field
|
1324
|
+
when Arel::Nodes::SqlLiteral
|
1325
|
+
field.send(dir.downcase)
|
1326
|
+
else
|
1327
|
+
order_column(field.to_s).send(dir.downcase)
|
1328
|
+
end
|
1329
|
+
}
|
1330
|
+
else
|
1331
|
+
arg
|
1332
|
+
end
|
1333
|
+
end.flatten!
|
1159
1334
|
end
|
1160
|
-
end
|
1161
1335
|
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
attributes.each_value do |value|
|
1167
|
-
if value.is_a?(ActiveRecord::Relation)
|
1168
|
-
self.bind_values += value.bind_values
|
1336
|
+
def order_column(field)
|
1337
|
+
arel_column(field) do |attr_name|
|
1338
|
+
if attr_name == "count" && !group_values.empty?
|
1339
|
+
arel_attribute(attr_name)
|
1169
1340
|
else
|
1170
|
-
|
1341
|
+
Arel.sql(connection.quote_table_name(attr_name))
|
1171
1342
|
end
|
1172
1343
|
end
|
1173
1344
|
end
|
1174
|
-
|
1345
|
+
|
1346
|
+
# Checks to make sure that the arguments are not blank. Note that if some
|
1347
|
+
# blank-like object were initially passed into the query method, then this
|
1348
|
+
# method will not raise an error.
|
1349
|
+
#
|
1350
|
+
# Example:
|
1351
|
+
#
|
1352
|
+
# Post.references() # raises an error
|
1353
|
+
# Post.references([]) # does not raise an error
|
1354
|
+
#
|
1355
|
+
# This particular method should be called with a method_name and the args
|
1356
|
+
# passed into that method as an input. For example:
|
1357
|
+
#
|
1358
|
+
# def references(*args)
|
1359
|
+
# check_if_method_has_arguments!("references", args)
|
1360
|
+
# ...
|
1361
|
+
# end
|
1362
|
+
def check_if_method_has_arguments!(method_name, args)
|
1363
|
+
if args.blank?
|
1364
|
+
raise ArgumentError, "The method .#{method_name}() must contain arguments."
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
|
1368
|
+
STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope, :references, :annotate, :optimizer_hints]
|
1369
|
+
def structurally_incompatible_values_for_or(other)
|
1370
|
+
values = other.values
|
1371
|
+
STRUCTURAL_OR_METHODS.reject do |method|
|
1372
|
+
default = DEFAULT_VALUES[method]
|
1373
|
+
v1, v2 = @values.fetch(method, default), values.fetch(method, default)
|
1374
|
+
v1 = v1.uniq if v1.is_a?(Array)
|
1375
|
+
v2 = v2.uniq if v2.is_a?(Array)
|
1376
|
+
v1 == v2
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def where_clause_factory
|
1381
|
+
@where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder)
|
1382
|
+
end
|
1383
|
+
alias having_clause_factory where_clause_factory
|
1384
|
+
|
1385
|
+
DEFAULT_VALUES = {
|
1386
|
+
create_with: FROZEN_EMPTY_HASH,
|
1387
|
+
where: Relation::WhereClause.empty,
|
1388
|
+
having: Relation::WhereClause.empty,
|
1389
|
+
from: Relation::FromClause.empty
|
1390
|
+
}
|
1391
|
+
|
1392
|
+
Relation::MULTI_VALUE_METHODS.each do |value|
|
1393
|
+
DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
|
1394
|
+
end
|
1175
1395
|
end
|
1176
1396
|
end
|