aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md1816
-rw-r--r--activerecord/RUNNING_UNIT_TESTS.rdoc6
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/activerecord.gemspec2
-rw-r--r--activerecord/examples/performance.rb1
-rw-r--r--activerecord/examples/simple.rb1
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/aggregations.rb9
-rw-r--r--activerecord/lib/active_record/association_relation.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb26
-rw-r--r--activerecord/lib/active_record/associations/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb3
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb28
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb30
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb8
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute.rb34
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb15
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb28
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb22
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb15
-rw-r--r--activerecord/lib/active_record/attribute_set.rb3
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attribute_set/yaml_encoder.rb39
-rw-r--r--activerecord/lib/active_record/attributes.rb13
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb7
-rw-r--r--activerecord/lib/active_record/callbacks.rb8
-rw-r--r--activerecord/lib/active_record/coders/json.rb2
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb113
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb167
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb366
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb125
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb77
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb43
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb92
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb102
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb95
-rw-r--r--activerecord/lib/active_record/connection_adapters/statement_pool.rb10
-rw-r--r--activerecord/lib/active_record/connection_handling.rb47
-rw-r--r--activerecord/lib/active_record/core.rb34
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb24
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb8
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb27
-rw-r--r--activerecord/lib/active_record/legacy_yaml_adapter.rb2
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb11
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb8
-rw-r--r--activerecord/lib/active_record/migration.rb24
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb32
-rw-r--r--activerecord/lib/active_record/model_schema.rb21
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb22
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb8
-rw-r--r--activerecord/lib/active_record/query_cache.rb33
-rw-r--r--activerecord/lib/active_record/querying.rb8
-rw-r--r--activerecord/lib/active_record/railtie.rb21
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/reflection.rb182
-rw-r--r--activerecord/lib/active_record/relation.rb71
-rw-r--r--activerecord/lib/active_record/relation/batches.rb46
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb20
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb6
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb137
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb13
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb11
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb57
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb56
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb3
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb65
-rw-r--r--activerecord/lib/active_record/schema_migration.rb17
-rw-r--r--activerecord/lib/active_record/scoping/default.rb3
-rw-r--r--activerecord/lib/active_record/scoping/named.rb10
-rw-r--r--activerecord/lib/active_record/statement_cache.rb2
-rw-r--r--activerecord/lib/active_record/suppressor.rb9
-rw-r--r--activerecord/lib/active_record/table_metadata.rb9
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb3
-rw-r--r--activerecord/lib/active_record/type.rb2
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/serialized.rb8
-rw-r--r--activerecord/lib/active_record/type/time.rb12
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/absence.rb1
-rw-r--r--activerecord/lib/active_record/validations/length.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb23
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb33
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/application_record.rb5
-rw-r--r--activerecord/test/cases/adapter_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/boolean_test.rb14
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql2/charset_collation_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb56
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb45
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/json_test.rb15
-rw-r--r--activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql2/quoting_test.rb21
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb62
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb17
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb150
-rw-r--r--activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb30
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb72
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb32
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb173
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb24
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb28
-rw-r--r--activerecord/test/cases/aggregations_test.rb5
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb15
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_test.rb51
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb19
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb18
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb14
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb28
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb9
-rw-r--r--activerecord/test/cases/attribute_test.rb7
-rw-r--r--activerecord/test/cases/attributes_test.rb22
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb12
-rw-r--r--activerecord/test/cases/batches_test.rb40
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb3
-rw-r--r--activerecord/test/cases/calculations_test.rb8
-rw-r--r--activerecord/test/cases/coders/json_test.rb15
-rw-r--r--activerecord/test/cases/comment_test.rb139
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb71
-rw-r--r--activerecord/test/cases/connection_adapters/connection_specification_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb10
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/connection_management_test.rb70
-rw-r--r--activerecord/test/cases/connection_pool_test.rb7
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb27
-rw-r--r--activerecord/test/cases/counter_cache_test.rb2
-rw-r--r--activerecord/test/cases/database_statements_test.rb6
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb3
-rw-r--r--activerecord/test/cases/dirty_test.rb32
-rw-r--r--activerecord/test/cases/finder_test.rb123
-rw-r--r--activerecord/test/cases/fixture_set/file_test.rb6
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/helper.rb2
-rw-r--r--activerecord/test/cases/hot_compatibility_test.rb88
-rw-r--r--activerecord/test/cases/inheritance_test.rb5
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb15
-rw-r--r--activerecord/test/cases/locking_test.rb8
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb6
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb2
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb44
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb7
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb46
-rw-r--r--activerecord/test/cases/migration/references_statements_test.rb5
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb24
-rw-r--r--activerecord/test/cases/migration_test.rb90
-rw-r--r--activerecord/test/cases/modules_test.rb4
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb14
-rw-r--r--activerecord/test/cases/multiple_db_test.rb13
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb32
-rw-r--r--activerecord/test/cases/persistence_test.rb2
-rw-r--r--activerecord/test/cases/primary_keys_test.rb40
-rw-r--r--activerecord/test/cases/query_cache_test.rb78
-rw-r--r--activerecord/test/cases/quoting_test.rb6
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation/or_test.rb6
-rw-r--r--activerecord/test/cases/relation/record_fetch_warning_test.rb34
-rw-r--r--activerecord/test/cases/relation/where_test.rb14
-rw-r--r--activerecord/test/cases/relations_test.rb73
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb68
-rw-r--r--activerecord/test/cases/schema_loading_test.rb52
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb24
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb35
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb33
-rw-r--r--activerecord/test/cases/statement_cache_test.rb12
-rw-r--r--activerecord/test/cases/store_test.rb3
-rw-r--r--activerecord/test/cases/suppressor_test.rb25
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb44
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb41
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb38
-rw-r--r--activerecord/test/cases/tasks/sqlite_rake_test.rb31
-rw-r--r--activerecord/test/cases/test_case.rb4
-rw-r--r--activerecord/test/cases/time_precision_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb11
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb26
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb18
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb12
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb22
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb36
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb114
-rw-r--r--activerecord/test/cases/validations_test.rb2
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb10
-rw-r--r--activerecord/test/fixtures/price_estimates.yml11
-rw-r--r--activerecord/test/migrations/to_copy/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy/2_people_have_descriptions.rb2
-rw-r--r--activerecord/test/migrations/to_copy2/2_create_comments.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb2
-rw-r--r--activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb2
-rw-r--r--activerecord/test/models/author.rb10
-rw-r--r--activerecord/test/models/car.rb2
-rw-r--r--activerecord/test/models/cat.rb10
-rw-r--r--activerecord/test/models/comment.rb1
-rw-r--r--activerecord/test/models/customer.rb7
-rw-r--r--activerecord/test/models/notification.rb1
-rw-r--r--activerecord/test/models/owner.rb3
-rw-r--r--activerecord/test/models/pet.rb3
-rw-r--r--activerecord/test/models/pet_treasure.rb6
-rw-r--r--activerecord/test/models/tag.rb6
-rw-r--r--activerecord/test/models/uuid_item.rb6
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb12
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb5
-rw-r--r--activerecord/test/schema/schema.rb33
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb4
277 files changed, 4567 insertions, 3945 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index b59c9b4635..0bd3c2e78c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1815 +1,35 @@
-* Improve schema_migrations insertion performance by inserting all versions
- in one INSERT SQL.
-
- *Akira Matsuda*, *Naoto Koshikawa*
-
-* Using `references` or `belongs_to` in migrations will always add index
- for the referenced column by default, without adding `index: true` option
- to generated migration file. Users can opt out of this by passing
- `index: false`.
-
- Fixes #18146.
-
- *Matthew Draper*, *Prathamesh Sonpatki*
-
-* Run `type` attributes through attributes API type-casting before
- instantiating the corresponding subclass. This makes it possible to define
- custom STI mappings.
-
- Fixes #21986.
-
- *Yves Senn*
-
-* Don't try to quote functions or expressions passed to `:default` option if
- they are passed as procs.
-
- This will generate proper query with the passed function or expression for
- the default option, instead of trying to quote it in incorrect fashion.
-
- Example:
-
- create_table :posts do |t|
- t.datetime :published_at, default: -> { 'NOW()' }
- end
-
- *Ryuta Kamizono*
-
-* Fix regression when loading fixture files with symbol keys.
-
- Fixes #22584.
-
- *Yves Senn*
-
-* Use `version` column as primary key for schema_migrations table because
- `schema_migrations` versions are guaranteed to be unique.
-
- This makes it possible to use `update_attributes` on models that do
- not have a primary key.
-
- *Richard Schneeman*
-
-* Add short-hand methods for text and blob types in MySQL.
-
- In Pg and Sqlite3, `:text` and `:binary` have variable unlimited length.
- But in MySQL, these have limited length for each types (ref #21591, #21619).
- This change adds short-hand methods for each text and blob types.
-
- Example:
-
- create_table :foos do |t|
- t.tinyblob :tiny_blob
- t.mediumblob :medium_blob
- t.longblob :long_blob
- t.tinytext :tiny_text
- t.mediumtext :medium_text
- t.longtext :long_text
- end
-
- *Ryuta Kamizono*
-
-* Take into account UTC offset when assigning string representation of
- timestamp with offset specified to attribute of time type.
-
- *Andrey Novikov*
-
-* When calling `first` with a `limit` argument, return directly from the
- `loaded?` records if available.
-
- *Ben Woosley*
-
-* Deprecate sending the `offset` argument to `find_nth`. Please use the
- `offset` method on relation instead.
-
- *Ben Woosley*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Order the result of `find(ids)` to match the passed array, if the relation
- has no explicit order defined.
-
- Fixes #20338.
-
- *Miguel Grazziotin*, *Matthew Draper*
-
-* Omit default limit values in dumped schema. It's tidier, and if the defaults
- change in the future, we can address that via Migration API Versioning.
-
- *Jean Boussier*
-
-* Support passing the schema name as a prefix to table name in
- `ConnectionAdapters::SchemaStatements#indexes`. Previously the prefix would
- be considered a full part of the index name, and only the schema in the
- current search path would be considered.
-
- *Grey Baker*
-
-* Ignore index name in `index_exists?` and `remove_index` when not passed a
- name to check for.
-
- *Grey Baker*
-
-* Extract support for the legacy `mysql` database adapter from core. It will
- live on in a separate gem for now, but most users should just use `mysql2`.
-
- *Abdelkader Boudih*
-
-* ApplicationRecord is a new superclass for all app models, analogous to app
- controllers subclassing ApplicationController instead of
- ActionController::Base. This gives apps a single spot to configure app-wide
- model behavior.
-
- Newly generated applications have `app/models/application_record.rb`
- present by default.
-
- *Genadi Samokovarov*
-
-* Version the API presented to migration classes, so we can change parameter
- defaults without breaking existing migrations, or forcing them to be
- rewritten through a deprecation cycle.
-
- New migrations specify the Rails version they were written for:
-
- class AddStatusToOrders < ActiveRecord::Migration[5.0]
- def change
- # ...
- end
- end
-
- *Matthew Draper*, *Ravil Bayramgalin*
-
-* Use bind params for `limit` and `offset`. This will generate significantly
- fewer prepared statements for common tasks like pagination. To support this
- change, passing a string containing a comma to `limit` has been deprecated,
- and passing an Arel node to `limit` is no longer supported.
-
- Fixes #22250.
-
- *Sean Griffin*
-
-* Introduce after_{create,update,delete}_commit callbacks.
-
- Before:
-
- after_commit :add_to_index_later, on: :create
- after_commit :update_in_index_later, on: :update
- after_commit :remove_from_index_later, on: :destroy
-
- After:
-
- after_create_commit :add_to_index_later
- after_update_commit :update_in_index_later
- after_destroy_commit :remove_from_index_later
-
- Fixes #22515.
-
- *Genadi Samokovarov*
-
-* Respect the column default values for `inheritance_column` when
- instantiating records through the base class.
-
- Fixes #17121.
-
- Example:
-
- # The schema of BaseModel has `t.string :type, default: 'SubType'`
- subtype = BaseModel.new
- assert_equals SubType, subtype.class
-
- *Kuldeep Aggarwal*
-
-* Fix `rake db:structure:dump` on Postgres when multiple schemas are used.
-
- Fixes #22346.
-
- *Nick Muerdter*, *ckoenig*
-
-* Add schema dumping support for PostgreSQL geometric data types.
-
- *Ryuta Kamizono*
-
-* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`.
-
- Fixes #21893.
-
- *Yuichiro Kaneko*
-
-* Deprecate `connection.tables` on the SQLite3 and MySQL adapters.
- Also deprecate passing arguments to `#tables`.
- And deprecate `table_exists?`.
-
- The `#tables` method of some adapters (mysql, mysql2, sqlite3) would return
- both tables and views while others (postgresql) just return tables. To make
- their behavior consistent, `#tables` will return only tables in the future.
-
- The `#table_exists?` method would check both tables and views. To make
- their behavior consistent with `#tables`, `#table_exists?` will check only
- tables in the future.
-
- *Yuichiro Kaneko*
-
-* Improve support for non Active Record objects on `validates_associated`
-
- Skipping `marked_for_destruction?` when the associated object does not responds
- to it make easier to validate virtual associations built on top of Active Model
- objects and/or serialized objects that implement a `valid?` instance method.
-
- *Kassio Borges*, *Lucas Mazza*
-
-* Change connection management middleware to return a new response with
- a body proxy, rather than mutating the original.
-
- *Kevin Buchanan*
-
-* Make `db:migrate:status` to render `1_some.rb` format migrate files.
-
- These files are in `db/migrate`:
-
- * 1_valid_people_have_last_names.rb
- * 20150819202140_irreversible_migration.rb
- * 20150823202140_add_admin_flag_to_users.rb
- * 20150823202141_migration_tests.rb
- * 2_we_need_reminders.rb
- * 3_innocent_jointable.rb
-
- Before:
-
- $ bundle exec rake db:migrate:status
- ...
-
- Status Migration ID Migration Name
- --------------------------------------------------
- up 001 ********** NO FILE **********
- up 002 ********** NO FILE **********
- up 003 ********** NO FILE **********
- up 20150819202140 Irreversible migration
- up 20150823202140 Add admin flag to users
- up 20150823202141 Migration tests
-
- After:
-
- $ bundle exec rake db:migrate:status
- ...
-
- Status Migration ID Migration Name
- --------------------------------------------------
- up 001 Valid people have last names
- up 002 We need reminders
- up 003 Innocent jointable
- up 20150819202140 Irreversible migration
- up 20150823202140 Add admin flag to users
- up 20150823202141 Migration tests
-
- *Yuichiro Kaneko*
-
-* Define `ActiveRecord::Sanitization.sanitize_sql_for_order` and use it inside
- `preprocess_order_args`.
-
- *Yuichiro Kaneko*
-
-* Allow bigint with default nil for avoiding auto increment primary key.
-
- *Ryuta Kamizono*
-
-* Remove `DEFAULT_CHARSET` and `DEFAULT_COLLATION` in `MySQLDatabaseTasks`.
-
- We should omit the collation entirely rather than providing a default.
- Then the choice is the responsibility of the server and MySQL distribution.
-
- *Ryuta Kamizono*
-
-* Alias `ActiveRecord::Relation#left_joins` to
- `ActiveRecord::Relation#left_outer_joins`.
-
- *Takashi Kokubun*
-
-* Use advisory locking to raise a `ConcurrentMigrationError` instead of
- attempting to migrate when another migration is currently running.
-
- *Sam Davies*
-
-* Added `ActiveRecord::Relation#left_outer_joins`.
-
- Example:
-
- User.left_outer_joins(:posts)
- # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON
- "posts"."user_id" = "users"."id"
-
- *Florian Thomas*
-
-* Support passing an array to `order` for SQL parameter sanitization.
-
- *Aaron Suggs*
-
-* Avoid disabling errors on the PostgreSQL connection when enabling the
- `standard_conforming_strings` setting. Errors were previously disabled because
- the setting wasn't writable in Postgres 8.1 and didn't exist in earlier
- versions. Now Rails only supports Postgres 8.2+ we're fine to assume the
- setting exists. Disabling errors caused problems when using a connection
- pooling tool like PgBouncer because it's not guaranteed to have the same
- connection between calls to `execute` and it could leave the connection
- with errors disabled.
-
- Fixes #22101.
-
- *Harry Marr*
-
-* Set `scope.reordering_value` to `true` if `:reordering`-values are specified.
-
- Fixes #21886.
-
- *Hiroaki Izu*
-
-* Add support for bidirectional destroy dependencies.
-
- Fixes #13609.
-
- Example:
-
- class Content < ActiveRecord::Base
- has_one :position, dependent: :destroy
- end
-
- class Position < ActiveRecord::Base
- belongs_to :content, dependent: :destroy
- end
-
- *Seb Jacobs*
-
-* Includes HABTM returns correct size now. It's caused by the join dependency
- only instantiates one HABTM object because the join table hasn't a primary key.
-
- Fixes #16032.
-
- Examples:
-
- before:
-
- Project.first.salaried_developers.size # => 3
- Project.includes(:salaried_developers).first.salaried_developers.size # => 1
-
- after:
-
- Project.first.salaried_developers.size # => 3
- Project.includes(:salaried_developers).first.salaried_developers.size # => 3
-
- *Bigxiang*
-
-* Add option to index errors in nested attributes
-
- For models which have nested attributes, errors within those models will
- now be indexed if :index_errors is specified when defining a
- has_many relationship, or if its set in the global config.
-
- Example:
-
- class Guitar < ActiveRecord::Base
- has_many :tuning_pegs
- accepts_nested_attributes_for :tuning_pegs
- end
-
- class TuningPeg < ActiveRecord::Base
- belongs_to :guitar
- validates_numericality_of :pitch
- end
-
- # Old style
- guitar.errors["tuning_pegs.pitch"] = ["is not a number"]
-
- # New style (if defined globally, or set in has_many_relationship)
- guitar.errors["tuning_pegs[1].pitch"] = ["is not a number"]
-
- *Michael Probber*, *Terence Sun*
-
-* Exit with non-zero status for failed database rake tasks.
-
- *Jay Hayes*
-
-* Queries such as `Computer.joins(:monitor).group(:status).count` will now be
- interpreted as `Computer.joins(:monitor).group('computers.status').count`
- so that when `Computer` and `Monitor` have both `status` columns we don't
- have conflicts in projection.
-
- *Rafael Sales*
-
-* Add ability to default to `uuid` as primary key when generating database migrations.
-
- Example:
-
- config.generators do |g|
- g.orm :active_record, primary_key_type: :uuid
- end
-
- *Jon McCartie*
-
-* Don't cache arguments in `#find_by` if they are an `ActiveRecord::Relation`.
-
- Fixes #20817.
-
- *Hiroaki Izu*
-
-* Qualify column name inserted by `group` in calculation.
-
- Giving `group` an unqualified column name now works, even if the relation
- has `JOIN` with another table which also has a column of the name.
-
- *Soutaro Matsumoto*
-
-* Don't cache prepared statements containing an IN clause or a SQL literal, as
- these queries will change often and are unlikely to have a cache hit.
-
- *Sean Griffin*
-
-* Fix `rewhere` in a `has_many` association.
-
- Fixes #21955.
-
- *Josh Branchaud*, *Kal*
-
-* `where` raises ArgumentError on unsupported types.
-
- Fixes #20473.
-
- *Jake Worth*
-
-* Add an immutable string type to help reduce memory usage for apps which do
- not need mutation detection on strings.
-
- *Sean Griffin*
-
-* Give `ActiveRecord::Relation#update` its own deprecation warning when
- passed an `ActiveRecord::Base` instance.
-
- Fixes #21945.
-
- *Ted Johansson*
-
-* Make it possible to pass `:to_table` when adding a foreign key through
- `add_reference`.
-
- Fixes #21563.
-
- *Yves Senn*
-
-* No longer pass deprecated option `-i` to `pg_dump`.
-
- *Paul Sadauskas*
-
-* Concurrent `AR::Base#increment!` and `#decrement!` on the same record
- are all reflected in the database rather than overwriting each other.
-
- *Bogdan Gusiev*
-
-* Avoid leaking the first relation we call `first` on, per model.
-
- Fixes #21921.
-
- *Matthew Draper*, *Jean Boussier*
-
-* Remove unused `pk_and_sequence_for` in `AbstractMysqlAdapter`.
-
- *Ryuta Kamizono*
-
-* Allow fixtures files to set the model class in the YAML file itself.
-
- To load the fixtures file `accounts.yml` as the `User` model, use:
-
- _fixture:
- model_class: User
- david:
- name: David
-
- Fixes #9516.
-
- *Roque Pinel*
-
-* Don't require a database connection to load a class which uses acceptance
- validations.
+* Ensure hashes can be assigned to attributes created using `composed_of`.
+ Fixes #25210.
*Sean Griffin*
-* Correctly apply `unscope` when preloading through associations.
-
- *Jimmy Bourassa*
-
-* Fixed taking precision into count when assigning a value to timestamp attribute.
-
- Timestamp column can have less precision than ruby timestamp
- In result in how big a fraction of a second can be stored in the
- database.
-
-
- m = Model.create!
- m.created_at.usec == m.reload.created_at.usec # => false
- # due to different precision in Time.now and database column
-
- If the precision is low enough, (mysql default is 0, so it is always low
- enough by default) the value changes when model is reloaded from the
- database. This patch fixes that issue ensuring that any timestamp
- assigned as an attribute is converted to column precision under the
- attribute.
-
- *Bogdan Gusiev*
-
-* Introduce `connection.data_sources` and `connection.data_source_exists?`.
- These methods determine what relations can be used to back Active Record
- models (usually tables and views).
-
- Also deprecate `SchemaCache#tables`, `SchemaCache#table_exists?` and
- `SchemaCache#clear_table_cache!` in favor of their new data source
- counterparts.
-
- *Yves Senn*, *Matthew Draper*
-
-* Add `ActiveRecord::Base.ignored_columns` to make some columns
- invisible from Active Record.
-
- *Jean Boussier*
-
-* `ActiveRecord::Tasks::MySQLDatabaseTasks` fails if shellout to
- mysql commands (like `mysqldump`) is not successful.
-
- *Steve Mitchell*
-
-* Ensure `select` quotes aliased attributes, even when using `from`.
-
- Fixes #21488.
-
- *Sean Griffin*, *@johanlunds*
-
-* MySQL: support `unsigned` numeric data types.
-
- Example:
-
- create_table :foos do |t|
- t.unsigned_integer :quantity
- t.unsigned_bigint :total
- t.unsigned_float :percentage
- t.unsigned_decimal :price, precision: 10, scale: 2
- end
-
- The `unsigned: true` option may be used for the primary key:
-
- create_table :foos, id: :bigint, unsigned: true do |t|
- …
- end
+* Fix logging edge case where if an attribute was of the binary type and
+ was provided as a Hash.
- *Ryuta Kamizono*
+ *Jon Moss*
-* Add `#views` and `#view_exists?` methods on connection adapters.
+* Handle JSON deserialization correctly if the column default from database
+ adapter returns `''` instead of `nil`.
- *Ryuta Kamizono*
+ *Johannes Opper*
-* Correctly dump composite primary key.
+* Introduce ActiveRecord::TransactionSerializationError for catching
+ transaction serialization failures or deadlocks.
- Example:
-
- create_table :barcodes, primary_key: ["region", "code"] do |t|
- t.string :region
- t.integer :code
- end
-
- *Ryuta Kamizono*
-
-* Lookup the attribute name for `restrict_with_error` messages on the
- model class that defines the association.
-
- *kuboon*, *Ronak Jangir*
-
-* Correct query for PostgreSQL 8.2 compatibility.
-
- *Ben Murphy*, *Matthew Draper*
-
-* `bin/rake db:migrate` uses
- `ActiveRecord::Tasks::DatabaseTasks.migrations_paths` instead of
- `Migrator.migrations_paths`.
-
- *Tobias Bielohlawek*
-
-* Support dropping indexes concurrently in PostgreSQL.
-
- See http://www.postgresql.org/docs/9.4/static/sql-dropindex.html for more
- details.
-
- *Grey Baker*
+ *Erol Fornoles*
-* Deprecate passing conditions to `ActiveRecord::Relation#delete_all`
- and `ActiveRecord::Relation#destroy_all`.
+* PostgreSQL: Fix db:structure:load silent failure on SQL error
- *Wojciech Wnętrzak*
-
-* Instantiating an AR model with `ActionController::Parameters` now raises
- an `ActiveModel::ForbiddenAttributesError` if the parameters include a
- `type` field that has not been explicitly permitted. Previously, the
- `type` field was simply ignored in the same situation.
-
- *Prem Sichanugrist*
-
-* PostgreSQL, `create_schema`, `drop_schema` and `rename_table` now quote
- schema names.
-
- Fixes #21418.
+ The command line flag "-v ON_ERROR_STOP=1" should be used
+ when invoking psql to make sure errors are not suppressed.
Example:
- create_schema("my.schema")
- # CREATE SCHEMA "my.schema";
-
- *Yves Senn*
-
-* PostgreSQL, add `:if_exists` option to `#drop_schema`. This makes it
- possible to drop a schema that might exist without raising an exception if
- it doesn't.
-
- *Yves Senn*
-
-* Only try to nullify has_one target association if the record is persisted.
-
- Fixes #21223.
-
- *Agis Anastasopoulos*
-
-* Uniqueness validator raises descriptive error when running on a persisted
- record without primary key.
-
- Fixes #21304.
-
- *Yves Senn*
-
-* Add a native JSON data type support in MySQL.
-
- Example:
-
- create_table :json_data_type do |t|
- t.json :settings
- end
-
- *Ryuta Kamizono*
-
-* Descriptive error message when fixtures contain a missing column.
-
- Fixes #21201.
-
- *Yves Senn*
-
-* `ActiveRecord::Tasks::PostgreSQLDatabaseTasks` fail if shellout to
- postgresql commands (like `pg_dump`) is not successful.
-
- *Bryan Paxton*, *Nate Berkopec*
-
-* Add `ActiveRecord::Relation#in_batches` to work with records and relations
- in batches.
-
- Available options are `of` (batch size), `load`, `start`, and `finish`.
-
- Examples:
-
- Person.in_batches.each_record(&:party_all_night!)
- Person.in_batches.update_all(awesome: true)
- Person.in_batches.delete_all
- Person.in_batches.each do |relation|
- relation.delete_all
- sleep 10 # Throttles the delete queries
- end
-
- Fixes #20933.
-
- *Sina Siadat*
-
-* Added methods for PostgreSQL geometric data types to use in migrations.
-
- Example:
-
- create_table :foo do |t|
- t.line :foo_line
- t.lseg :foo_lseg
- t.box :foo_box
- t.path :foo_path
- t.polygon :foo_polygon
- t.circle :foo_circle
- end
-
- *Mehmet Emin İNAÇ*
-
-* Add `cache_key` to ActiveRecord::Relation.
-
- Example:
-
- @users = User.where("name like ?", "%Alberto%")
- @users.cache_key
- # => "/users/query-5942b155a43b139f2471b872ac54251f-3-20150714212107656125000"
-
- *Alberto Fernández-Capel*
-
-* Properly allow uniqueness validations on primary keys.
-
- Fixes #20966.
-
- *Sean Griffin*, *presskey*
-
-* Don't raise an error if an association failed to destroy when `destroy` was
- called on the parent (as opposed to `destroy!`).
-
- Fixes #20991.
-
- *Sean Griffin*
-
-* `ActiveRecord::RecordNotFound` modified to store model name, primary_key and
- id of the caller model. It allows the catcher of this exception to make
- a better decision to what to do with it.
-
- Example:
-
- class SomeAbstractController < ActionController::Base
- rescue_from ActiveRecord::RecordNotFound, with: :redirect_to_404
-
- private def redirect_to_404(e)
- return redirect_to(posts_url) if e.model == 'Post'
- raise
- end
- end
-
- *Sameer Rahmani*
-
-* Deprecate the keys for association `restrict_dependent_destroy` errors in favor
- of new key names.
-
- Previously `has_one` and `has_many` associations were using the
- `one` and `many` keys respectively. Both of these keys have special
- meaning in I18n (they are considered to be pluralizations) so by
- renaming them to `has_one` and `has_many` we make the messages more explicit
- and most importantly they don't clash with linguistical systems that need to
- validate translation keys (and their pluralizations).
-
- The `:'restrict_dependent_destroy.one'` key should be replaced with
- `:'restrict_dependent_destroy.has_one'`, and `:'restrict_dependent_destroy.many'`
- with `:'restrict_dependent_destroy.has_many'`.
-
- *Roque Pinel*, *Christopher Dell*
-
-* Fix state being carried over from previous transaction.
-
- Considering the following example where `name` is a required attribute.
- Before we had `new_record?` returning `true` for a persisted record:
-
- author = Author.create! name: 'foo'
- author.name = nil
- author.save # => false
- author.new_record? # => true
-
- Fixes #20824.
-
- *Roque Pinel*
-
-* Correctly ignore `mark_for_destruction` when `autosave` isn't set to `true`
- when validating associations.
-
- Fixes #20882.
-
- *Sean Griffin*
-
-* Fix a bug where counter_cache doesn't always work with polymorphic
- relations.
-
- Fixes #16407.
-
- *Stefan Kanev*, *Sean Griffin*
-
-* Ensure that cyclic associations with autosave don't cause duplicate errors
- to be added to the parent record.
-
- Fixes #20874.
-
- *Sean Griffin*
-
-* Ensure that `ActionController::Parameters` can still be passed to nested
- attributes.
-
- Fixes #20922.
-
- *Sean Griffin*
-
-* Deprecate force association reload by passing a truthy argument to
- association method.
-
- For collection association, you can call `#reload` on association proxy to
- force a reload:
-
- @user.posts.reload # Instead of @user.posts(true)
-
- For singular association, you can call `#reload` on the parent object to
- clear its association cache then call the association method:
-
- @user.reload.profile # Instead of @user.profile(true)
-
- Passing a truthy argument to force association to reload will be removed in
- Rails 5.1.
-
- *Prem Sichanugrist*
-
-* Replaced `ActiveSupport::Concurrency::Latch` with `Concurrent::CountDownLatch`
- from the concurrent-ruby gem.
-
- *Jerry D'Antonio*
-
-* Fix through associations using scopes having the scope merged multiple
- times.
-
- Fixes #20721.
- Fixes #20727.
-
- *Sean Griffin*
-
-* `ActiveRecord::Base.dump_schema_after_migration` applies migration tasks
- other than `db:migrate`. (eg. `db:rollback`, `db:migrate:dup`, ...)
-
- Fixes #20743.
-
- *Yves Senn*
-
-* Add alternate syntax to make `change_column_default` reversible.
-
- User can pass in `:from` and `:to` to make `change_column_default` command
- become reversible.
-
- Example:
-
- change_column_default :posts, :status, from: nil, to: "draft"
- change_column_default :users, :authorized, from: true, to: false
-
- *Prem Sichanugrist*
-
-* Prevent error when using `force_reload: true` on an unassigned polymorphic
- belongs_to association.
-
- Fixes #20426.
-
- *James Dabbs*
-
-* Correctly raise `ActiveRecord::AssociationTypeMismatch` when assigning
- a wrong type to a namespaced association.
-
- Fixes #20545.
-
- *Diego Carrion*
-
-* `validates_absence_of` respects `marked_for_destruction?`.
-
- Fixes #20449.
-
- *Yves Senn*
-
-* Include the `Enumerable` module in `ActiveRecord::Relation`
-
- *Sean Griffin*, *bogdan*
-
-* Use `Enumerable#sum` in `ActiveRecord::Relation` if a block is given.
-
- *Sean Griffin*
-
-* Let `WITH` queries (Common Table Expressions) be explainable.
-
- *Vladimir Kochnev*
-
-* Make `remove_index :table, :column` reversible.
-
- *Yves Senn*
-
-* Fixed an error which would occur in dirty checking when calling
- `update_attributes` from a getter.
-
- Fixes #20531.
-
- *Sean Griffin*
-
-* Make `remove_foreign_key` reversible. Any foreign key options must be
- specified, similar to `remove_column`.
-
- *Aster Ryan*
-
-* Add `:_prefix` and `:_suffix` options to `enum` definition.
-
- Fixes #17511, #17415.
-
- *Igor Kapkov*
-
-* Correctly handle decimal arrays with defaults in the schema dumper.
-
- Fixes #20515.
-
- *Sean Griffin*, *jmondo*
-
-* Deprecate the PostgreSQL `:point` type in favor of a new one which will return
- `Point` objects instead of an `Array`
-
- *Sean Griffin*
-
-* Ensure symbols passed to `ActiveRecord::Relation#select` are always treated
- as columns.
-
- Fixes #20360.
-
- *Sean Griffin*
-
-* Do not set `sql_mode` if `strict: :default` is specified.
-
- # config/database.yml
- production:
- adapter: mysql2
- database: foo_prod
- user: foo
- strict: :default
-
- *Ryuta Kamizono*
-
-* Allow proc defaults to be passed to the attributes API. See documentation
- for examples.
-
- *Sean Griffin*, *Kir Shatrov*
-
-* SQLite: `:collation` support for string and text columns.
-
- Example:
-
- create_table :foo do |t|
- t.string :string_nocase, collation: 'NOCASE'
- t.text :text_rtrim, collation: 'RTRIM'
- end
-
- add_column :foo, :title, :string, collation: 'RTRIM'
-
- change_column :foo, :title, :string, collation: 'NOCASE'
-
- *Akshay Vishnoi*
-
-* Allow the use of symbols or strings to specify enum values in test
- fixtures:
-
- awdr:
- title: "Agile Web Development with Rails"
- status: :proposed
-
- *George Claghorn*
-
-* Clear query cache when `ActiveRecord::Base#reload` is called.
-
- *Shane Hender, Pierre Nespo*
-
-* Include stored procedures and function on the MySQL structure dump.
-
- *Jonathan Worek*
-
-* Pass `:extend` option for `has_and_belongs_to_many` associations to the
- underlying `has_many :through`.
-
- *Jaehyun Shin*
-
-* Deprecate `Relation#uniq` use `Relation#distinct` instead.
-
- See #9683.
-
- *Yves Senn*
-
-* Allow single table inheritance instantiation to work when storing
- demodulized class names.
-
- *Alex Robbin*
-
-* Correctly pass MySQL options when using `structure_dump` or
- `structure_load`.
-
- Specifically, it fixes an issue when using SSL authentication.
-
- *Alex Coomans*
-
-* Dump indexes in `create_table` instead of `add_index`.
-
- If the adapter supports indexes in `create_table`, generated SQL is
- slightly more efficient.
-
- *Ryuta Kamizono*
-
-* Correctly dump `:options` on `create_table` for MySQL.
-
- *Ryuta Kamizono*
-
-* PostgreSQL: `:collation` support for string and text columns.
-
- Example:
-
- create_table :foos do |t|
- t.string :string_en, collation: 'en_US.UTF-8'
- t.text :text_ja, collation: 'ja_JP.UTF-8'
- end
-
- *Ryuta Kamizono*
-
-* Remove `ActiveRecord::Serialization::XmlSerializer` from core.
-
- *Zachary Scott*
-
-* Make `unscope` aware of "less than" and "greater than" conditions.
-
- *TAKAHASHI Kazuaki*
-
-* `find_by` and `find_by!` raise `ArgumentError` when called without
- arguments.
-
- *Kohei Suzuki*
-
-* Revert behavior of `db:schema:load` back to loading the full
- environment. This ensures that initializers are run.
-
- Fixes #19545.
-
- *Yves Senn*
-
-* Fix missing index when using `timestamps` with the `index` option.
-
- The `index` option used with `timestamps` should be passed to both
- `column` definitions for `created_at` and `updated_at` rather than just
- the first.
-
- *Paul Mucur*
-
-* Rename `:class` to `:anonymous_class` in association options.
-
- Fixes #19659.
-
- *Andrew White*
-
-* Autosave existing records on a has many through association when the parent
- is new.
-
- Fixes #19782.
-
- *Sean Griffin*
-
-* Fixed a bug where uniqueness validations would error on out of range values,
- even if an validation should have prevented it from hitting the database.
-
- *Andrey Voronkov*
-
-* MySQL: `:charset` and `:collation` support for string and text columns.
-
- Example:
-
- create_table :foos do |t|
- t.string :string_utf8_bin, charset: 'utf8', collation: 'utf8_bin'
- t.text :text_ascii, charset: 'ascii'
- end
-
- *Ryuta Kamizono*
-
-* Foreign key related methods in the migration DSL respect
- `ActiveRecord::Base.pluralize_table_names = false`.
-
- Fixes #19643.
-
- *Mehmet Emin İNAÇ*
-
-* Reduce memory usage from loading types on PostgreSQL.
-
- Fixes #19578.
-
- *Sean Griffin*
-
-* Add `config.active_record.warn_on_records_fetched_greater_than` option.
-
- When set to an integer, a warning will be logged whenever a result set
- larger than the specified size is returned by a query.
-
- Fixes #16463.
-
- *Jason Nochlin*
-
-* Ignore `.psqlrc` when loading database structure.
-
- *Jason Weathered*
-
-* Fix referencing wrong table aliases while joining tables of has many through
- association (only when calling calculation methods).
-
- Fixes #19276.
-
- *pinglamb*
-
-* Correctly persist a serialized attribute that has been returned to
- its default value by an in-place modification.
-
- Fixes #19467.
-
- *Matthew Draper*
-
-* Fix generating the schema file when using PostgreSQL `BigInt[]` data type.
- Previously the `limit: 8` was not coming through, and this caused it to
- become `Int[]` data type after rebuilding from the schema.
-
- Fixes #19420.
-
- *Jake Waller*
-
-* Reuse the `CollectionAssociation#reader` cache when the foreign key is
- available prior to save.
-
- *Ben Woosley*
-
-* Add `config.active_record.dump_schemas` to fix `db:structure:dump`
- when using schema_search_path and PostgreSQL extensions.
-
- Fixes #17157.
-
- *Ryan Wallace*
-
-* Renaming `use_transactional_fixtures` to `use_transactional_tests` for clarity.
-
- Fixes #18864.
-
- *Brandon Weiss*
-
-* Increase pg gem version requirement to `~> 0.18`. Earlier versions of the
- pg gem are known to have problems with Ruby 2.2.
-
- *Matt Brictson*
-
-* Correctly dump `serial` and `bigserial`.
-
- *Ryuta Kamizono*
-
-* Fix default `format` value in `ActiveRecord::Tasks::DatabaseTasks#schema_file`.
-
- *James Cox*
-
-* Don't enroll records in the transaction if they don't have commit callbacks.
- This was causing a memory leak when creating many records inside a transaction.
-
- Fixes #15549.
-
- *Will Bryant*, *Aaron Patterson*
-
-* Correctly create through records when created on a has many through
- association when using `where`.
-
- Fixes #19073.
-
- *Sean Griffin*
-
-* Add `SchemaMigration.create_table` support for any unicode charsets with MySQL.
-
- *Ryuta Kamizono*
-
-* PostgreSQL no longer disables user triggers if system triggers can't be
- disabled. Disabling user triggers does not fulfill what the method promises.
- Rails currently requires superuser privileges for this method.
-
- If you absolutely rely on this behavior, consider patching
- `disable_referential_integrity`.
-
- *Yves Senn*
-
-* Restore aborted transaction state when `disable_referential_integrity` fails
- due to missing permissions.
-
- *Toby Ovod-Everett*, *Yves Senn*
-
-* In PostgreSQL, print a warning message if `disable_referential_integrity`
- fails due to missing permissions.
-
- *Andrey Nering*, *Yves Senn*
-
-* Allow a `:limit` option for MySQL bigint primary key support.
-
- Example:
-
- create_table :foos, id: :primary_key, limit: 8 do |t|
- end
-
- # or
-
- create_table :foos, id: false do |t|
- t.primary_key :id, limit: 8
- end
-
- *Ryuta Kamizono*
-
-* `belongs_to` will now trigger a validation error by default if the association is not present.
- You can turn this off on a per-association basis with `optional: true`.
- (Note this new default only applies to new Rails apps that will be generated with
- `config.active_record.belongs_to_required_by_default = true` in initializer.)
-
- *Josef Šimánek*
-
-* Fixed `ActiveRecord::Relation#becomes!` and `changed_attributes` issues for type
- columns.
-
- Fixes #17139.
-
- *Miklos Fazekas*
-
-* Format the time string according to the precision of the time column.
-
- *Ryuta Kamizono*
-
-* Allow a `:precision` option for time type columns.
-
- *Ryuta Kamizono*
-
-* Add `ActiveRecord::Base.suppress` to prevent the receiver from being saved
- during the given block.
-
- For example, here's a pattern of creating notifications when new comments
- are posted. (The notification may in turn trigger an email, a push
- notification, or just appear in the UI somewhere):
-
- class Comment < ActiveRecord::Base
- belongs_to :commentable, polymorphic: true
- after_create -> { Notification.create! comment: self,
- recipients: commentable.recipients }
- end
-
- That's what you want the bulk of the time. A new comment creates a new
- Notification. There may be edge cases where you don't want that, like
- when copying a commentable and its comments, in which case write a
- concern with something like this:
-
- module Copyable
- def copy_to(destination)
- Notification.suppress do
- # Copy logic that creates new comments that we do not want triggering
- # notifications.
- end
- end
- end
-
- *Michael Ryan*
-
-* `:time` option added for `#touch`.
-
- Fixes #18905.
-
- *Hyonjee Joo*
-
-* Deprecate passing of `start` value to `find_in_batches` and `find_each`
- in favour of `begin_at` value.
-
- *Vipul A M*
-
-* Add `foreign_key_exists?` method.
-
- *Tõnis Simo*
-
-* Use SQL COUNT and LIMIT 1 queries for `none?` and `one?` methods
- if no block or limit is given, instead of loading the entire
- collection into memory. This applies to relations (e.g. `User.all`)
- as well as associations (e.g. `account.users`)
-
- # Before:
-
- users.none?
- # SELECT "users".* FROM "users"
-
- users.one?
- # SELECT "users".* FROM "users"
-
- # After:
-
- users.none?
- # SELECT 1 AS one FROM "users" LIMIT 1
-
- users.one?
- # SELECT COUNT(*) FROM "users"
-
- *Eugene Gilburg*
-
-* Have `enum` perform type casting consistently with the rest of Active
- Record, such as `where`.
-
- *Sean Griffin*
-
-* `scoping` no longer pollutes the current scope of sibling classes when using
- STI.
-
- Fixes #18806.
-
- Example:
-
- StiOne.none.scoping do
- StiTwo.all
- end
-
-
- *Sean Griffin*
-
-* `remove_reference` with `foreign_key: true` removes the foreign key before
- removing the column. This fixes a bug where it was not possible to remove
- the column on MySQL.
-
- Fixes #18664.
-
- *Yves Senn*
-
-* `find_in_batches` now accepts an `:finish` parameter that complements the `:start`
- parameter to specify where to stop batch processing.
-
- *Vipul A M*
-
-* Fix a rounding problem for PostgreSQL timestamp columns.
-
- If a timestamp column has a precision specified, it needs to
- format according to that.
-
- *Ryuta Kamizono*
-
-* Respect the database default charset for `schema_migrations` table.
-
- The charset of `version` column in `schema_migrations` table depends
- on the database default charset and collation rather than the encoding
- of the connection.
-
- *Ryuta Kamizono*
-
-* Raise `ArgumentError` when passing `nil` or `false` to `Relation#merge`.
-
- These are not valid values to merge in a relation, so it should warn users
- early.
-
- *Rafael Mendonça França*
-
-* Use `SCHEMA` instead of `DB_STRUCTURE` for specifying a structure file.
-
- This makes the `db:structure` tasks consistent with `test:load_structure`.
-
- *Dieter Komendera*
-
-* Respect custom primary keys for associations when calling `Relation#where`
-
- Fixes #18813.
-
- *Sean Griffin*
-
-* Fix several edge cases which could result in a counter cache updating
- twice or not updating at all for `has_many` and `has_many :through`.
-
- Fixes #10865.
-
- *Sean Griffin*
-
-* Foreign keys added by migrations were given random, generated names. This
- meant a different `structure.sql` would be generated every time a developer
- ran migrations on their machine.
-
- The generated part of foreign key names is now a hash of the table name and
- column name, which is consistent every time you run the migration.
-
- *Chris Sinjakli*
-
-* Validation errors would be raised for parent records when an association
- was saved when the parent had `validate: false`. It should not be the
- responsibility of the model to validate an associated object unless the
- object was created or modified by the parent.
-
- This fixes the issue by skipping validations if the parent record is
- persisted, not changed, and not marked for destruction.
-
- Fixes #17621.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Fix n+1 query problem when eager loading nil associations (fixes #18312)
-
- *Sammy Larbi*
-
-* Change the default error message from `can't be blank` to `must exist` for
- the presence validator of the `:required` option on `belongs_to`/`has_one`
- associations.
-
- *Henrik Nygren*
-
-* Fixed `ActiveRecord::Relation#group` method when an argument is an SQL
- reserved keyword:
-
- Example:
-
- SplitTest.group(:key).count
- Property.group(:value).count
-
- *Bogdan Gusiev*
-
-* Added the `#or` method on `ActiveRecord::Relation`, allowing use of the OR
- operator to combine WHERE or HAVING clauses.
-
- Example:
-
- Post.where('id = 1').or(Post.where('id = 2'))
- # => SELECT * FROM posts WHERE (id = 1) OR (id = 2)
-
- *Sean Griffin*, *Matthew Draper*, *Gael Muller*, *Olivier El Mekki*
-
-* Don't define autosave association callbacks twice from
- `accepts_nested_attributes_for`.
-
- Fixes #18704.
-
- *Sean Griffin*
-
-* Integer types will no longer raise a `RangeError` when assigning an
- attribute, but will instead raise when going to the database.
-
- Fixes several vague issues which were never reported directly. See the
- commit message from the commit which added this line for some examples.
-
- *Sean Griffin*
-
-* Values which would error while being sent to the database (such as an
- ASCII-8BIT string with invalid UTF-8 bytes on SQLite3), no longer error on
- assignment. They will still error when sent to the database, but you are
- given the ability to re-assign it to a valid value.
-
- Fixes #18580.
-
- *Sean Griffin*
-
-* Don't remove join dependencies in `Relation#exists?`
-
- Fixes #18632.
-
- *Sean Griffin*
-
-* Invalid values assigned to a JSON column are assumed to be `nil`.
-
- Fixes #18629.
-
- *Sean Griffin*
-
-* Add `ActiveRecord::Base#accessed_fields`, which can be used to quickly
- discover which fields were read from a model when you are looking to only
- select the data you need from the database.
-
- *Sean Griffin*
-
-* Introduce the `:if_exists` option for `drop_table`.
-
- Example:
-
- drop_table(:posts, if_exists: true)
-
- That would execute:
-
- DROP TABLE IF EXISTS posts
-
- If the table doesn't exist, `if_exists: false` (the default) raises an
- exception whereas `if_exists: true` does nothing.
-
- *Cody Cutrer*, *Stefan Kanev*, *Ryuta Kamizono*
-
-* Don't run SQL if attribute value is not changed for update_attribute method.
-
- *Prathamesh Sonpatki*
-
-* `time` columns can now get affected by `time_zone_aware_attributes`. If you have
- set `config.time_zone` to a value other than `'UTC'`, they will be treated
- as in that time zone by default in Rails 5.1. If this is not the desired
- behavior, you can set
-
- ActiveRecord::Base.time_zone_aware_types = [:datetime]
-
- A deprecation warning will be emitted if you have a `:time` column, and have
- not explicitly opted out.
-
- Fixes #3145.
-
- *Sean Griffin*
-
-* Tests now run after_commit callbacks. You no longer have to declare
- `uses_transaction ‘test name’` to test the results of an after_commit.
-
- after_commit callbacks run after committing a transaction whose parent
- is not `joinable?`: un-nested transactions, transactions within test cases,
- and transactions in `console --sandbox`.
-
- *arthurnn*, *Ravil Bayramgalin*, *Matthew Draper*
-
-* `nil` as a value for a binary column in a query no longer logs as
- "<NULL binary data>", and instead logs as just "nil".
-
- *Sean Griffin*
-
-* `attribute_will_change!` will no longer cause non-persistable attributes to
- be sent to the database.
-
- Fixes #18407.
-
- *Sean Griffin*
-
-* Remove support for the `protected_attributes` gem.
-
- *Carlos Antonio da Silva*, *Roberto Miranda*
-
-* Fix accessing of fixtures having non-string labels like Fixnum.
-
- *Prathamesh Sonpatki*
-
-* Remove deprecated support to preload instance-dependent associations.
-
- *Yves Senn*
-
-* Remove deprecated support for PostgreSQL ranges with exclusive lower bounds.
-
- *Yves Senn*
-
-* Remove deprecation when modifying a relation with cached Arel.
- This raises an `ImmutableRelation` error instead.
-
- *Yves Senn*
-
-* Added `ActiveRecord::SecureToken` in order to encapsulate generation of
- unique tokens for attributes in a model using `SecureRandom`.
-
- *Roberto Miranda*
-
-* Change the behavior of boolean columns to be closer to Ruby's semantics.
-
- Before this change we had a small set of "truthy", and all others are "falsy".
-
- Now, we have a small set of "falsy" values and all others are "truthy" matching
- Ruby's semantics.
-
- *Rafael Mendonça França*
-
-* Deprecate `ActiveRecord::Base.errors_in_transactional_callbacks=`.
-
- *Rafael Mendonça França*
-
-* Change transaction callbacks to not swallow errors.
-
- Before this change any errors raised inside a transaction callback
- were getting rescued and printed in the logs.
-
- Now these errors are not rescued anymore and just bubble up, as the other callbacks.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `sanitize_sql_hash_for_conditions`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `Reflection#source_macro`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `symbolized_base_class` and `symbolized_sti_name`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated access to connection specification using a string accessor.
-
- Now all strings will be handled as a URL.
-
- *Rafael Mendonça França*
-
-* Change the default `null` value for `timestamps` to `false`.
-
- *Rafael Mendonça França*
-
-* Return an array of pools from `connection_pools`.
-
- *Rafael Mendonça França*
-
-* Return a null column from `column_for_attribute` when no column exists.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `serialized_attributes`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated automatic counter caches on `has_many :through`.
-
- *Rafael Mendonça França*
-
-* Change the way in which callback chains can be halted.
-
- The preferred method to halt a callback chain from now on is to explicitly
- `throw(:abort)`.
- In the past, returning `false` in an Active Record `before_` callback had the
- side effect of halting the callback chain.
- This is not recommended anymore and, depending on the value of the
- `ActiveSupport.halt_callback_chains_on_return_false` option, will
- either not work at all or display a deprecation warning.
-
- *claudiob*
-
-* Clear query cache on rollback.
-
- *Florian Weingarten*
-
-* Fix setting of foreign_key for through associations when building a new record.
-
- Fixes #12698.
-
- *Ivan Antropov*
-
-* Improve dumping of the primary key. If it is not a default primary key,
- correctly dump the type and options.
-
- Fixes #14169, #16599.
-
- *Ryuta Kamizono*
-
-* Format the datetime string according to the precision of the datetime field.
-
- Incompatible to rounding behavior between MySQL 5.6 and earlier.
-
- In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
- is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
-
- http://bugs.mysql.com/bug.php?id=68760
-
- *Ryuta Kamizono*
-
-* Allow a precision option for MySQL datetimes.
-
- *Ryuta Kamizono*
-
-* Fixed automatic `inverse_of` for models nested in a module.
-
- *Andrew McCloud*
-
-* Change `ActiveRecord::Relation#update` behavior so that it can
- be called without passing ids of the records to be updated.
-
- This change allows updating multiple records returned by
- `ActiveRecord::Relation` with callbacks and validations.
-
- # Before
- # ArgumentError: wrong number of arguments (1 for 2)
- Comment.where(group: 'expert').update(body: "Group of Rails Experts")
-
- # After
- # Comments with group expert updated with body "Group of Rails Experts"
- Comment.where(group: 'expert').update(body: "Group of Rails Experts")
-
- *Prathamesh Sonpatki*
-
-* Fix `reaping_frequency` option when the value is a string.
-
- This usually happens when it is configured using `DATABASE_URL`.
-
- *korbin*
-
-* Fix error message when trying to create an associated record and the foreign
- key is missing.
-
- Before this fix the following exception was being raised:
-
- NoMethodError: undefined method `val' for #<Arel::Nodes::BindParam:0x007fc64d19c218>
-
- Now the message is:
-
- ActiveRecord::UnknownAttributeError: unknown attribute 'foreign_key' for Model.
-
- *Rafael Mendonça França*
-
-* Fix change detection problem for PostgreSQL bytea type and
- `ArgumentError: string contains null byte` exception with pg-0.18.
-
- Fixes #17680.
-
- *Lars Kanis*
-
-* When a table has a composite primary key, the `primary_key` method for
- SQLite3 and PostgreSQL adapters was only returning the first field of the key.
- Ensures that it will return nil instead, as Active Record doesn't support
- composite primary keys.
-
- Fixes #18070.
-
- *arthurnn*
-
-* `validates_size_of` / `validates_length_of` do not count records
- which are `marked_for_destruction?`.
-
- Fixes #7247.
-
- *Yves Senn*
-
-* Ensure `first!` and friends work on loaded associations.
-
- Fixes #18237.
-
- *Sean Griffin*
-
-* `eager_load` preserves readonly flag for associations.
-
- Fixes #15853.
-
- *Takashi Kokubun*
-
-* Provide `:touch` option to `save()` to accommodate saving without updating
- timestamps.
-
- Fixes #18202.
-
- *Dan Olson*
-
-* Provide a more helpful error message when an unsupported class is passed to
- `serialize`.
-
- Fixes #18224.
-
- *Sean Griffin*
-
-* Add bigint primary key support for MySQL.
-
- Example:
-
- create_table :foos, id: :bigint do |t|
- end
-
- *Ryuta Kamizono*
-
-* Support for any type of primary key.
-
- Fixes #14194.
-
- *Ryuta Kamizono*
-
-* Dump the default `nil` for PostgreSQL UUID primary key.
-
- *Ryuta Kamizono*
-
-* Add a `:foreign_key` option to `references` and associated migration
- methods. The model and migration generators now use this option, rather than
- the `add_foreign_key` form.
-
- *Sean Griffin*
-
-* Don't raise when writing an attribute with an out-of-range datetime passed
- by the user.
-
- *Grey Baker*
-
-* Replace deprecated `ActiveRecord::Tasks::DatabaseTasks#load_schema` with
- `ActiveRecord::Tasks::DatabaseTasks#load_schema_for`.
-
- *Yves Senn*
-
-* Fix bug with `ActiveRecord::Type::Numeric` that caused negative values to
- be marked as having changed when set to the same negative value.
-
- Fixes #18161.
-
- *Daniel Fox*
-
-* Introduce `force: :cascade` option for `create_table`. Using this option
- will recreate tables even if they have dependent objects (like foreign keys).
- `db/schema.rb` now uses `force: :cascade`. This makes it possible to
- reload the schema when foreign keys are in place.
-
- *Matthew Draper*, *Yves Senn*
-
-* `db:schema:load` and `db:structure:load` no longer purge the database
- before loading the schema. This is left for the user to do.
- `db:test:prepare` will still purge the database.
-
- Fixes #17945.
-
- *Yves Senn*
-
-* Fix undesirable RangeError by `Type::Integer`. Add `Type::UnsignedInteger`.
-
- *Ryuta Kamizono*
-
-* Add `foreign_type` option to `has_one` and `has_many` association macros.
-
- This option enables to define the column name of associated object's type for polymorphic associations.
-
- *Ulisses Almeida*, *Kassio Borges*
-
-* Remove deprecated behavior allowing nested arrays to be passed as query
- values.
-
- *Melanie Gilman*
-
-* Deprecate passing a class as a value in a query. Users should pass strings
- instead.
-
- *Melanie Gilman*
-
-* `add_timestamps` and `remove_timestamps` now properly reversible with
- options.
+ psql -v ON_ERROR_STOP=1 -q -f awesome-file.sql my-app-db
- *Noam Gagliardi-Rabinovich*
+ Fixes #23818.
-* `ActiveRecord::ConnectionAdapters::ColumnDumper#column_spec` and
- `ActiveRecord::ConnectionAdapters::ColumnDumper#prepare_column_options` no
- longer have a `types` argument. They should access
- `connection#native_database_types` directly.
+ *Ralin Chimev*
- *Yves Senn*
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc
index a74fcf2df7..cd22f76d01 100644
--- a/activerecord/RUNNING_UNIT_TESTS.rdoc
+++ b/activerecord/RUNNING_UNIT_TESTS.rdoc
@@ -7,11 +7,11 @@ http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#setting-up-
To run a specific test:
- $ ruby -Itest test/cases/base_test.rb -n method_name
+ $ bundle exec ruby -Itest test/cases/base_test.rb -n method_name
To run a set of tests:
- $ ruby -Itest test/cases/base_test.rb
+ $ bundle exec ruby -Itest test/cases/base_test.rb
You can also run tests that depend upon a specific database backend. For
example:
@@ -41,7 +41,7 @@ parameters in +test/config.example.yml+ are.
You can override the +connections:+ parameter in either file using the +ARCONN+
(Active Record CONNection) environment variable:
- $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb
+ $ ARCONN=postgresql bundle exec ruby -Itest test/cases/base_test.rb
You can specify a custom location for the config file using the +ARCONFIG+
environment variable.
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0564dca94a..012db35765 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -20,6 +20,8 @@ end
desc 'Run mysql2, sqlite, and postgresql tests by default'
task :default => :test
+task :package
+
desc 'Run mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 4405da2812..881dee13e4 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index a5a1f284a0..c3c46c19c5 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -1,4 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
require "active_record"
require 'benchmark/ips'
diff --git a/activerecord/examples/simple.rb b/activerecord/examples/simple.rb
index 4ed5d80eb2..5a041e8417 100644
--- a/activerecord/examples/simple.rb
+++ b/activerecord/examples/simple.rb
@@ -1,4 +1,3 @@
-require File.expand_path('../../../load_paths', __FILE__)
require 'active_record'
class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ab3846ae65..baa497dc98 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -137,7 +137,6 @@ module ActiveRecord
eager_autoload do
autoload :AbstractAdapter
- autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
end
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 3ff41ed81b..8bed5bca28 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -256,15 +256,16 @@ module ActiveRecord
def writer_method(name, class_name, mapping, allow_nil, converter)
define_method("#{name}=") do |part|
klass = class_name.constantize
- if part.is_a?(Hash)
- raise ArgumentError unless part.size == part.keys.max
- part = klass.new(*part.sort.map(&:last))
- end
unless part.is_a?(klass) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
end
+ if part.is_a?(Hash)
+ raise ArgumentError unless part.size == part.keys.max
+ part = klass.new(*part.sort.map(&:last))
+ end
+
if part.nil? && allow_nil
mapping.each { |key, _| self[key] = nil }
@aggregation_cache[name] = nil
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index ee0bb8fafe..c18e88e4cf 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
end
def ==(other)
- other == to_a
+ other == records
end
def build(*args, &block)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f6d8e8a342..3729e22e64 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -300,10 +300,10 @@ module ActiveRecord
#
# === A word of warning
#
- # Don't create associations that have the same name as instance methods of
- # ActiveRecord::Base. Since the association adds a method with that name to
- # its model, it will override the inherited method and break things.
- # For instance, +attributes+ and +connection+ would be bad choices for association names.
+ # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, using an association with the same name as one provided by <tt>ActiveRecord::Base</tt> will override the method inherited through <tt>ActiveRecord::Base</tt> and will break things.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of <tt>ActiveRecord::Base</tt> instance methods.
#
# == Auto-generated methods
# See also Instance Public methods below for more details.
@@ -318,7 +318,7 @@ module ActiveRecord
# create_other(attributes={}) | X | | X
# create_other!(attributes={}) | X | | X
#
- # ===Collection associations (one-to-many / many-to-many)
+ # === Collection associations (one-to-many / many-to-many)
# | | | has_many
# generated methods | habtm | has_many | :through
# ----------------------------------+-------+----------+----------
@@ -504,7 +504,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax
+ # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
@@ -1326,7 +1326,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. true by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
@@ -1456,7 +1457,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated object when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated object.
@@ -1580,7 +1582,8 @@ module ActiveRecord
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction, when
# saving the parent object.
@@ -1593,6 +1596,8 @@ module ActiveRecord
# If true, the associated object will be touched (the updated_at/on attributes set to current time)
# when this record is either saved or destroyed. If you specify a symbol, that attribute
# will be updated with the current time in addition to the updated_at/on attribute.
+ # Please note that with touching no validation is performed and only the +after_touch+,
+ # +after_commit+ and +after_rollback+ callbacks are executed.
# [:inverse_of]
# Specifies the name of the #has_one or #has_many association on the associated
# object that is the inverse of this #belongs_to association. Does not work in
@@ -1764,7 +1769,8 @@ module ActiveRecord
# So if a Person class makes a #has_and_belongs_to_many association to Project,
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction, when
# saving the parent object.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index d64ab64c99..62e867a353 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -217,7 +217,8 @@ module ActiveRecord
unless record.is_a?(reflection.klass)
fresh_class = reflection.class_name.safe_constantize
unless fresh_class && record.is_a?(fresh_class)
- message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
+ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
@@ -257,7 +258,7 @@ module ActiveRecord
# Returns true if statement cache should be skipped on the association reader.
def skip_statement_cache?
- reflection.scope_chain.any?(&:any?) ||
+ reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
reflection.source_reflection.active_record.default_scopes.any?
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 48437a1c9e..15844de0bc 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -124,8 +124,7 @@ module ActiveRecord
scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass)
reflection = chain_head
- loop do
- break unless reflection
+ while reflection
table = reflection.alias_name
unless reflection == chain_tail
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 41698c5360..24997370b2 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -61,6 +61,7 @@ module ActiveRecord
def update_counters_on_replace(record)
if require_counter_update? && different_target?(record)
+ owner.instance_variable_set :@_after_replace_counter_called, true
record.increment!(reflection.counter_cache_column)
decrement_counters
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 346329c610..3121e70a04 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -33,6 +33,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
if (@_after_create_counter_called ||= false)
@_after_create_counter_called = false
+ elsif (@_after_replace_counter_called ||= false)
+ @_after_replace_counter_called = false
elsif attribute_changed?(foreign_key) && !new_record?
if reflection.polymorphic?
model = attribute(reflection.foreign_type).try(:constantize)
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 56a8dc4e18..f25bd7ca9f 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -70,7 +70,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.wrap_scope(scope, mod)
if scope
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ if scope.arity > 0
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ else
+ proc { instance_exec(&scope).extending(mod) }
+ end
else
proc { extending(mod) }
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index b888148841..5fbd79d118 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -76,6 +76,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
+ def self.primary_key
+ false
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 473b80a658..0eaa0a4f36 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -10,9 +10,9 @@ module ActiveRecord
# HasManyAssociation => has_many
# HasManyThroughAssociation + ThroughAssociation => has_many :through
#
- # CollectionAssociation class provides common methods to the collections
+ # The CollectionAssociation class provides common methods to the collections
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
- # +:through association+ option.
+ # the +:through association+ option.
#
# You need to be careful with assumptions regarding the target: The proxy
# does not fetch records from the database until it needs them, but new
@@ -72,7 +72,10 @@ module ActiveRecord
pk_type = reflection.primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
- replace(klass.find(ids).index_by(&:id).values_at(*ids))
+ records = klass.where(reflection.association_primary_key => ids).index_by do |r|
+ r.send(reflection.association_primary_key)
+ end.values_at(*ids)
+ replace(records)
end
def reset
@@ -133,6 +136,14 @@ module ActiveRecord
first_nth_or_last(:forty_two, *args)
end
+ def third_to_last(*args)
+ first_nth_or_last(:third_to_last, *args)
+ end
+
+ def second_to_last(*args)
+ first_nth_or_last(:second_to_last, *args)
+ end
+
def last(*args)
first_nth_or_last(:last, *args)
end
@@ -235,9 +246,12 @@ module ActiveRecord
end
end
- # Count all records using SQL. Construct options and pass them with
- # scope to the target class's +count+.
+ # Returns the number of records. If no arguments are given, it counts all
+ # columns using SQL. If one argument is given, it counts only the passed
+ # column using SQL. If a block is given, it counts the number of records
+ # yielding a true value.
def count(column_name = nil)
+ return super if block_given?
relation = scope
if association_scope.distinct_value
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -269,7 +283,7 @@ module ActiveRecord
_options = records.extract_options!
dependent = _options[:dependent] || options[:dependent]
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, dependent)
end
@@ -280,7 +294,7 @@ module ActiveRecord
# +:dependent+ option.
def destroy(*records)
return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index fe693cfbb6..5d1e7ffb73 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -197,6 +197,16 @@ module ActiveRecord
@association.forty_two(*args)
end
+ # Same as #first except returns only the third-to-last record.
+ def third_to_last(*args)
+ @association.third_to_last(*args)
+ end
+
+ # Same as #first except returns only the second-to-last record.
+ def second_to_last(*args)
+ @association.second_to_last(*args)
+ end
+
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -587,7 +597,7 @@ module ActiveRecord
# Pet.find(1)
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and executes delete on them.
#
# class Person < ActiveRecord::Base
@@ -651,7 +661,7 @@ module ActiveRecord
#
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
#
- # You can pass +Fixnum+ or +String+ values, it finds the records
+ # You can pass +Integer+ or +String+ values, it finds the records
# responding to the +id+ and then deletes them from the database.
#
# person.pets.size # => 3
@@ -705,12 +715,13 @@ module ActiveRecord
end
alias uniq distinct
- # Count all records using SQL.
+ # Count all records.
#
# class Person < ActiveRecord::Base
# has_many :pets
# end
#
+ # # This will perform the count using SQL.
# person.pets.count # => 3
# person.pets
# # => [
@@ -718,8 +729,13 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def count(column_name = nil)
- @association.count(column_name)
+ #
+ # Passing a block will select all of a person's pets in SQL and then
+ # perform the count using Ruby.
+ #
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
+ def count(column_name = nil, &block)
+ @association.count(column_name, &block)
end
# Returns the size of the collection. If the collection hasn't been loaded,
@@ -969,6 +985,10 @@ module ActiveRecord
end
alias_method :to_a, :to_ary
+ def records # :nodoc:
+ load_target
+ end
+
# Adds one or more +records+ to the collection by setting their foreign keys
# to the association's primary key. Returns +self+, so several appends may be
# chained together.
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 0e4e951269..491152bbcb 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -92,8 +92,9 @@ module ActiveRecord
# associations # => [:appointments]
# joins # => []
#
- def initialize(base, associations, joins)
+ def initialize(base, associations, joins, eager_loading: true)
@alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster)
+ @eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new base, build(tree, base)
@join_root.children.each { |child| construct_tables! @join_root, child }
@@ -228,7 +229,7 @@ module ActiveRecord
def find_reflection(klass, name)
klass._reflect_on_association(name) or
- raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
+ raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
end
def build(associations, base_klass)
@@ -238,11 +239,12 @@ module ActiveRecord
reflection.check_eager_loadable!
if reflection.polymorphic?
+ next unless @eager_loading
raise EagerLoadPolymorphicError.new(reflection)
end
JoinAssociation.new reflection, build(right, reflection.klass)
- end
+ end.compact
end
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index be65cf318c..c5fbe0d1d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -54,12 +54,18 @@ module ActiveRecord
end
scope_chain_index += 1
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
+ klass_scope =
+ if klass.current_scope
+ klass.current_scope.clone
+ else
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ klass.send(:build_default_scope, relation)
+ end
+ scope_chain_items.concat [klass_scope].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
@@ -75,7 +81,7 @@ module ActiveRecord
column = klass.columns_hash[reflection.type.to_s]
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new)
+ constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new)
end
joins << table.create_join(table, table.create_on(constraint), join_type)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index ecf6fb8643..e64af84e1a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -184,6 +184,7 @@ module ActiveRecord
def self.new(klass, owners, reflection, preload_scope); self; end
def self.run(preloader); end
def self.preloaded_records; []; end
+ def self.owners; []; end
end
# Returns a class containing the logic needed to load preload the data
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index e11a5cfb8a..3032bc786e 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -47,7 +47,7 @@ module ActiveRecord
# This is overridden by HABTM as the condition should be on the foreign_key column in
# the join table
def association_key
- table[association_key_name]
+ klass.arel_attribute(association_key_name, table)
end
# The name of the key on the model which declares the association
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 6c83058202..b0203909ce 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -38,12 +38,7 @@ module ActiveRecord
}
end
- record_offset = {}
- @preloaded_records.each_with_index do |record,i|
- record_offset[record] = i
- end
-
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
@@ -53,13 +48,25 @@ module ActiveRecord
target_records_from_association(association)
}.compact
- rhs_records.sort_by { |rhs| record_offset[rhs] }
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
+ if reflection_scope.values[:order].present?
+ @id_map ||= id_to_index_map @preloaded_records
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
+ else
+ rhs_records
+ end
end
- }
+ end
end
private
+ def id_to_index_map(ids)
+ id_map = {}
+ ids.each_with_index { |id, index| id_map[id] = index }
+ id_map
+ end
+
def reset_association(owners, association_name)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index c7cc48ba16..f913f0852a 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
def get_records
- return scope.limit(1).to_a if skip_statement_cache?
+ return scope.limit(1).records if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb
index 3c4c8f10ec..9530f134d0 100644
--- a/activerecord/lib/active_record/attribute.rb
+++ b/activerecord/lib/active_record/attribute.rb
@@ -77,7 +77,11 @@ module ActiveRecord
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ if changed_in_place?
+ with_value_from_user(value).with_type(type)
+ else
+ self.class.new(name, value_before_type_cast, type, original_attribute)
+ end
end
def type_cast(*)
@@ -108,6 +112,22 @@ module ActiveRecord
[self.class, name, value_before_type_cast, type].hash
end
+ def init_with(coder)
+ @name = coder["name"]
+ @value_before_type_cast = coder["value_before_type_cast"]
+ @type = coder["type"]
+ @original_attribute = coder["original_attribute"]
+ @value = coder["value"] if coder.map.key?("value")
+ end
+
+ def encode_with(coder)
+ coder["name"] = name
+ coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
+ coder["type"] = type if type
+ coder["original_attribute"] = original_attribute if original_attribute
+ coder["value"] = value if defined?(@value)
+ end
+
protected
attr_reader :original_attribute
@@ -170,7 +190,7 @@ module ActiveRecord
super(name, nil, Type::Value.new)
end
- def value
+ def type_cast(*)
nil
end
@@ -185,6 +205,8 @@ module ActiveRecord
end
class Uninitialized < Attribute # :nodoc:
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
+
def initialize(name, type)
super(name, nil, type)
end
@@ -195,12 +217,20 @@ module ActiveRecord
end
end
+ def original_value
+ UNINITIALIZED_ORIGINAL_VALUE
+ end
+
def value_for_database
end
def initialized?
false
end
+
+ def with_type(type)
+ self.class.new(name, type)
+ end
end
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 6dbd92ce28..4580813364 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -4,20 +4,25 @@ module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
+ @user_provided_value = value
super(name, value, type, database_default)
end
- def type_cast(value)
- if value.is_a?(Proc)
- super(value.call)
+ def value_before_type_cast
+ if user_provided_value.is_a?(Proc)
+ @memoized_value_before_type_cast ||= user_provided_value.call
else
- super
+ @user_provided_value
end
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ self.class.new(name, user_provided_value, type, original_attribute)
end
+
+ protected
+
+ attr_reader :user_provided_value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index a6d81c82b4..b96d8e9352 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -29,14 +29,6 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Tries to assign given value to given attribute.
- # In case of an error, re-raises with the ActiveRecord constant.
- def _assign_attribute(k, v) # :nodoc:
- super
- rescue ActiveModel::UnknownAttributeError
- raise UnknownAttributeError.new(self, k)
- end
-
# Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
@@ -46,7 +38,7 @@ module ActiveRecord
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum and
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 423a93964e..1fb5eb28cd 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -34,30 +34,6 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
- class AttributeMethodCache
- def initialize
- @module = Module.new
- @method_cache = Concurrent::Map.new
- end
-
- def [](name)
- @method_cache.compute_if_absent(name) do
- safe_name = name.unpack('h*'.freeze).first
- temp_method = "__temp__#{safe_name}"
- ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
- @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
- @module.instance_method temp_method
- end
- end
-
- private
-
- # Override this method in the subclasses for method body.
- def method_body(method_name, const_name)
- raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method."
- end
- end
-
class GeneratedAttributeMethods < Module; end # :nodoc:
module ClassMethods
@@ -358,8 +334,6 @@ module ActiveRecord
#
# Note: +:id+ is always present.
#
- # Alias for the #read_attribute method.
- #
# class Person < ActiveRecord::Base
# belongs_to :organization
# end
@@ -384,7 +358,7 @@ module ActiveRecord
# person = Person.new
# person[:age] = '22'
# person[:age] # => 22
- # person[:age] # => Fixnum
+ # person[:age].class # => Integer
def []=(attr_name, value)
write_attribute(attr_name, value)
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 5197e21fa4..ab2ecaa7c5 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,8 +1,11 @@
module ActiveRecord
module AttributeMethods
module Read
- ReaderMethodCache = Class.new(AttributeMethodCache) {
- private
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ protected
+
# We want to generate the methods via module_eval rather than
# define_method, because define_method is slower on dispatch.
# Evaluating many similar methods may use more memory as the instruction
@@ -21,21 +24,6 @@ module ActiveRecord
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
- def method_body(method_name, const_name)
- <<-EOMETHOD
- def #{method_name}
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- _read_attribute(name) { |n| missing_attribute(n, caller) }
- end
- EOMETHOD
- end
- }.new
-
- extend ActiveSupport::Concern
-
- module ClassMethods
- protected
-
def define_method_attribute(name)
safe_name = name.unpack('h*'.freeze).first
temp_method = "__temp__#{safe_name}"
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 061628725d..e160460286 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -20,7 +20,7 @@ module ActiveRecord
nil
end
else
- map(super) { |t| cast(t) }
+ map_avoiding_infinite_recursion(super) { |v| cast(v) }
end
end
@@ -34,13 +34,23 @@ module ActiveRecord
elsif value.is_a?(::Float)
value
else
- map(value) { |v| convert_time_to_time_zone(v) }
+ map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
end
end
def set_time_zone_without_conversion(value)
::Time.zone.local_to_utc(value).in_time_zone
end
+
+ def map_avoiding_infinite_recursion(value)
+ map(value) do |v|
+ if value.equal?(v)
+ nil
+ else
+ yield(v)
+ end
+ end
+ end
end
extend ActiveSupport::Concern
@@ -94,7 +104,7 @@ module ActiveRecord
To silence this deprecation warning, add the following:
- config.active_record.time_zone_aware_types << :time
+ config.active_record.time_zone_aware_types = [:datetime, :time]
MESSAGE
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index bbf2a51a0e..70c2d2f25d 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -1,19 +1,6 @@
module ActiveRecord
module AttributeMethods
module Write
- WriterMethodCache = Class.new(AttributeMethodCache) {
- private
-
- def method_body(method_name, const_name)
- <<-EOMETHOD
- def #{method_name}(value)
- name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name}
- write_attribute(name, value)
- end
- EOMETHOD
- end
- }.new
-
extend ActiveSupport::Concern
included do
@@ -39,7 +26,7 @@ module ActiveRecord
end
# Updates the attribute identified by <tt>attr_name</tt> with the
- # specified +value+. Empty strings for fixnum and float columns are
+ # specified +value+. Empty strings for Integer and Float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
write_attribute_with_type_cast(attr_name, value, true)
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index be581ac2a9..720d5f8b7c 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,7 +1,10 @@
require 'active_record/attribute_set/builder'
+require 'active_record/attribute_set/yaml_encoder'
module ActiveRecord
class AttributeSet # :nodoc:
+ delegate :each_value, to: :attributes
+
def initialize(attributes)
@attributes = attributes
end
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index 3bd7c7997b..24a255efc1 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
class LazyAttributeHash # :nodoc:
- delegate :transform_values, :each_key, to: :materialize
+ delegate :transform_values, :each_key, :each_value, to: :materialize
def initialize(types, values, additional_types)
@types = types
diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
new file mode 100644
index 0000000000..6208048231
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb
@@ -0,0 +1,39 @@
+module ActiveRecord
+ class AttributeSet
+ # Attempts to do more intelligent YAML dumping of an
+ # ActiveRecord::AttributeSet to reduce the size of the resulting string
+ class YAMLEncoder
+ def initialize(default_types)
+ @default_types = default_types
+ end
+
+ def encode(attribute_set, coder)
+ coder['concise_attributes'] = attribute_set.each_value.map do |attr|
+ if attr.type.equal?(default_types[attr.name])
+ attr.with_type(nil)
+ else
+ attr
+ end
+ end
+ end
+
+ def decode(coder)
+ if coder['attributes']
+ coder['attributes']
+ else
+ attributes_hash = Hash[coder['concise_attributes'].map do |attr|
+ if attr.type.nil?
+ attr = attr.with_type(default_types[attr.name])
+ end
+ [attr.name, attr]
+ end]
+ AttributeSet.new(attributes_hash)
+ end
+ end
+
+ protected
+
+ attr_reader :default_types
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 5d0405c3be..519de271c3 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -67,12 +67,14 @@ module ActiveRecord
#
# A default can also be provided.
#
+ # # db/schema.rb
# create_table :store_listings, force: true do |t|
# t.string :my_string, default: "original default"
# end
#
# StoreListing.new.my_string # => "original default"
#
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :my_string, :string, default: "new default"
# end
@@ -89,6 +91,7 @@ module ActiveRecord
#
# \Attributes do not need to be backed by a database column.
#
+ # # app/models/my_model.rb
# class MyModel < ActiveRecord::Base
# attribute :my_string, :string
# attribute :my_int_array, :integer, array: true
@@ -119,7 +122,7 @@ module ActiveRecord
#
# class MoneyType < ActiveRecord::Type::Integer
# def cast(value)
- # if !value.kind_of(Numeric) && value.include?('$')
+ # if !value.kind_of?(Numeric) && value.include?('$')
# price_in_dollars = value.gsub(/\$/, '').to_f
# super(price_in_dollars * 100)
# else
@@ -131,7 +134,7 @@ module ActiveRecord
# # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
- # # /app/models/store_listing.rb
+ # # app/models/store_listing.rb
# class StoreListing < ActiveRecord::Base
# attribute :price_in_cents, :money
# end
@@ -154,7 +157,7 @@ module ActiveRecord
# end
#
# class MoneyType < Type::Value
- # def initialize(currency_converter)
+ # def initialize(currency_converter:)
# @currency_converter = currency_converter
# end
#
@@ -167,11 +170,13 @@ module ActiveRecord
# end
# end
#
+ # # config/initializers/types.rb
# ActiveRecord::Type.register(:money, MoneyType)
#
+ # # app/models/product.rb
# class Product < ActiveRecord::Base
# currency_converter = ConversionRatesFromTheInternet.new
- # attribute :price_in_bitcoins, :money, currency_converter
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
# end
#
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index bac5a38a5d..06c7482bf9 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
#
# == Validation
#
- # Children records are validated unless <tt>:validate</tt> is +false+.
+ # Child records are validated unless <tt>:validate</tt> is +false+.
#
# == Callbacks
#
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 4a31a1aa84..6a1a27ce41 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -13,7 +13,6 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/class/subclasses'
-require 'arel'
require 'active_record/attribute_decorators'
require 'active_record/errors'
require 'active_record/log_subscriber'
@@ -132,9 +131,6 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
- # or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
- #
# == Attribute query methods
#
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
@@ -173,7 +169,8 @@ module ActiveRecord #:nodoc:
# ActiveRecord::RecordNotFound error if they do not return any records,
# like <tt>Person.find_by_last_name!</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
+ # "_and_".
#
# Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 854f9776a3..95de6937af 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -53,9 +53,9 @@ module ActiveRecord
# end
#
# class Firm < ActiveRecord::Base
- # # Destroys the associated clients and people when the firm is destroyed
- # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
- # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
# end
#
# == Inheritable callback queues
@@ -179,7 +179,7 @@ module ActiveRecord
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
- # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise a ActiveRecord::RecordInvalid exception.
+ # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
diff --git a/activerecord/lib/active_record/coders/json.rb b/activerecord/lib/active_record/coders/json.rb
index 75d3bfe625..cb185a881e 100644
--- a/activerecord/lib/active_record/coders/json.rb
+++ b/activerecord/lib/active_record/coders/json.rb
@@ -6,7 +6,7 @@ module ActiveRecord
end
def self.load(json)
- ActiveSupport::JSON.decode(json) unless json.nil?
+ ActiveSupport::JSON.decode(json) unless json.blank?
end
end
end
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 5dcc98424a..8d41c0d799 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -16,7 +16,7 @@ module ActiveRecord
query = collection
.unscope(:select)
- .select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
+ .select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
.unscope(:order)
result = connection.select_one(query)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index ccd2899489..c341773be1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -298,7 +298,7 @@ module ActiveRecord
def run
return unless frequency
Thread.new(frequency, pool) { |t, p|
- while true
+ loop do
sleep t
p.reap
end
@@ -618,7 +618,7 @@ module ActiveRecord
timeout_time = Time.now + (@checkout_timeout * 2)
@available.with_a_bias_for(Thread.current) do
- while true
+ loop do
synchronize do
return if collected_conns.size == @connections.size && @now_connecting == 0
remaining_timeout = timeout_time - Time.now
@@ -778,8 +778,7 @@ module ActiveRecord
end
# ConnectionHandler is a collection of ConnectionPool objects. It is used
- # for keeping separate connection pools for Active Record models that connect
- # to different databases.
+ # for keeping separate connection pools that connect to different databases.
#
# For example, suppose that you have 5 models, with the following hierarchy:
#
@@ -821,17 +820,16 @@ module ActiveRecord
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
# All Active Record models use this handler to determine the connection pool that they
# should use.
+ #
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
+ # about the model. The model, needs to pass a specification name to the handler,
+ # in order to lookup the correct connection pool.
class ConnectionHandler
def initialize
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
- # alone would lead to memory leaks in development mode as all previous
- # instances of the class would stay in memory.
+ # These caches are keyed by spec.name (ConnectionSpecification#name).
@owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
h[k] = Concurrent::Map.new(:initial_capacity => 2)
end
- @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
- h[k] = Concurrent::Map.new
- end
end
def connection_pool_list
@@ -839,10 +837,12 @@ module ActiveRecord
end
alias :connection_pools :connection_pool_list
- def establish_connection(owner, spec)
- @class_to_pool.clear
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
+ def establish_connection(config)
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.spec(config)
+
+ remove_connection(spec.name)
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
end
# Returns true if there are any active connections among the connection
@@ -873,18 +873,18 @@ module ActiveRecord
# active or defined connection: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
# for (not necessarily the current class).
- def retrieve_connection(klass) #:nodoc:
- pool = retrieve_connection_pool(klass)
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ def retrieve_connection(spec_name) #:nodoc:
+ pool = retrieve_connection_pool(spec_name)
+ raise ConnectionNotEstablished, "No connection pool with id '#{spec_name}' found." unless pool
conn = pool.connection
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
conn
end
# Returns true if a connection that's accessible to this class has
# already been opened.
- def connected?(klass)
- conn = retrieve_connection_pool(klass)
+ def connected?(spec_name)
+ conn = retrieve_connection_pool(spec_name)
conn && conn.connected?
end
@@ -892,82 +892,43 @@ module ActiveRecord
# connection and the defined connection (if they exist). The result
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
- def remove_connection(owner)
- if pool = owner_to_pool.delete(owner.name)
- @class_to_pool.clear
+ def remove_connection(spec_name)
+ if pool = owner_to_pool.delete(spec_name)
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
end
end
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
# This makes retrieving the connection pool O(1) once the process is warm.
# When a connection is established or removed, we invalidate the cache.
- #
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
- # take place, but that's ok since the nil case is not the common one that we wish
- # to optimise for.
- def retrieve_connection_pool(klass)
- class_to_pool[klass.name] ||= begin
- until pool = pool_for(klass)
- klass = klass.superclass
- break unless klass <= Base
- end
-
- class_to_pool[klass.name] = pool
- end
- end
-
- private
-
- def owner_to_pool
- @owner_to_pool[Process.pid]
- end
-
- def class_to_pool
- @class_to_pool[Process.pid]
- end
-
- def pool_for(owner)
- owner_to_pool.fetch(owner.name) {
- if ancestor_pool = pool_from_any_process_for(owner)
+ def retrieve_connection_pool(spec_name)
+ owner_to_pool.fetch(spec_name) do
+ # Check if a connection was previously established in an ancestor process,
+ # which may have been forked.
+ if ancestor_pool = pool_from_any_process_for(spec_name)
# A connection was established in an ancestor process that must have
# subsequently forked. We can't reuse the connection, but we can copy
# the specification and establish a new connection with it.
- establish_connection(owner, ancestor_pool.spec).tap do |pool|
+ establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
end
else
- owner_to_pool[owner.name] = nil
+ owner_to_pool[spec_name] = nil
end
- }
+ end
end
- def pool_from_any_process_for(owner)
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
- owner_to_pool && owner_to_pool[owner.name]
- end
- end
+ private
- class ConnectionManagement
- def initialize(app)
- @app = app
+ def owner_to_pool
+ @owner_to_pool[Process.pid]
end
- def call(env)
- testing = env['rack.test']
-
- status, headers, body = @app.call(env)
- proxy = ::Rack::BodyProxy.new(body) do
- ActiveRecord::Base.clear_active_connections! unless testing
- end
- [status, headers, proxy]
- rescue Exception
- ActiveRecord::Base.clear_active_connections! unless testing
- raise
+ def pool_from_any_process_for(spec_name)
+ owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
+ owner_to_pool && owner_to_pool[spec_name]
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 7a2a1a0e33..507a925d32 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -27,10 +27,10 @@ module ActiveRecord
end
# Returns an ActiveRecord::Result instance.
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- if arel.is_a?(String)
+ if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
else
preparable = visitor.preparable
@@ -66,18 +66,23 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
+ exec_query(sql, name, binds).rows
end
- undef_method :select_rows
- # Executes the SQL statement in the context of this connection.
+ # Executes the SQL statement in the context of this connection and returns
+ # the raw result from the connection adapter.
+ # Note: depending on your database connector, the result returned by this
+ # method may be manually memory managed. Consider using the exec_query
+ # wrapper instead.
def execute(sql, name = nil)
+ raise NotImplementedError
end
- undef_method :execute
# Executes +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ raise NotImplementedError
end
# Executes insert +sql+ statement in the context of this connection using
@@ -121,18 +126,21 @@ module ActiveRecord
end
alias create insert
alias insert_sql insert
+ deprecate insert_sql: :insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
alias update_sql update
+ deprecate update_sql: :update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
alias delete_sql delete
+ deprecate delete_sql: :delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
@@ -213,9 +221,7 @@ module ActiveRecord
# * You are creating a nested (savepoint) transaction
#
# The mysql2 and postgresql adapters support setting the transaction
- # isolation level. However, support is disabled for MySQL versions below 5,
- # because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
- # which means the isolation level gets persisted outside the transaction.
+ # isolation level.
def transaction(requires_new: nil, isolation: nil, joinable: true)
if !requires_new && current_transaction.joinable?
if isolation
@@ -285,9 +291,6 @@ module ActiveRecord
exec_rollback_to_savepoint(name)
end
- def exec_rollback_to_savepoint(name = nil) #:nodoc:
- end
-
def default_sequence_name(table, column)
nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 5e27cfe507..0bdfd4f900 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -61,11 +61,11 @@ module ActiveRecord
@query_cache.clear
end
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds) }
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7e3760d34b..860ef17dca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# Quotes the column name. Defaults to no quoting.
def quote_column_name(column_name)
- column_name
+ column_name.to_s
end
# Quotes the table name. Defaults to column name quoting.
@@ -146,6 +146,10 @@ module ActiveRecord
end
end
+ def quoted_time(value) # :nodoc:
+ quoted_date(value).sub(/\A2000-01-01 /, '')
+ end
+
def prepare_binds_for_database(binds) # :nodoc:
binds.map(&:value_for_database)
end
@@ -166,6 +170,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
+ when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
when *types_which_need_no_typecasting
value
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index c0662f8473..3a06f75292 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -1,8 +1,8 @@
module ActiveRecord
module ConnectionAdapters
- module Savepoints #:nodoc:
- def supports_savepoints?
- true
+ module Savepoints
+ def current_savepoint_name
+ current_transaction.savepoint_name
end
def create_savepoint(name = current_savepoint_name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 0ba4d94e3c..6add697eeb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -53,8 +53,8 @@ module ActiveRecord
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
- create_sql << "(#{statements.join(', ')}) " if statements.present?
- create_sql << "#{o.options}"
+ create_sql << "(#{statements.join(', ')})" if statements.present?
+ add_table_options!(create_sql, table_options(o))
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
create_sql
end
@@ -82,6 +82,19 @@ module ActiveRecord
"DROP CONSTRAINT #{quote_column_name(name)}"
end
+ def table_options(o)
+ table_options = {}
+ table_options[:comment] = o.comment
+ table_options[:options] = o.options
+ table_options
+ end
+
+ def add_table_options!(create_sql, options)
+ if options_sql = options[:options]
+ create_sql << " #{options_sql}"
+ end
+ end
+
def column_options(o)
column_options = {}
column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +105,7 @@ module ActiveRecord
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
column_options[:collation] = o.collation
+ column_options[:comment] = o.comment
column_options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 690e0ba957..8dbafc5a4b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,14 +3,14 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -51,11 +51,12 @@ module ActiveRecord
options[:primary_key] != default_primary_key
end
- def defined_for?(options_or_to_table = {})
- if options_or_to_table.is_a?(Hash)
- options_or_to_table.all? {|key, value| options[key].to_s == value.to_s }
+ def defined_for?(to_table_ord = nil, to_table: nil, **options)
+ if to_table_ord
+ self.to_table == to_table_ord.to_s
else
- to_table == options_or_to_table.to_s
+ (to_table.nil? || to_table.to_s == self.to_table) &&
+ options.all? { |k, v| self.options[k].to_s == v.to_s }
end
end
@@ -182,6 +183,7 @@ module ActiveRecord
end
CODE
end
+ alias_method :numeric, :decimal
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -206,17 +208,18 @@ module ActiveRecord
include ColumnMethods
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
- def initialize(name, temporary, options, as = nil)
+ def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
@columns_hash = {}
@indexes = {}
- @foreign_keys = {}
+ @foreign_keys = []
@primary_keys = nil
@temporary = temporary
@options = options
@as = as
@name = name
+ @comment = comment
end
def primary_keys(name = nil) # :nodoc:
@@ -329,7 +332,10 @@ module ActiveRecord
end
def foreign_key(table_name, options = {}) # :nodoc:
- foreign_keys[table_name] = options
+ table_name_prefix = ActiveRecord::Base.table_name_prefix
+ table_name_suffix = ActiveRecord::Base.table_name_suffix
+ table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
+ foreign_keys.push([table_name, options])
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
@@ -372,6 +378,7 @@ module ActiveRecord
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column.collation = options[:collation]
+ column.comment = options[:comment]
column
end
@@ -436,6 +443,7 @@ module ActiveRecord
# t.bigint
# t.float
# t.decimal
+ # t.numeric
# t.datetime
# t.timestamp
# t.time
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index a95109fdae..677a4c6bd0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,15 +7,16 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- spec = prepare_column_options(column)
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
+ spec = Hash[prepare_column_options(column).map { |k, v| [k, "#{k}: #{v}"] }]
+ spec[:name] = column.name.inspect
+ spec[:type] = schema_type(column).to_s
spec
end
def column_spec_for_primary_key(column)
- return if column.type == :integer
- spec = { id: column.type.inspect }
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
+ return {} if default_primary_key?(column)
+ spec = { id: schema_type(column).inspect }
+ spec.merge!(prepare_column_options(column).except!(:null))
end
# This can be overridden on an Adapter level basis to support other
@@ -23,9 +24,6 @@ module ActiveRecord
# PostgreSQL::ColumnDumper)
def prepare_column_options(column)
spec = {}
- spec[:name] = column.name.inspect
- spec[:type] = schema_type(column)
- spec[:null] = 'false' unless column.null
if limit = schema_limit(column)
spec[:limit] = limit
@@ -42,26 +40,38 @@ module ActiveRecord
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ spec[:null] = 'false' unless column.null
+
if collation = schema_collation(column)
spec[:collation] = collation
end
+ spec[:comment] = column.comment.inspect if column.comment.present?
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation]
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
+ def default_primary_key?(column)
+ schema_type(column) == :integer
+ end
+
def schema_type(column)
- column.type.to_s
+ if column.bigint?
+ :bigint
+ else
+ column.type
+ end
end
def schema_limit(column)
- limit = column.limit
+ limit = column.limit unless column.bigint?
limit.inspect if limit && limit != native_database_types[column.type][:limit]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 002f2ea8ce..eec0bc8518 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -18,6 +18,11 @@ module ActiveRecord
nil
end
+ # Returns the table comment that's stored in database metadata.
+ def table_comment(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -92,7 +97,9 @@ module ActiveRecord
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
- def columns(table_name) end
+ def columns(table_name)
+ raise NotImplementedError, "#columns is not implemented"
+ end
# Checks to see if a column exists in a given table.
#
@@ -110,18 +117,25 @@ module ActiveRecord
#
def column_exists?(table_name, column_name, type = nil, options = {})
column_name = column_name.to_s
- columns(table_name).any?{ |c| c.name == column_name &&
- (!type || c.type == type) &&
- (!options.key?(:limit) || c.limit == options[:limit]) &&
- (!options.key?(:precision) || c.precision == options[:precision]) &&
- (!options.key?(:scale) || c.scale == options[:scale]) &&
- (!options.key?(:default) || c.default == options[:default]) &&
- (!options.key?(:null) || c.null == options[:null]) }
+ checks = []
+ checks << lambda { |c| c.name == column_name }
+ checks << lambda { |c| c.type == type } if type
+ (migration_keys - [:name]).each do |attr|
+ checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
+ end
+
+ columns(table_name).any? { |c| checks.all? { |check| check[c] } }
end
# Returns just a table's primary key
def primary_key(table_name)
pks = primary_keys(table_name)
+ warn <<-WARNING.strip_heredoc if pks.count > 1
+ WARNING: Rails does not support composite primary key.
+
+ #{table_name} has composite primary key. Composite primary key is ignored.
+ WARNING
+
pks.first if pks.one?
end
@@ -245,8 +259,8 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
+ def create_table(table_name, comment: nil, **options)
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -274,6 +288,14 @@ module ActiveRecord
end
end
+ if supports_comments? && !supports_comments_in_create?
+ change_table_comment(table_name, comment) if comment
+
+ td.columns.each do |column|
+ change_column_comment(table_name, column.name, column.comment) if column.comment
+ end
+ end
+
result
end
@@ -320,12 +342,13 @@ module ActiveRecord
column_options = options.delete(:column_options) || {}
column_options.reverse_merge!(null: false)
+ type = column_options.delete(:type) || :integer
t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
create_table(join_table_name, options.merge!(id: false)) do |td|
- td.integer t1_column, column_options
- td.integer t2_column, column_options
+ td.send type, t1_column, column_options
+ td.send type, t2_column, column_options
yield td if block_given?
end
end
@@ -450,7 +473,7 @@ module ActiveRecord
# The +type+ parameter is normally one of the migrations native types,
# which is one of the following:
# <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
# <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
# <tt>:binary</tt>, <tt>:boolean</tt>.
#
@@ -468,9 +491,9 @@ module ActiveRecord
# Allows or disallows +NULL+ values in the column. This option could
# have been named <tt>:null_allowed</tt>.
# * <tt>:precision</tt> -
- # Specifies the precision for a <tt>:decimal</tt> column.
+ # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
- # Specifies the scale for a <tt>:decimal</tt> column.
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
#
# Note: The precision is the total number of significant digits
# and the scale is the number of digits that can be stored following
@@ -487,8 +510,6 @@ module ActiveRecord
# Default is (10,0).
# * PostgreSQL: <tt>:precision</tt> [1..infinity],
# <tt>:scale</tt> [0..infinity]. No default.
- # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
- # Internal storage as strings. No default.
# * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
# but the maximum supported <tt>:precision</tt> is 16. No default.
# * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
@@ -691,7 +712,7 @@ module ActiveRecord
#
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
+ # Note: only supported by MySQL.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -770,6 +791,7 @@ module ActiveRecord
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
# Add an appropriate index. Defaults to false.
+ # See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
# [<tt>:polymorphic</tt>]
@@ -789,6 +811,14 @@ module ActiveRecord
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
+ # ====== Create a supplier_id column with a unique index
+ #
+ # add_reference(:products, :supplier, index: { unique: true })
+ #
+ # ====== Create a supplier_id column with a named index
+ #
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
+ #
# ====== Create a supplier_id column and appropriate foreign key
#
# add_reference(:products, :supplier, foreign_key: true)
@@ -817,14 +847,19 @@ module ActiveRecord
#
# remove_reference(:products, :user, index: true, foreign_key: true)
#
- def remove_reference(table_name, ref_name, options = {})
- if options[:foreign_key]
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
+ if foreign_key
reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
- remove_foreign_key(table_name, reference_name)
+ if foreign_key.is_a?(Hash)
+ foreign_key_options = foreign_key
+ else
+ foreign_key_options = { to_table: reference_name }
+ end
+ remove_foreign_key(table_name, **foreign_key_options)
end
remove_column(table_name, "#{ref_name}_id")
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
end
alias :remove_belongs_to :remove_reference
@@ -847,7 +882,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
#
# ====== Creating a foreign key on a specific column
#
@@ -863,7 +898,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
#
# The +options+ hash can include the following keys:
# [<tt>:column</tt>]
@@ -955,11 +990,23 @@ module ActiveRecord
end
def dump_schema_information #:nodoc:
+ versions = ActiveRecord::SchemaMigration.order('version').pluck(:version)
+ insert_versions_sql(versions)
+ end
+
+ def insert_versions_sql(versions) # :nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- sql = "INSERT INTO #{sm_table} (version) VALUES "
- sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ')
- sql << ";\n\n"
+ if supports_multi_insert?
+ sql = "INSERT INTO #{sm_table} (version) VALUES "
+ sql << versions.map {|v| "('#{v}')" }.join(', ')
+ sql << ";\n\n"
+ sql
+ else
+ versions.map { |version|
+ "INSERT INTO #{sm_table} (version) VALUES ('#{version}');"
+ }.join "\n\n"
+ end
end
# Should not be called normally, but this operation is non-destructive.
@@ -972,6 +1019,10 @@ module ActiveRecord
ActiveRecord::InternalMetadata.create_table
end
+ def internal_string_options_for_primary_key # :nodoc:
+ { primary_key: true }
+ end
+
def assume_migrated_upto_version(version, migrations_paths)
migrations_paths = Array(migrations_paths)
version = version.to_i
@@ -992,7 +1043,7 @@ module ActiveRecord
if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
end
- execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| '(#{v})'}.join(', ') }"
+ execute insert_versions_sql(inserting)
end
end
@@ -1040,9 +1091,9 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
+ # Additional options (like +:null+) are forwarded to #add_column.
#
- # add_timestamps(:suppliers, null: false)
+ # add_timestamps(:suppliers, null: true)
#
def add_timestamps(table_name, options = {})
options[:null] = false if options[:null].nil?
@@ -1064,16 +1115,19 @@ module ActiveRecord
Table.new(table_name, base)
end
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
- column_names = Array(column_name)
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
+ if column_name.is_a?(String) && /\W/ === column_name
+ column_names = column_name
+ else
+ column_names = Array(column_name)
+ end
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
- index_name ||= index_name(table_name, column: column_names)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ index_name ||= index_name(table_name, index_name_options(column_names))
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1087,21 +1141,30 @@ module ActiveRecord
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
- if index_name.length > max_index_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
- end
+ validate_index_length!(table_name, index_name, options.fetch(:internal, false))
+
if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options, algorithm, using]
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
+ # Changes the comment for a table or removes it if +nil+.
+ def change_table_comment(table_name, comment)
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
+ end
+
+ # Changes the comment for a column or removes it if +nil+.
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1118,6 +1181,8 @@ module ActiveRecord
# Overridden by the MySQL adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
+ return [column_names] if column_names.is_a?(String)
+
option_strings = Hash[column_names.map {|name| [name, '']}]
# add index sort order if supported
@@ -1129,6 +1194,8 @@ module ActiveRecord
end
def index_name_for_remove(table_name, options = {})
+ return options[:name] if can_remove_index_by_name?(options)
+
# if the adapter doesn't support the indexes call the best we can do
# is return the default index name for the options provided
return index_name(table_name, options) unless respond_to?(:indexes)
@@ -1136,7 +1203,7 @@ module ActiveRecord
checks = []
if options.is_a?(Hash)
- checks << lambda { |i| i.name == options[:name].to_s } if options.has_key?(:name)
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
column_names = Array(options[:column]).map(&:to_s)
else
column_names = Array(options).map(&:to_s)
@@ -1183,14 +1250,22 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary = false, options = nil, as = nil)
- TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args)
+ TableDefinition.new(*args)
end
def create_alter_table(name)
AlterTable.new create_table_definition(name)
end
+ def index_name_options(column_names) # :nodoc:
+ if column_names.is_a?(String)
+ column_names = column_names.scan(/\w+/).join('_')
+ end
+
+ { column: column_names }
+ end
+
def foreign_key_name(table_name, options) # :nodoc:
identifier = "#{table_name}_#{options.fetch(:column)}_fk"
hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
@@ -1199,8 +1274,10 @@ module ActiveRecord
end
end
- def validate_index_length!(table_name, new_name) # :nodoc:
- if new_name.length > allowed_index_name_length
+ def validate_index_length!(table_name, new_name, internal = false) # :nodoc:
+ max_index_length = internal ? index_name_length : allowed_index_name_length
+
+ if new_name.length > max_index_length
raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
end
end
@@ -1212,6 +1289,10 @@ module ActiveRecord
default_or_changes
end
end
+
+ def can_remove_index_by_name?(options)
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 14d04a6388..ca795cb1ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -167,8 +167,13 @@ module ActiveRecord
def commit_transaction
transaction = @stack.last
- transaction.before_commit_records
- @stack.pop
+
+ begin
+ transaction.before_commit_records
+ ensure
+ @stack.pop
+ end
+
transaction.commit
transaction.commit_records
end
@@ -183,7 +188,10 @@ module ActiveRecord
transaction = begin_transaction options
yield
rescue Exception => error
- rollback_transaction if transaction
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
raise
ensure
unless error
@@ -209,7 +217,16 @@ module ActiveRecord
end
private
+
NULL_TRANSACTION = NullTransaction.new
+
+ # Deallocate invalidated prepared statements outside of the transaction
+ def after_failure_actions(transaction, error)
+ return unless transaction.is_a?(RealTransaction)
+ return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
+ @connection.clear_cache!
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d9b42d4283..d4b9e301bc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,4 @@
require 'active_record/type'
-require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/determine_if_preparable_visitor'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
@@ -28,7 +27,6 @@ module ActiveRecord
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
autoload :ConnectionHandler
- autoload :ConnectionManagement
end
autoload_under 'abstract' do
@@ -69,6 +67,7 @@ module ActiveRecord
include QueryCache
include ActiveSupport::Callbacks
include ColumnDumper
+ include Savepoints
SIMPLE_INT = /\A\d+\z/
@@ -106,8 +105,15 @@ module ActiveRecord
@config = config
@pool = nil
@schema_cache = SchemaCache.new self
- @visitor = nil
- @prepared_statements = false
+ @quoted_column_names, @quoted_table_names = {}, {}
+ @visitor = arel_visitor
+
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
+ @prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
+ else
+ @prepared_statements = false
+ end
end
class Version
@@ -143,8 +149,12 @@ module ActiveRecord
end
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::ToSql.new(self)
+ end
+
def valid_type?(type)
- true
+ false
end
def schema_creation
@@ -238,6 +248,11 @@ module ActiveRecord
false
end
+ # Does this adapter support expression indices?
+ def supports_expression_index?
+ false
+ end
+
# Does this adapter support explain?
def supports_explain?
false
@@ -279,6 +294,21 @@ module ActiveRecord
false
end
+ # Does this adapter support metadata comments on database objects (tables, columns, indexes)?
+ def supports_comments?
+ false
+ end
+
+ # Can comments for tables, columns, and indexes be specified in create/alter table statements?
+ def supports_comments_in_create?
+ false
+ end
+
+ # Does this adapter support multi-value insert?
+ def supports_multi_insert?
+ true
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -380,12 +410,6 @@ module ActiveRecord
@connection
end
- def create_savepoint(name = nil)
- end
-
- def release_savepoint(name = nil)
- end
-
def case_sensitive_comparison(table, attribute, column, value)
if value.nil?
table[attribute].eq(value)
@@ -398,7 +422,7 @@ module ActiveRecord
if can_perform_case_insensitive_comparison_for?(column)
table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
else
- case_sensitive_comparison(table, attribute, column, value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
@@ -407,10 +431,6 @@ module ActiveRecord
end
private :can_perform_case_insensitive_comparison_for?
- def current_savepoint_name
- current_transaction.savepoint_name
- end
-
# Check the connection back in to the connection pool
def close
pool.checkin self
@@ -422,8 +442,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
- Column.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
+ Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
@@ -434,6 +454,24 @@ module ActiveRecord
visitor.accept(node, collector).value
end
+ def combine_bind_parameters(
+ from_clause: [],
+ join_clause: [],
+ where_clause: [],
+ having_clause: [],
+ limit: nil,
+ offset: nil
+ ) # :nodoc:
+ result = from_clause + join_clause + where_clause + having_clause
+ if limit
+ result << limit
+ end
+ if offset
+ result << offset
+ end
+ result
+ end
+
protected
def initialize_type_map(m) # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 3e84786be0..718a6c5b91 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,5 +1,8 @@
require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
require 'active_record/connection_adapters/mysql/column'
+require 'active_record/connection_adapters/mysql/explain_pretty_printer'
+require 'active_record/connection_adapters/mysql/quoting'
require 'active_record/connection_adapters/mysql/schema_creation'
require 'active_record/connection_adapters/mysql/schema_definitions'
require 'active_record/connection_adapters/mysql/schema_dumper'
@@ -10,17 +13,21 @@ require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ include MySQL::Quoting
include MySQL::ColumnDumper
- include Savepoints
def update_table_definition(table_name, base) # :nodoc:
MySQL::Table.new(table_name, base)
end
- def schema_creation
+ def schema_creation # :nodoc:
MySQL::SchemaCreation.new(self)
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::MySQL.new(self)
+ end
+
##
# :singleton-method:
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
@@ -31,25 +38,17 @@ module ActiveRecord
class_attribute :emulate_booleans
self.emulate_booleans = true
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
NATIVE_DATABASE_TYPES = {
primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
- text: { name: "text" },
+ text: { name: "text", limit: 65535 },
integer: { name: "int", limit: 4 },
float: { name: "float" },
decimal: { name: "decimal" },
datetime: { name: "datetime" },
time: { name: "time" },
date: { name: "date" },
- binary: { name: "blob" },
+ binary: { name: "blob", limit: 65535 },
boolean: { name: "tinyint", limit: 1 },
json: { name: "json" },
}
@@ -57,35 +56,38 @@ module ActiveRecord
INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]
- # FIXME: Make the first parameter more similar for the two adapters
+ class StatementPool < ConnectionAdapters::StatementPool
+ private def dealloc(stmt)
+ stmt[:stmt].close
+ end
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @quoted_column_names, @quoted_table_names = {}, {}
- @visitor = Arel::Visitors::MySQL.new self
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
+ if version < '5.0.0'
+ raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
end
end
- MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
- def initialize_schema_migrations_table
- if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
- ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
- else
- ActiveRecord::SchemaMigration.create_table
- end
+
+ def internal_string_options_for_primary_key # :nodoc:
+ super.tap { |options|
+ options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
+ }
end
- def version
+ def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
+ def mariadb? # :nodoc:
+ full_version =~ /mariadb/i
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -99,18 +101,20 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
+ def supports_statement_cache?
+ true
+ end
+
# Technically MySQL allows to create indexes with the sort order syntax
# but at the moment (5.5) it doesn't yet implement them
def supports_index_sort_order?
true
end
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
- # where the transaction level gets persisted for the whole session:
- #
- # http://bugs.mysql.com/bug.php?id=39170
def supports_transaction_isolation?
- version >= '5.0.0'
+ true
end
def supports_explain?
@@ -126,25 +130,27 @@ module ActiveRecord
end
def supports_views?
- version >= '5.0.0'
+ true
end
def supports_datetime_with_precision?
- version >= '5.6.4'
+ if mariadb?
+ version >= '5.3.0'
+ else
+ version >= '5.6.4'
+ end
end
- # 5.0.0 definitely supports it, possibly supported by earlier versions but
- # not sure
def supports_advisory_locks?
- version >= '5.0.0'
+ true
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
+ select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
+ select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
end
def native_database_types
@@ -163,8 +169,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) #:nodoc:
+ MySQL::Column.new(*args)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -173,48 +179,6 @@ module ActiveRecord
raise NotImplementedError
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
- def quoted_true
- QUOTED_TRUE
- end
-
- def unquoted_true
- 1
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- def unquoted_false
- 0
- end
-
- def quoted_date(value)
- if supports_datetime_with_precision?
- super
- else
- super.sub(/\.\d{6}\z/, '')
- end
- end
-
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -228,6 +192,14 @@ module ActiveRecord
end
end
+ # CONNECTION MANAGEMENT ====================================
+
+ # Clears the prepared statements cache.
+ def clear_cache!
+ reload_type_map
+ @statements.clear
+ end
+
#--
# DATABASE STATEMENTS ======================================
#++
@@ -238,77 +210,7 @@ module ActiveRecord
result = exec_query(sql, 'EXPLAIN', binds)
elapsed = Time.now - start
- ExplainPrettyPrinter.new.pp(result, elapsed)
- end
-
- class ExplainPrettyPrinter # :nodoc:
- # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
- # MySQL shell:
- #
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
- # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
- # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
- # 2 rows in set (0.00 sec)
- #
- # This is an exercise in Ruby hyperrealism :).
- def pp(result, elapsed)
- widths = compute_column_widths(result)
- separator = build_separator(widths)
-
- pp = []
-
- pp << separator
- pp << build_cells(result.columns, widths)
- pp << separator
-
- result.rows.each do |row|
- pp << build_cells(row, widths)
- end
-
- pp << separator
- pp << build_footer(result.rows.length, elapsed)
-
- pp.join("\n") + "\n"
- end
-
- private
-
- def compute_column_widths(result)
- [].tap do |widths|
- result.columns.each_with_index do |column, i|
- cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
- widths << cells_in_column.map(&:length).max
- end
- end
- end
-
- def build_separator(widths)
- padding = 1
- '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
- end
-
- def build_cells(items, widths)
- cells = []
- items.each_with_index do |item, i|
- item = 'NULL' if item.nil?
- justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
- cells << item.to_s.send(justifier, widths[i])
- end
- '| ' + cells.join(' | ') + ' |'
- end
-
- def build_footer(nrows, elapsed)
- rows_label = nrows == 1 ? 'row' : 'rows'
- "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
- end
- end
-
- def clear_cache!
- super
- reload_type_map
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
end
# Executes the SQL statement in the context of this connection.
@@ -376,9 +278,9 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
end
@@ -387,7 +289,7 @@ module ActiveRecord
# Example:
# drop_database('sebastian_development')
def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
def current_database
@@ -445,8 +347,7 @@ module ActiveRecord
def data_source_exists?(table_name)
return false unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ schema, name = extract_schema_qualified_name(table_name)
sql = "SELECT table_name FROM information_schema.tables "
sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
@@ -461,8 +362,7 @@ module ActiveRecord
def view_exists?(view_name) # :nodoc:
return false unless view_name.present?
- schema, name = view_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A view was provided without a schema
+ schema, name = extract_schema_qualified_name(view_name)
sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
@@ -483,7 +383,7 @@ module ActiveRecord
mysql_index_type = row[:Index_type].downcase.to_sym
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
end
indexes.last.columns << row[:Column_name]
@@ -496,21 +396,28 @@ module ActiveRecord
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) # :nodoc:
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
- execute_and_free(sql, 'SCHEMA') do |result|
- each_hash(result).map do |field|
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
- else
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
- end
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |field|
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ default, default_function = nil, field[:Default]
+ else
+ default, default_function = field[:Default], nil
end
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
end
end
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ def table_comment(table_name) # :nodoc:
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT table_comment
+ FROM information_schema.tables
+ WHERE table_name=#{quote(table_name)}
+ SQL
+ end
+
+ def create_table(table_name, **options) #:nodoc:
+ super(table_name, options: 'ENGINE=InnoDB', **options)
end
def bulk_change_table(table_name, operations) #:nodoc:
@@ -593,11 +500,21 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ execute add_sql_comment!(sql, comment)
+ end
+
+ def add_sql_comment!(sql, comment) # :nodoc:
+ sql << " COMMENT #{quote(comment)}" if comment
+ sql
end
def foreign_keys(table_name)
+ raise ArgumentError unless table_name.present?
+
+ schema, name = extract_schema_qualified_name(table_name)
+
fk_info = select_all <<-SQL.strip_heredoc
SELECT fk.referenced_table_name as 'to_table'
,fk.referenced_column_name as 'primary_key'
@@ -605,8 +522,8 @@ module ActiveRecord
,fk.constraint_name as 'name'
FROM information_schema.key_column_usage fk
WHERE fk.referenced_column_name is not null
- AND fk.table_schema = '#{@config[:database]}'
- AND fk.table_name = '#{table_name}'
+ AND fk.table_schema = #{quote(schema)}
+ AND fk.table_name = #{quote(name)}
SQL
create_table_info = create_table_info(table_name)
@@ -632,7 +549,12 @@ module ActiveRecord
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
# strip AUTO_INCREMENT
- raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+
+ # strip COMMENT
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
+
+ raw_table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -660,8 +582,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
- variables.first['Value'] unless variables.empty?
+ select_value("SELECT @@#{name}", 'SCHEMA')
rescue ActiveRecord::StatementInvalid
nil
end
@@ -669,8 +590,7 @@ module ActiveRecord
def primary_keys(table_name) # :nodoc:
raise ArgumentError unless table_name.present?
- schema, name = table_name.to_s.split('.', 2)
- schema, name = @config[:database], schema unless name # A table was provided without a schema
+ schema, name = extract_schema_qualified_name(table_name)
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
SELECT column_name
@@ -683,20 +603,17 @@ module ActiveRecord
end
def case_sensitive_comparison(table, attribute, column, value)
- if value.nil? || column.case_sensitive?
- super
- else
+ if !value.nil? && column.collation && !column.case_sensitive?
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
+ else
+ super
end
end
- def case_insensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- super
- else
- table[attribute].eq(Arel::Nodes::BindParam.new)
- end
+ def can_perform_case_insensitive_comparison_for?(column)
+ column.case_sensitive?
end
+ private :can_perform_case_insensitive_comparison_for?
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -746,7 +663,7 @@ module ActiveRecord
register_integer_type m, %r(^smallint)i, limit: 2
register_integer_type m, %r(^tinyint)i, limit: 1
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
@@ -790,7 +707,7 @@ module ActiveRecord
case length
when Hash
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
- when Fixnum
+ when Integer
column_names.each {|name| option_strings[name] += "(#{length})"}
end
end
@@ -810,12 +727,22 @@ module ActiveRecord
column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
+ # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
+ ER_DUP_ENTRY = 1062
+ ER_NO_REFERENCED_ROW_2 = 1452
+ ER_DATA_TOO_LONG = 1406
+ ER_LOCK_DEADLOCK = 1213
+
def translate_exception(exception, message)
case error_number(exception)
- when 1062
+ when ER_DUP_ENTRY
RecordNotUnique.new(message)
- when 1452
+ when ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message)
+ when ER_DATA_TOO_LONG
+ ValueTooLong.new(message)
+ when ER_LOCK_DEADLOCK
+ TransactionSerializationError.new(message)
else
super
end
@@ -901,10 +828,6 @@ module ActiveRecord
subselect.from subsubselect.as('__active_record_temp')
end
- def mariadb?
- full_version =~ /mariadb/i
- end
-
def supports_rename_index?
mariadb? ? false : version >= '5.7.6'
end
@@ -917,7 +840,7 @@ module ActiveRecord
# Increase timeout so the server doesn't disconnect us.
wait_timeout = @config[:wait_timeout]
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
defaults = [':default', :default].to_set
@@ -925,9 +848,19 @@ module ActiveRecord
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
- unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
+ if sql_mode = variables.delete('sql_mode')
+ sql_mode = quote(sql_mode)
+ elsif !defaults.include?(strict_mode?)
+ if strict_mode?
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
+ else
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
+ end
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
end
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
# NAMES does not have an equals sign, see
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
@@ -949,7 +882,13 @@ module ActiveRecord
end.compact.join(', ')
# ...and send them all in one query
- @connection.query "SET #{encoding} #{variable_assignments}"
+ @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ end
+
+ def column_definitions(table_name) # :nodoc:
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
+ each_hash(result)
+ end
end
def extract_foreign_key_action(structure, name, action) # :nodoc:
@@ -969,8 +908,14 @@ module ActiveRecord
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- MySQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ MySQL::TableDefinition.new(*args)
+ end
+
+ def extract_schema_qualified_name(string) # :nodoc:
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
+ schema, name = @config[:database], schema unless name
+ [schema, name]
end
def integer_to_sql(limit) # :nodoc:
@@ -980,7 +925,6 @@ module ActiveRecord
when 3; 'mediumint'
when nil, 4; 'int'
when 5..8; 'bigint'
- when 11; 'int(11)' # backward compatibility with Rails 2.0
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
end
end
@@ -1016,8 +960,8 @@ module ActiveRecord
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
@@ -1026,8 +970,8 @@ module ActiveRecord
def cast_value(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 81de7c03fb..28f0c8686a 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,11 +1,9 @@
-require 'set'
-
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
+ attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -15,14 +13,15 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
@name = name.freeze
+ @table_name = table_name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
@collation = collation
- @table_name = nil
+ @comment = comment
end
def has_default?
@@ -30,7 +29,7 @@ module ActiveRecord
end
def bigint?
- /bigint/ === sql_type
+ /\Abigint\b/ === sql_type
end
# Returns the human name of the column name.
@@ -54,7 +53,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function, collation]
+ [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index f633892dee..346916337e 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -3,16 +3,20 @@ require 'uri'
module ActiveRecord
module ConnectionAdapters
class ConnectionSpecification #:nodoc:
- attr_reader :config, :adapter_method
+ attr_reader :name, :config, :adapter_method
- def initialize(config, adapter_method)
- @config, @adapter_method = config, adapter_method
+ def initialize(name, config, adapter_method)
+ @name, @config, @adapter_method = name, config, adapter_method
end
def initialize_dup(original)
@config = original.config.dup
end
+ def to_hash
+ @config.merge(name: @name)
+ end
+
# Expands a connection string into a hash.
class ConnectionUrlResolver # :nodoc:
@@ -33,7 +37,7 @@ module ActiveRecord
def initialize(url)
raise "Database URL cannot be empty" if url.blank?
@uri = uri_parser.parse(url)
- @adapter = @uri.scheme.tr('-', '_')
+ @adapter = @uri.scheme && @uri.scheme.tr('-', '_')
@adapter = "postgresql" if @adapter == "postgres"
if @uri.opaque
@@ -179,7 +183,12 @@ module ActiveRecord
end
adapter_method = "#{spec[:adapter]}_connection"
- ConnectionSpecification.new(spec, adapter_method)
+
+ unless ActiveRecord::Base.respond_to?(adapter_method)
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
+ end
+
+ ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method)
end
private
@@ -224,7 +233,7 @@ module ActiveRecord
#
def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
- resolve_connection(config)
+ resolve_connection(config).merge("name" => spec.to_s)
else
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
new file mode 100644
index 0000000000..13c9b6cbd9
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -0,0 +1,125 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module DatabaseStatements
+ # Returns an ActiveRecord::Result instance.
+ def select_all(arel, name = nil, binds = [], preparable: nil)
+ result = if ExplainRegistry.collect? && prepared_statements
+ unprepared_statement { super }
+ else
+ super
+ end
+ @connection.next_result while @connection.more_results?
+ result
+ end
+
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation(arel, binds)
+ @connection.query_options.merge!(as: :hash)
+ select_result(to_sql(arel, binds), name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.first
+ end
+ ensure
+ @connection.query_options.merge!(as: :array)
+ end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil, binds = [])
+ select_result(sql, name, binds) do |result|
+ @connection.next_result while @connection.more_results?
+ result.to_a
+ end
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ super
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) do |result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
+ end
+ end
+ end
+
+ def exec_delete(sql, name, binds)
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { @connection.affected_rows }
+ else
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
+ end
+ end
+ alias :exec_update :exec_delete
+
+ protected
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
+ private
+
+ def select_result(sql, name = nil, binds = [])
+ if without_prepared_statement?(binds)
+ execute_and_free(sql, name) { |result| yield result }
+ else
+ exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result }
+ end
+ end
+
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
+ if @connection
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ end
+
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+
+ log(sql, name, binds) do
+ if cache_stmt
+ cache = @statements[sql] ||= {
+ stmt: @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ else
+ stmt = @connection.prepare(sql)
+ end
+
+ begin
+ result = stmt.execute(*type_casted_binds)
+ rescue Mysql2::Error => e
+ if cache_stmt
+ @statements.delete(sql)
+ else
+ stmt.close
+ end
+ raise e
+ end
+
+ ret = yield stmt, result
+ result.free if result
+ stmt.close unless cache_stmt
+ ret
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
new file mode 100644
index 0000000000..1820853196
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb
@@ -0,0 +1,70 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
+ # MySQL shell:
+ #
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # 2 rows in set (0.00 sec)
+ #
+ # This is an exercise in Ruby hyperrealism :).
+ def pp(result, elapsed)
+ widths = compute_column_widths(result)
+ separator = build_separator(widths)
+
+ pp = []
+
+ pp << separator
+ pp << build_cells(result.columns, widths)
+ pp << separator
+
+ result.rows.each do |row|
+ pp << build_cells(row, widths)
+ end
+
+ pp << separator
+ pp << build_footer(result.rows.length, elapsed)
+
+ pp.join("\n") + "\n"
+ end
+
+ private
+
+ def compute_column_widths(result)
+ [].tap do |widths|
+ result.columns.each_with_index do |column, i|
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
+ widths << cells_in_column.map(&:length).max
+ end
+ end
+ end
+
+ def build_separator(widths)
+ padding = 1
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
+ end
+
+ def build_cells(items, widths)
+ cells = []
+ items.each_with_index do |item, i|
+ item = 'NULL' if item.nil?
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
+ cells << item.to_s.send(justifier, widths[i])
+ end
+ '| ' + cells.join(' | ') + ' |'
+ end
+
+ def build_footer(nrows, elapsed)
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
new file mode 100644
index 0000000000..fbab654112
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module Quoting # :nodoc:
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name)
+ @quoted_table_names[name] ||= super.gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def unquoted_true
+ 1
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ def unquoted_false
+ 0
+ end
+
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index 1e2c859af9..fd2dc2aee8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -2,6 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class SchemaCreation < AbstractAdapter::SchemaCreation
+ delegate :add_sql_comment!, to: :@conn
+ private :add_sql_comment!
+
private
def visit_DropForeignKey(name)
@@ -22,6 +25,10 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def add_table_options!(create_sql, options)
+ add_sql_comment!(super, options[:comment])
+ end
+
def column_options(o)
column_options = super
column_options[:charset] = o.charset
@@ -29,13 +36,15 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- if options[:charset]
- sql << " CHARACTER SET #{options[:charset]}"
+ if charset = options[:charset]
+ sql << " CHARACTER SET #{charset}"
end
- if options[:collation]
- sql << " COLLATE #{options[:collation]}"
+
+ if collation = options[:collation]
+ sql << " COLLATE #{collation}"
end
- super
+
+ add_sql_comment!(super, options[:comment])
end
def add_column_position!(sql, options)
@@ -44,12 +53,13 @@ module ActiveRecord
elsif options[:after]
sql << " AFTER #{quote_column_name(options[:after])}"
end
+
sql
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 9dee3172f4..2ba9657f24 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -3,18 +3,13 @@ module ActiveRecord
module MySQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = {}
if column.bigint?
- spec[:id] = ':bigint'
+ spec = { id: :bigint.inspect }
spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- elsif column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- return if spec.empty?
else
- spec[:id] = column.type.inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
end
+ spec[:unsigned] = 'true' if column.unsigned?
spec
end
@@ -30,24 +25,24 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ super && column.auto_increment?
+ end
+
def schema_type(column)
if column.sql_type == 'tinyblob'
- 'blob'
+ :blob
else
super
end
end
- def schema_limit(column)
- super unless column.type == :boolean
- end
-
def schema_precision(column)
super unless /time/ === column.sql_type && column.precision == 0
end
def schema_collation(column)
- if column.collation && table_name = column.instance_variable_get(:@table_name)
+ if column.collation && table_name = column.table_name
@table_collation_cache ||= {}
@table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
column.collation.inspect if column.collation != @table_collation_cache[table_name]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index c3c5b660fd..22d35f1db5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,7 +1,9 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_record/connection_adapters/mysql/database_statements'
gem 'mysql2', '>= 0.3.18', '< 0.5'
require 'mysql2'
+raise 'mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+' if Mysql2::VERSION == '0.4.3'
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -16,7 +18,7 @@ module ActiveRecord
if config[:flags].kind_of? Array
config[:flags].push "FOUND_ROWS".freeze
else
- config[:flags] |= Mysql2::Client::FOUND_ROWS
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
end
end
@@ -35,14 +37,28 @@ module ActiveRecord
class Mysql2Adapter < AbstractMysqlAdapter
ADAPTER_NAME = 'Mysql2'.freeze
+ include MySQL::DatabaseStatements
+
def initialize(connection, logger, connection_options, config)
super
- @prepared_statements = false
+ @prepared_statements = false unless config.key?(:prepared_statements)
configure_connection
end
def supports_json?
- version >= '5.7.8'
+ !mariadb? && version >= '5.7.8'
+ end
+
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ true
+ end
+
+ def supports_savepoints?
+ true
end
# HELPER METHODS ===========================================
@@ -95,61 +111,6 @@ module ActiveRecord
end
end
- #--
- # DATABASE STATEMENTS ======================================
- #++
-
- # Returns a record hash with the column names as keys and column values
- # as values.
- def select_one(arel, name = nil, binds = [])
- arel, binds = binds_from_relation(arel, binds)
- execute(to_sql(arel, binds), name).each(as: :hash) do |row|
- @connection.next_result while @connection.more_results?
- return row
- end
- end
-
- # Returns an array of arrays containing the field values.
- # Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- result.to_a
- end
-
- # Executes the SQL statement in the context of this connection.
- def execute(sql, name = nil)
- if @connection
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
- # made since we established the connection
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- end
-
- super
- end
-
- def exec_query(sql, name = 'SQL', binds = [], prepare: false)
- result = execute(sql, name)
- @connection.next_result while @connection.more_results?
- ActiveRecord::Result.new(result.fields, result.to_a)
- end
-
- alias exec_without_stmt exec_query
-
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
- execute to_sql(sql, binds), name
- end
-
- def exec_delete(sql, name, binds)
- execute to_sql(sql, binds), name
- @connection.affected_rows
- end
- alias :exec_update :exec_delete
-
- def last_inserted_id(result)
- @connection.last_id
- end
-
private
def connect
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index bfa03fa136..3ad1911a28 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -8,7 +8,6 @@ module ActiveRecord
def serial?
return unless default_function
- table_name = @table_name || '(?<table_name>.+)'
%r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 6c15facf3b..6f2e03b370 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -4,44 +4,7 @@ module ActiveRecord
module DatabaseStatements
def explain(arel, binds = [])
sql = "EXPLAIN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
- end
-
- class ExplainPrettyPrinter # :nodoc:
- # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
- # PostgreSQL shell:
- #
- # QUERY PLAN
- # ------------------------------------------------------------------------------
- # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
- # Join Filter: (posts.user_id = users.id)
- # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
- # Index Cond: (id = 1)
- # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
- # Filter: (posts.user_id = 1)
- # (6 rows)
- #
- def pp(result)
- header = result.columns.first
- lines = result.rows.map(&:first)
-
- # We add 2 because there's one char of padding at both sides, note
- # the extra hyphens in the example above.
- width = [header, *lines].map(&:length).max + 2
-
- pp = []
-
- pp << header.center(width).rstrip
- pp << '-' * width
-
- pp += lines.map {|line| " #{line}"}
-
- nrows = result.rows.length
- rows_label = nrows == 1 ? 'row' : 'rows'
- pp << "(#{nrows} #{rows_label})"
-
- pp.join("\n") + "\n"
- end
+ PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
def select_value(arel, name = nil, binds = [])
@@ -128,6 +91,8 @@ module ActiveRecord
# Executes an SQL statement, returning a PGresult object on success
# or raising a PGError exception otherwise.
+ # Note: the PGresult object is manually memory managed; if you don't
+ # need it specifically, you many want consider the exec_query wrapper.
def execute(sql, name = nil)
log(sql, name) do
@connection.async_exec(sql)
@@ -153,7 +118,7 @@ module ActiveRecord
alias :exec_update :exec_delete
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
- unless pk
+ if pk.nil?
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
pk = primary_key(table_ref) if table_ref
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
new file mode 100644
index 0000000000..789b88912c
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb
@@ -0,0 +1,42 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
+ # PostgreSQL shell:
+ #
+ # QUERY PLAN
+ # ------------------------------------------------------------------------------
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
+ # Join Filter: (posts.user_id = users.id)
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
+ # Index Cond: (id = 1)
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
+ # Filter: (posts.user_id = 1)
+ # (6 rows)
+ #
+ def pp(result)
+ header = result.columns.first
+ lines = result.rows.map(&:first)
+
+ # We add 2 because there's one char of padding at both sides, note
+ # the extra hyphens in the example above.
+ width = [header, *lines].map(&:length).max + 2
+
+ pp = []
+
+ pp << header.center(width).rstrip
+ pp << '-' * width
+
+ pp += lines.map {|line| " #{line}"}
+
+ nrows = result.rows.length
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ pp << "(#{nrows} #{rows_label})"
+
+ pp.join("\n") + "\n"
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
index eeccb09bdf..838cb63281 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/cidr.rb
@@ -1,3 +1,5 @@
+require 'ipaddr'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
index 2163674019..dcc12ae2a4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb
@@ -3,8 +3,6 @@ module ActiveRecord
module PostgreSQL
module OID # :nodoc:
class Money < Type::Decimal # :nodoc:
- class_attribute :precision
-
def type
:money
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
index 7427a25ad5..4da240edb2 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb
@@ -14,6 +14,8 @@ module ActiveRecord
def cast(value)
case value
when ::String
+ return if value.blank?
+
if value[0] == '(' && value[-1] == ')'
value = value[1...-1]
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index c1c77a967e..6414459cd1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -27,8 +27,8 @@ module ActiveRecord
# - schema_name."table.name"
# - "schema.name".table_name
# - "schema.name"."table.name"
- def quote_table_name(name)
- Utils.extract_schema_qualified_name(name.to_s).quoted
+ def quote_table_name(name) # :nodoc:
+ @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted
end
# Quotes schema names for use in SQL queries.
@@ -41,8 +41,8 @@ module ActiveRecord
end
# Quotes column names for use in SQL queries.
- def quote_column_name(name) #:nodoc:
- PGconn.quote_ident(name.to_s)
+ def quote_column_name(name) # :nodoc:
+ @quoted_column_names[name] ||= PGconn.quote_ident(super)
end
# Quote date/time values for use in SQL input.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index cc7721ddd8..a1e10fd364 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -3,16 +3,9 @@ module ActiveRecord
module PostgreSQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = {}
- if column.serial?
- return unless column.bigint?
- spec[:id] = ':bigserial'
- elsif column.type == :uuid
- spec[:id] = ':uuid'
- spec[:default] = schema_default(column) || 'nil'
- else
- spec[:id] = column.type.inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
+ if schema_type(column) == :uuid
+ spec[:default] ||= 'nil'
end
spec
end
@@ -31,13 +24,17 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ schema_type(column) == :serial
+ end
+
def schema_type(column)
return super unless column.serial?
if column.bigint?
- 'bigserial'
+ :bigserial
else
- 'serial'
+ :serial
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 67e727d8ed..f6860b9aba 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -172,7 +174,11 @@ module ActiveRecord
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
+ (SELECT COUNT(*) FROM pg_opclass o
+ JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
+ ON o.oid = c.oid WHERE o.opcdefault = 'f')
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -190,43 +196,61 @@ module ActiveRecord
indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
+ comment = row[5]
+ opclass = row[6]
- columns = Hash[query(<<-SQL, "SCHEMA")]
- SELECT a.attnum, a.attname
- FROM pg_attribute a
- WHERE a.attrelid = #{oid}
- AND a.attnum IN (#{indkey.join(",")})
- SQL
+ using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
- column_names = columns.values_at(*indkey).compact
+ if indkey.include?(0) || opclass > 0
+ columns = expressions
+ else
+ columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
+ SELECT a.attnum, a.attname
+ FROM pg_attribute a
+ WHERE a.attrelid = #{oid}
+ AND a.attnum IN (#{indkey.join(",")})
+ SQL
- unless column_names.empty?
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
-
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ orders = Hash[
+ expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
+ ]
end
+
+ IndexDefinition.new(table_name, index_name, unique, columns, [], orders, where, nil, using.to_sym, comment.presence)
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name)
- # Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment.presence)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) # :nodoc:
+ PostgreSQLColumn.new(*args)
+ end
+
+ # Returns a comment stored in database for given table
+ def table_comment(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
+ if name.identifier
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT pg_catalog.obj_description(c.oid, 'pg_class')
+ FROM pg_catalog.pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE c.relname = #{quote(name.identifier)}
+ AND c.relkind IN ('r') -- (r)elation/table
+ AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ SQL
+ end
end
# Returns the current database name.
@@ -325,7 +349,7 @@ module ActiveRecord
select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
end
end
@@ -340,7 +364,7 @@ module ActiveRecord
end
if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
end
if pk && sequence
@@ -445,6 +469,7 @@ module ActiveRecord
def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -466,6 +491,7 @@ module ActiveRecord
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
# Changes the default value of a table column.
@@ -494,6 +520,18 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
+ end
+
+ # Adds comment for given table or drops it if +comment+ is a +nil+
+ def change_table_comment(table_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
+ end
+
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
@@ -502,8 +540,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
+ end
end
def remove_index(table_name, options = {}) #:nodoc:
@@ -601,7 +641,7 @@ module ActiveRecord
when 1, 2; 'smallint'
when nil, 3, 4; 'integer'
when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
end
else
super(type, limit, precision, scale)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2de6fbfaf0..ddfc560747 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -5,6 +5,7 @@ require 'pg'
require "active_record/connection_adapters/abstract_adapter"
require "active_record/connection_adapters/postgresql/column"
require "active_record/connection_adapters/postgresql/database_statements"
+require "active_record/connection_adapters/postgresql/explain_pretty_printer"
require "active_record/connection_adapters/postgresql/oid"
require "active_record/connection_adapters/postgresql/quoting"
require "active_record/connection_adapters/postgresql/referential_integrity"
@@ -15,8 +16,6 @@ require "active_record/connection_adapters/postgresql/type_metadata"
require "active_record/connection_adapters/postgresql/utils"
require "active_record/connection_adapters/statement_pool"
-require 'ipaddr'
-
module ActiveRecord
module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects
@@ -118,12 +117,15 @@ module ActiveRecord
include PostgreSQL::SchemaStatements
include PostgreSQL::DatabaseStatements
include PostgreSQL::ColumnDumper
- include Savepoints
def schema_creation # :nodoc:
PostgreSQL::SchemaCreation.new self
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::PostgreSQL.new(self)
+ end
+
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
@@ -138,6 +140,10 @@ module ActiveRecord
true
end
+ def supports_expression_index?
+ true
+ end
+
def supports_transaction_isolation?
true
end
@@ -158,6 +164,14 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_comments?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -194,14 +208,6 @@ module ActiveRecord
def initialize(connection, logger, connection_parameters, config)
super(connection, logger, config)
- @visitor = Arel::Visitors::PostgreSQL.new self
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
-
@connection_parameters = connection_parameters
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -211,10 +217,10 @@ module ActiveRecord
connect
add_pg_encoders
@statements = StatementPool.new @connection,
- self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })
+ self.class.type_cast_config_to_integer(config[:statement_limit])
- if postgresql_version < 80200
- raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
+ if postgresql_version < 90100
+ raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1."
end
add_pg_decoders
@@ -296,9 +302,8 @@ module ActiveRecord
true
end
- # Returns true if pg > 9.1
def supports_extensions?
- postgresql_version >= 90100
+ true
end
# Range datatypes weren't introduced until PostgreSQL 9.2
@@ -398,8 +403,10 @@ module ActiveRecord
protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
+ VALUE_LIMIT_VIOLATION = "22001"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
+ SERIALIZATION_FAILURE = "40001"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
@@ -409,6 +416,10 @@ module ActiveRecord
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message)
+ when VALUE_LIMIT_VIOLATION
+ ValueTooLong.new(message)
+ when SERIALIZATION_FAILURE
+ TransactionSerializationError.new(message)
else
super
end
@@ -598,25 +609,41 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.cause
+ raise unless is_cached_plan_failure?(e)
- # Get the PG code for the failure. Annoyingly, the code for
- # prepared statements whose return value may have changed is
- # FEATURE_NOT_SUPPORTED. Check here for more details:
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- begin
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
- rescue
- raise e
- end
- if FEATURE_NOT_SUPPORTED == code
+ # Nothing we can do if we are in a transaction because all commands
+ # will raise InFailedSQLTransaction
+ if in_transaction?
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
+ else
+ # outside of transactions we can simply flush this query and retry
@statements.delete sql_key(sql)
retry
- else
- raise e
end
end
+ # Annoyingly, the code for prepared statements whose return value may
+ # have changed is FEATURE_NOT_SUPPORTED.
+ #
+ # This covers various different error types so we need to do additional
+ # work to classify the exception definitively as a
+ # ActiveRecord::PreparedStatementCacheExpired
+ #
+ # Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze
+ def is_cached_plan_failure?(e)
+ pgerror = e.cause
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
+ rescue
+ false
+ end
+
+ def in_transaction?
+ open_transactions > 0
+ end
+
# Returns the statement identifier for the client side cache
# of statements
def sql_key(sql)
@@ -645,12 +672,6 @@ module ActiveRecord
# connected server's characteristics.
def connect
@connection = PGconn.connect(@connection_parameters)
-
- # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
- # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
- # should know about this but can't detect it there, so deal with it here.
- OID::Money.precision = (postgresql_version >= 80300) ? 19 : 10
-
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
@@ -702,7 +723,7 @@ module ActiveRecord
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
- # SELECT column.name, column.type, default.value
+ # SELECT column.name, column.type, default.value, column.comment
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
@@ -722,7 +743,8 @@ module ActiveRecord
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
(SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
+ col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -736,8 +758,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- PostgreSQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ PostgreSQL::TableDefinition.new(*args)
end
def can_perform_case_insensitive_comparison_for?(column)
@@ -805,7 +827,7 @@ module ActiveRecord
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
- ActiveRecord::Type.register(:date_time, OID::DateTime, adapter: :postgresql)
+ ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb
new file mode 100644
index 0000000000..a946f5ebd0
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb
@@ -0,0 +1,19 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
+ # the output of the SQLite shell:
+ #
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
+ #
+ def pp(result)
+ result.rows.map do |row|
+ row.join('|')
+ end.join("\n") + "\n"
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
new file mode 100644
index 0000000000..d5a181d3e2
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -0,0 +1,48 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module Quoting # :nodoc:
+ def quote_string(s)
+ @connection.class.quote(s)
+ end
+
+ def quote_table_name_for_assignment(table, attr)
+ quote_column_name(attr)
+ end
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ end
+
+ def quoted_time(value)
+ quoted_date(value)
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ case value
+ when BigDecimal
+ value.to_f
+ when String
+ if value.encoding == Encoding::ASCII_8BIT
+ super(value.encode(Encoding::UTF_8))
+ else
+ super
+ end
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index fe1dcbd710..70c0d28830 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -3,6 +3,13 @@ module ActiveRecord
module SQLite3
class SchemaCreation < AbstractAdapter::SchemaCreation
private
+
+ def column_options(o)
+ options = super
+ options[:null] = false if o.primary_key
+ options
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d1893f35f5..eb2268157b 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,5 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/sqlite3/explain_pretty_printer'
+require 'active_record/connection_adapters/sqlite3/quoting'
require 'active_record/connection_adapters/sqlite3/schema_creation'
gem 'sqlite3', '~> 1.3.6'
@@ -7,7 +9,6 @@ require 'sqlite3'
module ActiveRecord
module ConnectionHandling # :nodoc:
- # sqlite3 adapter reuses sqlite_connection.
def sqlite3_connection(config)
# Require database.
unless config[:database]
@@ -49,7 +50,8 @@ module ActiveRecord
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
ADAPTER_NAME = 'SQLite'.freeze
- include Savepoints
+
+ include SQLite3::Quoting
NATIVE_DATABASE_TYPES = {
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
@@ -77,21 +79,15 @@ module ActiveRecord
SQLite3::SchemaCreation.new self
end
+ def arel_visitor # :nodoc:
+ Arel::Visitors::SQLite.new(self)
+ end
+
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
@active = nil
- @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
-
- @visitor = Arel::Visitors::SQLite.new self
- @quoted_column_names = {}
-
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
- @prepared_statements = true
- @visitor.extend(DetermineIfPreparableVisitor)
- else
- @prepared_statements = false
- end
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
end
def supports_ddl_transactions?
@@ -133,6 +129,10 @@ module ActiveRecord
true
end
+ def supports_multi_insert?
+ sqlite_version >= '3.7.11'
+ end
+
def active?
@active != false
end
@@ -154,6 +154,10 @@ module ActiveRecord
true
end
+ def valid_type?(type)
+ true
+ end
+
# Returns 62. SQLite supports index names up to 64
# characters. The rest is used by rails internally to perform
# temporary rename operations
@@ -174,65 +178,13 @@ module ActiveRecord
true
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- case value
- when Type::Binary::Data
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def _type_cast(value) # :nodoc:
- case value
- when BigDecimal
- value.to_f
- when String
- if value.encoding == Encoding::ASCII_8BIT
- super(value.encode(Encoding::UTF_8))
- else
- super
- end
- else
- super
- end
- end
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_table_name_for_assignment(table, attr)
- quote_column_name(attr)
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
- end
-
#--
# DATABASE STATEMENTS ======================================
#++
def explain(arel, binds = [])
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
- end
-
- class ExplainPrettyPrinter
- # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
- # the output of the SQLite shell:
- #
- # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
- # 0|1|1|SCAN TABLE posts (~100000 rows)
- #
- def pp(result) # :nodoc:
- result.rows.map do |row|
- row.join('|')
- end.join("\n") + "\n"
- end
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
end
def exec_query(sql, name = nil, binds = [], prepare: false)
@@ -280,10 +232,6 @@ module ActiveRecord
log(sql, name) { @connection.execute(sql) }
end
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
- end
-
def begin_db_transaction #:nodoc:
log('begin transaction',nil) { @connection.transaction }
end
@@ -351,7 +299,8 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) #:nodoc:
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
table_structure(table_name).map do |field|
case field["dflt_value"]
when /^null$/i
@@ -365,7 +314,7 @@ module ActiveRecord
collation = field['collation']
sql_type = field['type']
type_metadata = fetch_type_metadata(sql_type)
- new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation)
end
end
@@ -592,7 +541,7 @@ module ActiveRecord
result = exec_query(sql, 'SCHEMA').first
if result
- # Splitting with left parantheses and picking up last will return all
+ # Splitting with left parentheses and picking up last will return all
# columns separated with comma(,).
columns_string = result["sql"].split('(').last
diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
index 57463dd749..9b0ed3e08b 100644
--- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb
@@ -1,11 +1,13 @@
module ActiveRecord
module ConnectionAdapters
- class StatementPool
+ class StatementPool # :nodoc:
include Enumerable
- def initialize(max = 1000)
+ DEFAULT_STATEMENT_LIMIT = 1000
+
+ def initialize(statement_limit = nil)
@cache = Hash.new { |h,pid| h[pid] = {} }
- @max = max
+ @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
end
def each(&block)
@@ -25,7 +27,7 @@ module ActiveRecord
end
def []=(sql, stmt)
- while @max <= cache.size
+ while @statement_limit <= cache.size
dealloc(cache.shift.last)
end
cache[sql] = stmt
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index a8b3d03ba5..086c678af5 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -44,17 +44,18 @@ module ActiveRecord
#
# The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
# may be returned on an error.
- def establish_connection(spec = nil)
- spec ||= DEFAULT_ENV.call.to_sym
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations
- spec = resolver.spec(spec)
+ def establish_connection(config = nil)
+ raise "Anonymous class is not allowed." unless name
- unless respond_to?(spec.adapter_method)
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
- end
+ config ||= DEFAULT_ENV.call.to_sym
+ spec_name = self == Base ? "primary" : name
+ self.connection_specification_name = spec_name
+
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
+ spec = resolver.resolve(config).symbolize_keys
+ spec[:name] = spec_name
- remove_connection
- connection_handler.establish_connection self, spec
+ connection_handler.establish_connection(spec)
end
class MergeAndResolveDefaultUrlConfig # :nodoc:
@@ -87,6 +88,16 @@ module ActiveRecord
retrieve_connection
end
+ attr_writer :connection_specification_name
+
+ # Return the specification name from the current class or its parent.
+ def connection_specification_name
+ if !defined?(@connection_specification_name) || @connection_specification_name.nil?
+ return self == Base ? "primary" : superclass.connection_specification_name
+ end
+ @connection_specification_name
+ end
+
def connection_id
ActiveRecord::RuntimeRegistry.connection_id ||= Thread.current.object_id
end
@@ -106,20 +117,28 @@ module ActiveRecord
end
def connection_pool
- connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
+ connection_handler.retrieve_connection_pool(connection_specification_name) or raise ConnectionNotEstablished
end
def retrieve_connection
- connection_handler.retrieve_connection(self)
+ connection_handler.retrieve_connection(connection_specification_name)
end
# Returns +true+ if Active Record is connected.
def connected?
- connection_handler.connected?(self)
+ connection_handler.connected?(connection_specification_name)
end
- def remove_connection(klass = self)
- connection_handler.remove_connection(klass)
+ def remove_connection(name = nil)
+ name ||= @connection_specification_name if defined?(@connection_specification_name)
+ # if removing a connection that have a pool, we reset the
+ # connection_specification_name so it will use the parent
+ # pool.
+ if connection_handler.retrieve_connection_pool(name)
+ self.connection_specification_name = nil
+ end
+
+ connection_handler.remove_connection(name)
end
def clear_cache! # :nodoc:
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 475a298467..de337b24d6 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,6 +72,14 @@ module ActiveRecord
##
# :singleton-method:
+ # Specifies if an error should be raised on query limit or order being
+ # ignored when doing batch queries. Useful in applications where the
+ # limit or scope being ignored is error-worthy, rather than a warning.
+ mattr_accessor :error_on_ignored_order_or_limit, instance_writer: false
+ self.error_on_ignored_order_or_limit = false
+
+ ##
+ # :singleton-method:
# Specify whether or not to use timestamps for migration versions
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
@@ -128,7 +136,7 @@ module ActiveRecord
end
def initialize_find_by_cache # :nodoc:
- @find_by_statement_cache = {}.extend(Mutex_m)
+ @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
end
def inherited(child_class) # :nodoc:
@@ -151,7 +159,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -249,13 +257,18 @@ module ActiveRecord
# Returns the Arel engine.
def arel_engine # :nodoc:
@arel_engine ||=
- if Base == self || connection_handler.retrieve_connection_pool(self)
+ if Base == self || connection_handler.retrieve_connection_pool(connection_specification_name)
self
else
superclass.arel_engine
end
end
+ def arel_attribute(name, table = arel_table) # :nodoc:
+ name = attribute_alias(name) if attribute_alias?(name)
+ table[name]
+ end
+
def predicate_builder # :nodoc:
@predicate_builder ||= PredicateBuilder.new(table_metadata)
end
@@ -267,8 +280,9 @@ module ActiveRecord
private
def cached_find_by_statement(key, &block) # :nodoc:
- @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
- @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
+ cache = @find_by_statement_cache[connection.prepared_statements]
+ cache[key] || cache.synchronize {
+ cache[key] ||= StatementCache.create(connection, &block)
}
end
@@ -324,7 +338,7 @@ module ActiveRecord
# post.title # => 'hello world'
def init_with(coder)
coder = LegacyYamlAdapter.convert(self.class, coder)
- @attributes = coder['attributes']
+ @attributes = self.class.yaml_encoder.decode(coder)
init_internals
@@ -390,11 +404,9 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- # FIXME: Remove this when we better serialize attributes
- coder['raw_attributes'] = attributes_before_type_cast
- coder['attributes'] = @attributes
+ self.class.yaml_encoder.encode(@attributes, coder)
coder['new_record'] = new_record?
- coder['active_record_yaml_version'] = 1
+ coder['active_record_yaml_version'] = 2
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -418,7 +430,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- id.hash
+ [self.class, id].hash
else
super
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 903c63a7db..7be332fb97 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -152,7 +152,7 @@ module ActiveRecord
enum_values = ActiveSupport::HashWithIndifferentAccess.new
name = name.to_sym
- # def self.statuses statuses end
+ # def self.statuses() statuses end
detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index e5906b6756..38e4fbec8b 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -125,6 +125,10 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
+ # Raised when a record cannot be inserted or updated because a value too long for a column type.
+ class ValueTooLong < StatementInvalid
+ end
+
# Raised when number of bind variables in statement given to +:condition+ key
# (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
# does not match number of expected values supplied.
@@ -139,6 +143,11 @@ module ActiveRecord
class NoDatabaseError < StatementInvalid
end
+ # Raised when Postgres returns 'cached plan must not change result type' and
+ # we cannot retry gracefully (e.g. inside a transaction)
+ class PreparedStatementCacheExpired < StatementInvalid
+ end
+
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
@@ -275,4 +284,19 @@ module ActiveRecord
# The mysql2 and postgresql adapters support setting the transaction isolation level.
class TransactionIsolationError < ActiveRecordError
end
+
+ # TransactionSerializationError will be raised when a transaction is rolled
+ # back by the database due to a serialization failure or a deadlock.
+ #
+ # See the following:
+ #
+ # * http://www.postgresql.org/docs/current/static/transaction-iso.html
+ # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
+ class TransactionSerializationError < ActiveRecordError
+ end
+
+ # IrreversibleOrderError is raised when a relation's order is too complex for
+ # +reverse_order+ to automatically reverse.
+ class IrreversibleOrderError < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index f969556c50..e4a44244e2 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -52,9 +52,15 @@ module ActiveRecord
end
end
+ def prepare_erb(content)
+ erb = ERB.new(content)
+ erb.filename = @file
+ erb
+ end
+
def render(content)
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
- ERB.new(content).result(context.get_binding)
+ prepare_erb(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index ed1bbf5dcd..51bf12d0bf 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -66,7 +66,7 @@ module ActiveRecord
# By default, +test_helper.rb+ will load all of your fixtures into your test
# database, so this test will succeed.
#
- # The testing environment will automatically load the all fixtures into the database before each
+ # The testing environment will automatically load all the fixtures into the database before each
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixture's data may also be accessed by
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index b4f2f66e1c..f33456a744 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveRecord
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta1.1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 3b6fb70d0d..899683ee4f 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -192,7 +192,7 @@ module ActiveRecord
end
def type_condition(table = arel_table)
- sti_column = table[inheritance_column]
+ sti_column = arel_attribute(inheritance_column, table)
sti_names = ([self] + descendants).map(&:sti_name)
sti_column.in(sti_names)
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 10fee4dca2..17a5dc1d1b 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -5,10 +5,6 @@ module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
# as which environment migrations were run in.
class InternalMetadata < ActiveRecord::Base # :nodoc:
- # Keys in mysql are limited to 191 characters, due to this no adapter can
- # use a longer key
- KEY_LIMIT = 191
-
class << self
def primary_key
"key"
@@ -18,12 +14,12 @@ module ActiveRecord
"#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
end
- def index_name
- "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}"
+ def original_table_name
+ "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}"
end
def []=(key, value)
- first_or_initialize(key: key).update_attributes!(value: value)
+ find_or_initialize_by(key: key).update_attributes!(value: value)
end
def [](key)
@@ -34,14 +30,23 @@ module ActiveRecord
ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
+ def original_table_exists?
+ # This method will be removed in Rails 5.1
+ # Since it is only necessary when `active_record_internal_metadatas` could exist
+ ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) }
+ end
+
# Creates an internal metadata table with columns +key+ and +value+
def create_table
+ if original_table_exists?
+ connection.rename_table(original_table_name, table_name)
+ end
unless table_exists?
- connection.create_table(table_name, id: false) do |t|
- t.column :key, :string, null: false, limit: KEY_LIMIT
- t.column :value, :string
- t.index :key, unique: true, name: index_name
+ key_options = connection.internal_string_options_for_primary_key
+ connection.create_table(table_name, id: false) do |t|
+ t.string :key, key_options
+ t.string :value
t.timestamps
end
end
diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb
index 89dee58423..c7683f68c7 100644
--- a/activerecord/lib/active_record/legacy_yaml_adapter.rb
+++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb
@@ -4,7 +4,7 @@ module ActiveRecord
return coder unless coder.is_a?(Psych::Coder)
case coder["active_record_yaml_version"]
- when 1 then coder
+ when 1, 2 then coder
else
if coder["attributes"].is_a?(AttributeSet)
Rails420.convert(klass, coder)
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2336d23a1c..67b8efac66 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -150,7 +150,7 @@ module ActiveRecord
# The version column used for optimistic locking. Defaults to +lock_version+.
def locking_column
- reset_locking_column unless defined?(@locking_column)
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
@locking_column
end
@@ -184,9 +184,16 @@ module ActiveRecord
end
end
+
+ # In de/serialize we change `nil` to 0, so that we can allow passing
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
+ # during update record.
class LockingType < DelegateClass(Type::Value) # :nodoc:
def deserialize(value)
- # `nil` *should* be changed to 0
+ super.to_i
+ end
+
+ def serialize(value)
super.to_i
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index b63caa4473..8e32af1c49 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -22,7 +22,11 @@ module ActiveRecord
def render_bind(attribute)
value = if attribute.type.binary? && attribute.value
- "<#{attribute.value.bytesize} bytes of binary data>"
+ if attribute.value.is_a?(Hash)
+ "<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
+ else
+ "<#{attribute.value.bytesize} bytes of binary data>"
+ end
else
attribute.value_for_database
end
@@ -67,7 +71,7 @@ module ActiveRecord
case sql
when /\A\s*rollback/mi
RED
- when /\s*.*?select .*for update/mi, /\A\s*lock/mi
+ when /select .*for update/mi, /\A\s*lock/mi
WHITE
when /\A\s*select/i
BLUE
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4419a7b1e7..81fe053fe1 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -156,8 +156,8 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
- msg = "You are attempting to run a destructive action against your '#{env}' database\n"
- msg << "If you are sure you want to continue, run the same command with the environment variable\n"
+ msg = "You are attempting to run a destructive action against your '#{env}' database.\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable:\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
end
@@ -166,13 +166,13 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
- msg << "You are running in `#{ current }` environment."
+ msg << "You are running in `#{ current }` environment. "
msg << "If you are sure you want to continue, first set the environment using:\n\n"
msg << "\tbin/rails db:environment:set"
if defined?(Rails.env)
- super("#{msg} RAILS_ENV=#{::Rails.env}")
+ super("#{msg} RAILS_ENV=#{::Rails.env}\n\n")
else
- super(msg)
+ super("#{msg}\n\n")
end
end
end
@@ -277,7 +277,7 @@ module ActiveRecord
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
# * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a
- # default value for +column_name+ definded by +default+ on +table_name+.
+ # default value for +column_name+ defined by +default+ on +table_name+.
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
# Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
# indicates whether the value can be +NULL+. See
@@ -524,23 +524,17 @@ module ActiveRecord
end
def self.[](version)
- version = version.to_s
- name = "V#{version.tr('.', '_')}"
- unless Compatibility.const_defined?(name)
- versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
- raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
- end
- Compatibility.const_get(name)
+ Compatibility.find(version)
end
def self.current_version
- Rails.version.to_f
+ ActiveRecord::VERSION::STRING.to_f
end
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
- # loading a web page if config.active_record.migration_error is set to :page_load
+ # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
class CheckPending
def initialize(app)
@app = app
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 5d742b523b..74833f938f 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -1,7 +1,17 @@
module ActiveRecord
class Migration
module Compatibility # :nodoc: all
- V5_0 = Current
+ def self.find(version)
+ version = version.to_s
+ name = "V#{version.tr('.', '_')}"
+ unless const_defined?(name)
+ versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
+ raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
+ end
+ const_get(name)
+ end
+
+ V5_1 = Current
module FourTwoShared
module TableDefinition
@@ -30,6 +40,19 @@ module ActiveRecord
end
end
+ def change_table(table_name, options = {})
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
+ end
+
def add_reference(*, **options)
options[:index] ||= false
super
@@ -44,7 +67,7 @@ module ActiveRecord
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
options[:name] =
- if options.key?(:name).present?
+ if options[:name].present?
options[:name].to_s
else
index_name(table_name, column: column_names)
@@ -79,6 +102,9 @@ module ActiveRecord
end
end
+ class V5_0 < V5_1
+ end
+
class V4_2 < V5_0
# 4.2 is defined as a module because it needs to be shared with
# Legacy. When the time comes, V5_0 should be defined straight
@@ -89,7 +115,7 @@ module ActiveRecord
module Legacy
include FourTwoShared
- def run(*)
+ def migrate(*)
ActiveSupport::Deprecation.warn \
"Directly inheriting from ActiveRecord::Migration is deprecated. " \
"Please specify the Rails release the migration was written for:\n" \
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 722d7b5fce..7996c32bbc 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -44,9 +44,9 @@ module ActiveRecord
##
# :singleton-method:
- # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas"
+ # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata"
class_attribute :internal_metadata_table_name, instance_accessor: false
- self.internal_metadata_table_name = "active_record_internal_metadatas"
+ self.internal_metadata_table_name = "ar_internal_metadata"
##
# :singleton-method:
@@ -231,6 +231,18 @@ module ActiveRecord
@explicit_sequence_name = true
end
+ # Determines if the primary key values should be selected from their
+ # corresponding sequence before the insert statement.
+ def prefetch_primary_key?
+ connection.prefetch_primary_key?(table_name)
+ end
+
+ # Returns the next value that will be used as the primary key on
+ # an insert statement.
+ def next_sequence_value
+ connection.next_sequence_value(sequence_name)
+ end
+
# Indicates whether the table associated with this class exists
def table_exists?
connection.schema_cache.data_source_exists?(table_name)
@@ -255,6 +267,10 @@ module ActiveRecord
@attribute_types ||= Hash.new(Type::Value.new)
end
+ def yaml_encoder # :nodoc:
+ @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types)
+ end
+
# Returns the type of the attribute with the given name, after applying
# all modifiers. This method is the only valid source of information for
# anything related to the types of a model's attributes. This method will
@@ -363,6 +379,7 @@ module ActiveRecord
@columns = nil
@columns_hash = nil
@attribute_names = nil
+ @yaml_encoder = nil
direct_descendants.each do |descendant|
descendant.send(:reload_schema_from_cache)
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 0d5a8e6f25..fe68869143 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -195,19 +195,27 @@ module ActiveRecord
# Nested attributes for an associated collection can also be passed in
# the form of a hash of hashes instead of an array of hashes:
#
- # Member.create(name: 'joe',
- # posts_attributes: { first: { title: 'Foo' },
- # second: { title: 'Bar' } })
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: {
+ # first: { title: 'Foo' },
+ # second: { title: 'Bar' }
+ # }
+ # )
#
# has the same effect as
#
- # Member.create(name: 'joe',
- # posts_attributes: [ { title: 'Foo' },
- # { title: 'Bar' } ])
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: [
+ # { title: 'Foo' },
+ # { title: 'Bar' }
+ # ]
+ # )
#
# The keys of the hash which is the value for +:posts_attributes+ are
# ignored in this case.
- # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
# such keys, otherwise the hash will be wrapped in an array and
# interpreted as an attribute hash for a single post.
#
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 0b500346bc..1ab4e0404f 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module NullRelation # :nodoc:
def exec_queries
- @records = []
+ @records = [].freeze
end
def pluck(*column_names)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 4d661735cc..afed5e5e85 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -61,7 +61,7 @@ module ActiveRecord
# +instantiate+ instead of +new+, finder methods ensure they get new
# instances of the appropriate class for each record.
#
- # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
+ # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
@@ -106,7 +106,7 @@ module ActiveRecord
# the existing record gets updated.
#
# By default, save always runs validations. If any of them fail the action
- # is cancelled and #save returns +false+. However, if you supply
+ # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
@@ -133,7 +133,7 @@ module ActiveRecord
# the existing record gets updated.
#
# By default, #save! always runs validations. If any of them fail
- # ActiveRecord::RecordInvalid gets raised. However, if you supply
+ # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
# validate: false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
@@ -270,7 +270,7 @@ module ActiveRecord
alias update_attributes update
# Updates its receiver just like #update but calls #save! instead
- # of +save+, so an exception is raised if the record is invalid.
+ # of +save+, so an exception is raised if the record is invalid and saving will fail.
def update!(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index dcb2bd3d84..07d863d15e 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# Enable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def cache(&block)
- if ActiveRecord::Base.connected?
+ if connected?
connection.cache(&block)
else
yield
@@ -15,7 +15,7 @@ module ActiveRecord
# Disable the query cache within the block if Active Record is configured.
# If it's not, it will execute the given block.
def uncached(&block)
- if ActiveRecord::Base.connected?
+ if connected?
connection.uncached(&block)
else
yield
@@ -23,34 +23,31 @@ module ActiveRecord
end
end
- def initialize(app)
- @app = app
- end
-
- def call(env)
+ def self.run
connection = ActiveRecord::Base.connection
enabled = connection.query_cache_enabled
connection_id = ActiveRecord::Base.connection_id
connection.enable_query_cache!
- response = @app.call(env)
- response[2] = Rack::BodyProxy.new(response[2]) do
- restore_query_cache_settings(connection_id, enabled)
- end
-
- response
- rescue Exception => e
- restore_query_cache_settings(connection_id, enabled)
- raise e
+ [enabled, connection_id]
end
- private
+ def self.complete(state)
+ enabled, connection_id = state
- def restore_query_cache_settings(connection_id, enabled)
ActiveRecord::Base.connection_id = connection_id
ActiveRecord::Base.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
+ def self.install_executor_hooks(executor = ActiveSupport::Executor)
+ executor.register_hook(self)
+
+ executor.to_complete do
+ unless ActiveRecord::Base.connection.transaction_open?
+ ActiveRecord::Base.clear_active_connections!
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 1f429cfd94..53ddd95bb0 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Querying
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
- delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all
+ delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
@@ -35,8 +35,8 @@ module ActiveRecord
#
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
- def find_by_sql(sql, binds = [])
- result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ def find_by_sql(sql, binds = [], preparable: nil)
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
column_types = result_set.column_types.dup
columns_hash.each_key { |k| column_types.delete k }
message_bus = ActiveSupport::Notifications.instrumenter
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 38916f7376..98ea425d16 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,12 +16,6 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::QueryCache
-
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::ConnectionAdapters::ConnectionManagement
-
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
@@ -40,7 +34,7 @@ module ActiveRecord
task :load_config do
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
+ if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT)
if engine.paths['db/migrate'].existent
ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
end
@@ -71,7 +65,6 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
- self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types
end
end
@@ -153,11 +146,9 @@ end_warning
end
end
- initializer "active_record.set_reloader_hooks" do |app|
- hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
-
+ initializer "active_record.set_reloader_hooks" do
ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.send(hook) do
+ ActiveSupport::Reloader.before_class_unload do
if ActiveRecord::Base.connected?
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.clear_reloadable_connections!
@@ -166,6 +157,12 @@ end_warning
end
end
+ initializer "active_record.set_executor_hooks" do
+ ActiveSupport.on_load(:active_record) do
+ ActiveRecord::QueryCache.install_executor_hooks
+ end
+ end
+
initializer "active_record.add_watchable_files" do |app|
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 69a7838001..fc1b62ee96 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -22,7 +22,7 @@ db_namespace = namespace :db do
end
end
- desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases.'
task :create => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.create_current
end
@@ -33,7 +33,7 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases.'
task :drop => [:load_config, :check_protected_environments] do
db_namespace["drop:_unsafe"].invoke
end
@@ -256,7 +256,7 @@ db_namespace = namespace :db do
end
desc 'Loads a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -278,7 +278,7 @@ db_namespace = namespace :db do
desc 'Clears a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
- FileUtils.rm(filename) if File.exist?(filename)
+ rm_f filename, verbose: false
end
end
@@ -302,7 +302,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 320ced5afa..bf398b0d40 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -124,9 +124,24 @@ module ActiveRecord
end
end
- # Holds all the methods that are shared between MacroReflection, AssociationReflection
- # and ThroughReflection
+ # Holds all the methods that are shared between MacroReflection and ThroughReflection.
+ #
+ # AbstractReflection
+ # MacroReflection
+ # AggregateReflection
+ # AssociationReflection
+ # HasManyReflection
+ # HasOneReflection
+ # BelongsToReflection
+ # HasAndBelongsToManyReflection
+ # ThroughReflection
+ # PolymorphicReflection
+ # RuntimeReflection
class AbstractReflection # :nodoc:
+ def through_reflection?
+ false
+ end
+
def table_name
klass.table_name
end
@@ -228,18 +243,14 @@ module ActiveRecord
def alias_candidate(name)
"#{plural_name}_#{name}"
end
+
+ def chain
+ collect_join_chain
+ end
end
# Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
- #
- # MacroReflection
- # AggregateReflection
- # AssociationReflection
- # HasManyReflection
- # HasOneReflection
- # BelongsToReflection
- # ThroughReflection
class MacroReflection < AbstractReflection
# Returns the name of the macro.
#
@@ -418,7 +429,7 @@ module ActiveRecord
# A chain of reflections from this one back to the owner. For more see the explanation in
# ThroughReflection.
- def chain
+ def collect_join_chain
[self]
end
@@ -438,6 +449,10 @@ module ActiveRecord
scope ? [[scope]] : [[]]
end
+ def has_scope?
+ scope
+ end
+
def has_inverse?
inverse_name
end
@@ -483,28 +498,7 @@ module ActiveRecord
# Returns +true+ if +self+ is a +has_one+ reflection.
def has_one?; false; end
- def association_class
- case macro
- when :belongs_to
- if polymorphic?
- Associations::BelongsToPolymorphicAssociation
- else
- Associations::BelongsToAssociation
- end
- when :has_many
- if options[:through]
- Associations::HasManyThroughAssociation
- else
- Associations::HasManyAssociation
- end
- when :has_one
- if options[:through]
- Associations::HasOneThroughAssociation
- else
- Associations::HasOneAssociation
- end
- end
- end
+ def association_class; raise NotImplementedError; end
def polymorphic?
options[:polymorphic]
@@ -513,6 +507,18 @@ module ActiveRecord
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
+ def add_as_source(seed)
+ seed
+ end
+
+ def add_as_polymorphic_through(reflection, seed)
+ seed + [PolymorphicReflection.new(self, reflection)]
+ end
+
+ def add_as_through(seed)
+ seed + [self]
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -522,14 +528,7 @@ module ActiveRecord
private
def calculate_constructable(macro, options)
- case macro
- when :belongs_to
- !polymorphic?
- when :has_one
- !options[:through]
- else
- true
- end
+ true
end
# Attempts to find the inverse association name automatically.
@@ -622,34 +621,52 @@ module ActiveRecord
end
class HasManyReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
- end
-
def macro; :has_many; end
def collection?; true; end
- end
- class HasOneReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
+ def association_class
+ if options[:through]
+ Associations::HasManyThroughAssociation
+ else
+ Associations::HasManyAssociation
+ end
end
+ end
+ class HasOneReflection < AssociationReflection # :nodoc:
def macro; :has_one; end
def has_one?; true; end
- end
- class BelongsToReflection < AssociationReflection # :nodoc:
- def initialize(name, scope, options, active_record)
- super(name, scope, options, active_record)
+ def association_class
+ if options[:through]
+ Associations::HasOneThroughAssociation
+ else
+ Associations::HasOneAssociation
+ end
end
+ private
+
+ def calculate_constructable(macro, options)
+ !options[:through]
+ end
+ end
+
+ class BelongsToReflection < AssociationReflection # :nodoc:
def macro; :belongs_to; end
def belongs_to?; true; end
+ def association_class
+ if polymorphic?
+ Associations::BelongsToPolymorphicAssociation
+ else
+ Associations::BelongsToAssociation
+ end
+ end
+
def join_keys(association_klass)
key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
JoinKeys.new(key, foreign_key)
@@ -658,6 +675,12 @@ module ActiveRecord
def join_id_for(owner) # :nodoc:
owner[foreign_key]
end
+
+ private
+
+ def calculate_constructable(macro, options)
+ !polymorphic?
+ end
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -685,6 +708,10 @@ module ActiveRecord
@source_reflection_name = delegate_reflection.options[:source]
end
+ def through_reflection?
+ true
+ end
+
def klass
@klass ||= delegate_reflection.compute_class(class_name)
end
@@ -743,25 +770,13 @@ module ActiveRecord
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
#
- def chain
- @chain ||= begin
- a = source_reflection.chain
- b = through_reflection.chain.map(&:dup)
-
- if options[:source_type]
- b[0] = PolymorphicReflection.new(b[0], self)
- end
-
- chain = a + b
- chain[0] = self # Use self so we don't lose the information from :source_type
- chain
- end
+ def collect_join_chain
+ collect_join_reflections [self]
end
# This is for clearing cache on the reflection. Useful for tests that need to compare
# SQL queries on associations.
def clear_association_scope_cache # :nodoc:
- @chain = nil
delegate_reflection.clear_association_scope_cache
source_reflection.clear_association_scope_cache
through_reflection.clear_association_scope_cache
@@ -808,13 +823,19 @@ module ActiveRecord
end
end
+ def has_scope?
+ scope || options[:source_type] ||
+ source_reflection.has_scope? ||
+ through_reflection.has_scope?
+ end
+
def join_keys(association_klass)
source_reflection.join_keys(association_klass)
end
# A through association is nested if there would be more than one join table
def nested?
- chain.length > 2
+ source_reflection.through_reflection? || through_reflection.through_reflection?
end
# We want to use the klass from this reflection, rather than just delegate straight to
@@ -853,7 +874,7 @@ module ActiveRecord
example_options = options.dup
example_options[:source] = source_reflection_names.first
ActiveSupport::Deprecation.warn \
- "Ambiguous source reflection for through association. Please " \
+ "Ambiguous source reflection for through association. Please " \
"specify a :source directive on your declaration like:\n" \
"\n" \
" class #{active_record.name} < ActiveRecord::Base\n" \
@@ -914,6 +935,27 @@ module ActiveRecord
scope_chain
end
+ def add_as_source(seed)
+ collect_join_reflections seed
+ end
+
+ def add_as_polymorphic_through(reflection, seed)
+ collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
+ end
+
+ def add_as_through(seed)
+ collect_join_reflections(seed + [self])
+ end
+
+ def collect_join_reflections(seed)
+ a = source_reflection.add_as_source seed
+ if options[:source_type]
+ through_reflection.add_as_polymorphic_through self, a
+ else
+ through_reflection.add_as_through a
+ end
+ end
+
protected
def actual_source_reflection # FIXME: this is a horrible name
@@ -970,7 +1012,7 @@ module ActiveRecord
end
def constraints
- [source_type_info]
+ @reflection.constraints + [source_type_info]
end
def source_type_info
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 032b8d4c5d..7a1552856b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -45,9 +45,9 @@ module ActiveRecord
k.name == primary_key
}]
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
- values[klass.arel_table[klass.primary_key]] = primary_key_value
+ if !primary_key_value && klass.prefetch_primary_key?
+ primary_key_value = klass.next_sequence_value
+ values[arel_attribute(klass.primary_key)] = primary_key_value
end
end
@@ -94,17 +94,21 @@ module ActiveRecord
end
def substitute_values(values) # :nodoc:
- binds = values.map do |arel_attr, value|
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
- end
+ binds = []
+ substitutes = []
- substitutes = values.map do |(arel_attr, _)|
- [arel_attr, Arel::Nodes::BindParam.new]
+ values.each do |arel_attr, value|
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
end
+ def arel_attribute(name) # :nodoc:
+ klass.arel_attribute(name, table)
+ end
+
# Initializes new record from relation while maintaining the current
# scope.
#
@@ -249,17 +253,21 @@ module ActiveRecord
# Converts relation objects to Array.
def to_a
+ records.dup
+ end
+
+ def records # :nodoc:
load
@records
end
# Serializes the relation objects Array.
def encode_with(coder)
- coder.represent_seq(nil, to_a)
+ coder.represent_seq(nil, records)
end
def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
+ records.as_json(options)
end
# Returns size of the records.
@@ -271,12 +279,7 @@ module ActiveRecord
def empty?
return @records.empty? if loaded?
- if limit_value == 0
- true
- else
- c = count(:all)
- c.respond_to?(:zero?) ? c.zero? : c.empty?
- end
+ limit_value == 0 || !exists?
end
# Returns true if there are no records.
@@ -294,13 +297,13 @@ module ActiveRecord
# Returns true if there is exactly one record.
def one?
return super if block_given?
- limit_value ? to_a.one? : size == 1
+ limit_value ? records.one? : size == 1
end
# Returns true if there is more than one record.
def many?
return super if block_given?
- limit_value ? to_a.many? : size > 1
+ limit_value ? records.many? : size > 1
end
# Returns a cache key that can be used to identify the records fetched by
@@ -373,9 +376,9 @@ module ActiveRecord
stmt.table(table)
if joins_values.any?
- @klass.connection.join_to_update(stmt, arel, table[primary_key])
+ @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
- stmt.key = table[primary_key]
+ stmt.key = arel_attribute(primary_key)
stmt.take(arel.limit)
stmt.order(*arel.orders)
stmt.wheres = arel.constraints
@@ -414,13 +417,13 @@ module ActiveRecord
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
elsif id == :all
- to_a.each { |record| record.update(attributes) }
+ records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
object = find(id)
@@ -449,11 +452,11 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all
+ To achieve the same use where(conditions).destroy_all.
MESSAGE
where(conditions).destroy_all
else
- to_a.each(&:destroy).tap { reset }
+ records.each(&:destroy).tap { reset }
end
end
@@ -519,7 +522,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all
+ To achieve the same use where(conditions).delete_all.
MESSAGE
where(conditions).delete_all
else
@@ -527,7 +530,7 @@ module ActiveRecord
stmt.from(table)
if joins_values.any?
- @klass.connection.join_to_delete(stmt, arel, table[primary_key])
+ @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
else
stmt.wheres = arel.constraints
end
@@ -583,7 +586,7 @@ module ActiveRecord
def reset
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
- @records = []
+ @records = [].freeze
@offsets = {}
self
end
@@ -650,21 +653,21 @@ module ActiveRecord
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
- self == other.to_a
+ self == other.records
when Relation
other.to_sql == to_sql
when Array
- to_a == other
+ records == other
end
end
def pretty_print(q)
- q.pp(self.to_a)
+ q.pp(self.records)
end
# Returns true if relation is blank.
def blank?
- to_a.blank?
+ records.blank?
end
def values
@@ -672,7 +675,7 @@ module ActiveRecord
end
def inspect
- entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
+ entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
entries[10] = '...' if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
@@ -681,14 +684,14 @@ module ActiveRecord
protected
def load_records(records)
- @records = records
+ @records = records.freeze
@loaded = true
end
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 54587ae18e..3639625722 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
+
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
# is very inefficient since it will try to instantiate all the objects at once.
@@ -31,6 +33,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -48,13 +53,13 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(start: nil, finish: nil, batch_size: 1000)
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
- find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
@@ -83,6 +88,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -100,16 +108,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(start: nil, finish: nil, batch_size: 1000)
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
yield batch.to_a
end
end
@@ -140,6 +148,8 @@ module ActiveRecord
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
@@ -171,14 +181,14 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
- if logger && (arel.orders.present? || arel.taken.present?)
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ if arel.orders.present? || arel.taken.present?
+ act_on_order_or_limit_ignored(error_on_ignore)
end
relation = relation.reorder(batch_order).limit(of)
@@ -187,7 +197,7 @@ module ActiveRecord
loop do
if load
- records = batch_relation.to_a
+ records = batch_relation.records
ids = records.map(&:id)
yielded_relation = self.where(primary_key => ids)
yielded_relation.load_records(records)
@@ -204,20 +214,30 @@ module ActiveRecord
yield yielded_relation
break if ids.length < of
- batch_relation = relation.where(table[primary_key].gt(primary_key_offset))
+ batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
end
end
private
def apply_limits(relation, start, finish)
- relation = relation.where(table[primary_key].gteq(start)) if start
- relation = relation.where(table[primary_key].lteq(finish)) if finish
+ relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
+ relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
relation
end
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
+
+ def act_on_order_or_limit_ignored(error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
+
+ if raise_error
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ elsif logger
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index c6e39814dd..333b3a63cf 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -35,14 +35,14 @@ module ActiveRecord
return to_enum(:each_record) unless block_given?
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
- relation.to_a.each { |record| yield record }
+ relation.records.each { |record| yield record }
end
end
# Delegates #delete_all, #update_all, #destroy_all methods to each batch.
#
# People.in_batches.delete_all
- # People.in_batches.destroy_all('age < 10')
+ # People.where('age < 10').in_batches.destroy_all
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index f45844a9ea..d6d92b8607 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -37,7 +37,11 @@ module ActiveRecord
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
# between databases. In invalid cases, an error from the database is thrown.
def count(column_name = nil)
- calculate(:count, column_name)
+ if block_given?
+ to_a.count { |*block_args| yield(*block_args) }
+ else
+ calculate(:count, column_name)
+ end
end
# Calculates the average value on a given column. Returns +nil+ if there's
@@ -89,7 +93,7 @@ module ActiveRecord
#
# There are two basic forms of output:
#
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # * Single aggregate value: The single value is type cast to Integer for COUNT, Float
# for AVG, and the given column's type for everything else.
#
# * Grouped values: This returns an ordered hash of the values and groups them. It
@@ -155,15 +159,7 @@ module ActiveRecord
# See also #ids.
#
def pluck(*column_names)
- column_names.map! do |column_name|
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
- attribute_alias(column_name)
- else
- column_name.to_s
- end
- end
-
- if loaded? && (column_names - @klass.column_names).empty?
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
return @records.pluck(*column_names)
end
@@ -172,7 +168,7 @@ module ActiveRecord
else
relation = spawn
relation.select_values = column_names.map { |cn|
- columns_hash.key?(cn) ? arel_table[cn] : cn
+ @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
}
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
result.cast_values(klass.attribute_types)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e4e5d63006..2484cb3264 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -1,4 +1,3 @@
-require 'set'
require 'active_support/concern'
module ActiveRecord
@@ -36,8 +35,9 @@ module ActiveRecord
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
- :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :shuffle, :split, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3cbb12a09d..d255cad91b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -42,10 +42,10 @@ module ActiveRecord
# Person.find_by(name: 'Spartacus', rating: 4)
# # returns the first item or nil.
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_create
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
# # returns the first item or creates it and returns it.
#
# ==== Alternatives for #find
@@ -145,15 +145,21 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- if limit
- if order_values.empty? && primary_key
- order(arel_table[primary_key].desc).limit(limit).reverse
- else
- to_a.last(limit)
- end
- else
- find_last
- end
+ return find_last(limit) if loaded? || limit_value
+
+ result = limit(limit || 1)
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
+ result = result.reverse_order!
+
+ limit ? result.reverse : result.first
+ rescue ActiveRecord::IrreversibleOrderError
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
+ Finding a last element by loading the relation when SQL ORDER
+ can not be reversed is deprecated.
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
+ Please call `to_a.last` if you still want to load the relation.
+ WARNING
+ find_last(limit)
end
# Same as #last but raises ActiveRecord::RecordNotFound if no record
@@ -242,6 +248,38 @@ module ActiveRecord
find_nth! 41
end
+ # Find the third-to-last record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people
+ # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
+ # Person.where(["user_name = :u", { u: user_name }]).third_to_last
+ def third_to_last
+ find_nth_from_last 3
+ end
+
+ # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
+ # is found.
+ def third_to_last!
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ end
+
+ # Find the second-to-last record.
+ # If no order is defined it will order by primary key.
+ #
+ # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people
+ # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
+ # Person.where(["user_name = :u", { u: user_name }]).second_to_last
+ def second_to_last
+ find_nth_from_last 2
+ end
+
+ # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
+ # is found.
+ def second_to_last!
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ end
+
# Returns true if a record exists in the table that matches the +id+ or
# conditions given, or false otherwise. The argument can take six forms:
#
@@ -274,13 +312,13 @@ module ActiveRecord
conditions = conditions.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `exists?`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
return false if !conditions
- relation = apply_join_dependency(self, construct_join_dependency)
+ relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false))
return false if ActiveRecord::NullRelation === relation
relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1)
@@ -295,10 +333,12 @@ module ActiveRecord
end
connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false
+ rescue RangeError
+ false
end
# This method is called whenever no records are found with either a single
- # id or multiple ids and raises a ActiveRecord::RecordNotFound exception.
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
#
# The error message is different depending on whether a single id or
# multiple ids are provided. If multiple ids are provided, then the number
@@ -354,21 +394,13 @@ module ActiveRecord
end
end
- def construct_join_dependency(joins = [])
+ def construct_join_dependency(joins = [], eager_loading: true)
including = eager_load_values + includes_values
- ActiveRecord::Associations::JoinDependency.new(@klass, including, joins)
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading)
end
def construct_relation_for_association_calculations
- from = arel.froms.first
- if Arel::Table === from
- apply_join_dependency(self, construct_join_dependency(joins_values))
- else
- # FIXME: as far as I can tell, `from` will always be an Arel::Table.
- # There are no tests that test this branch, but presumably it's
- # possible for `from` to be a list?
- apply_join_dependency(self, construct_join_dependency(from))
- end
+ apply_join_dependency(self, construct_join_dependency(joins_values))
end
def apply_join_dependency(relation, join_dependency)
@@ -429,7 +461,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -468,7 +500,7 @@ module ActiveRecord
def find_some_ordered(ids)
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
- result = except(:limit, :offset).where(primary_key => ids).to_a
+ result = except(:limit, :offset).where(primary_key => ids).records
if result.size == ids.size
pk_type = @klass.type_for_attribute(primary_key)
@@ -484,25 +516,24 @@ module ActiveRecord
if loaded?
@records.first
else
- @take ||= limit(1).to_a.first
+ @take ||= limit(1).records.first
end
end
def find_nth(index, offset = nil)
+ # TODO: once the offset argument is removed we rely on offset_index
+ # within find_nth_with_limit, rather than pass it in via
+ # find_nth_with_limit_and_offset
+ if offset
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing an offset argument to find_nth is deprecated,
+ please use Relation#offset instead.
+ MSG
+ end
if loaded?
@records[index]
else
- # TODO: once the offset argument is removed we rely on offset_index
- # within find_nth_with_limit, rather than pass it in via
- # find_nth_with_limit_and_offset
- if offset
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing an offset argument to find_nth is deprecated,
- please use Relation#offset instead.
- MSG
- else
- offset = offset_index
- end
+ offset ||= offset_index
@offsets[offset + index] ||= find_nth_with_limit_and_offset(index, 1, offset: offset).first
end
end
@@ -515,7 +546,7 @@ module ActiveRecord
# TODO: once the offset argument is removed from find_nth,
# find_nth_with_limit_and_offset can be merged into this method
relation = if order_values.empty? && primary_key
- order(arel_table[primary_key].asc)
+ order(arel_attribute(primary_key).asc)
else
self
end
@@ -524,16 +555,22 @@ module ActiveRecord
relation.limit(limit).to_a
end
- def find_last
+ def find_nth_from_last(index)
if loaded?
- @records.last
+ @records[-index]
else
- @last ||=
- if limit_value
- to_a.last
- else
- reverse_order.limit(1).to_a.first
- end
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
end
end
@@ -547,5 +584,9 @@ module ActiveRecord
find_nth_with_limit(index, limit)
end
end
+
+ def find_last(limit)
+ limit ? records.last(limit) : records.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 0f88791d92..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -5,6 +5,7 @@ module ActiveRecord
require 'active_record/relation/predicate_builder/base_handler'
require 'active_record/relation/predicate_builder/basic_object_handler'
require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/polymorphic_array_handler'
require 'active_record/relation/predicate_builder/range_handler'
require 'active_record/relation/predicate_builder/relation_handler'
@@ -22,6 +23,7 @@ module ActiveRecord
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
+ register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -40,10 +42,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if table.associated_with?(column)
- value = AssociationQueryValue.new(table.associated_table(column), value)
- end
-
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
build(table.arel_attribute(column), value)
end
@@ -137,9 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
+ end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index e81be63cd3..413cb9fd84 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -1,6 +1,17 @@
module ActiveRecord
class PredicateBuilder
class AssociationQueryHandler # :nodoc:
+ def self.value_for(table, column, value)
+ associated_table = table.associated_table(column)
+ klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base)
+ PolymorphicArrayValue
+ else
+ AssociationQueryValue
+ end
+
+ klass.new(associated_table, value)
+ end
+
def initialize(predicate_builder)
@predicate_builder = predicate_builder
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
new file mode 100644
index 0000000000..b6c6240343
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
@@ -0,0 +1,57 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ table = value.associated_table
+ queries = value.type_to_ids_mapping.map do |type, ids|
+ { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
+ end
+
+ predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
+
+ if predicates.size > 1
+ type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
+ type_and_ids_predicates.inject(&:or)
+ else
+ predicates.first
+ end
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class PolymorphicArrayValue # :nodoc:
+ attr_reader :associated_table, :values
+
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ private
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ value.class.base_class
+ end
+
+ def convert_to_id(value)
+ value._read_attribute(primary_key(value))
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
index 063150958a..8a910a82fe 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class RelationHandler # :nodoc:
def call(attribute, value)
if value.select_values.empty?
- value = value.select(value.klass.arel_table[value.klass.primary_key])
+ value = value.select(value.arel_attribute(value.klass.primary_key))
end
attribute.in(value.arel)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 716b1e8505..8a87015e44 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -99,22 +99,28 @@ module ActiveRecord
end
def bound_attributes
- result = from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds
if limit_value && !string_containing_comma?(limit_value)
- result << Attribute.with_cast_value(
+ limit_bind = Attribute.with_cast_value(
"LIMIT".freeze,
connection.sanitize_limit(limit_value),
Type::Value.new,
)
end
if offset_value
- result << Attribute.with_cast_value(
+ offset_bind = Attribute.with_cast_value(
"OFFSET".freeze,
offset_value.to_i,
Type::Value.new,
)
end
- result
+ connection.combine_bind_parameters(
+ from_clause: from_clause.binds,
+ join_clause: arel.bind_values,
+ where_clause: where_clause.binds,
+ having_clause: having_clause.binds,
+ limit: limit_bind,
+ offset: offset_bind,
+ )
end
FROZEN_EMPTY_HASH = {}.freeze
@@ -652,9 +658,13 @@ module ActiveRecord
# present). Neither relation may have a #limit, #offset, or #distinct set.
#
# Post.where("id = 1").or(Post.where("author_id = 3"))
- # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
+ # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3))
#
def or(other)
+ unless other.is_a? Relation
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
+ end
+
spawn.or!(other)
end
@@ -998,12 +1008,6 @@ module ActiveRecord
self.send(unscope_code, result)
end
- def association_for_table(table_name)
- table_name = table_name.to_s
- @klass._reflect_on_association(table_name) ||
- @klass._reflect_on_association(table_name.singularize)
- end
-
def build_from
opts = from_clause.value
name = from_clause.name
@@ -1093,8 +1097,8 @@ module ActiveRecord
def arel_columns(columns)
columns.map do |field|
- if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value
- arel_table[field]
+ if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
+ arel_attribute(field)
elsif Symbol === field
connection.quote_table_name(field.to_s)
else
@@ -1104,14 +1108,23 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
- order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
+ if order_query.empty?
+ return [arel_attribute(primary_key).desc] if primary_key
+ raise IrreversibleOrderError,
+ "Relation has no current order and table has no primary key to be used as default order"
+ end
order_query.flat_map do |o|
case o
+ when Arel::Attribute
+ o.desc
when Arel::Nodes::Ordering
o.reverse
when String
- o.to_s.split(',').map! do |s|
+ if does_not_support_reverse?(o)
+ raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
+ end
+ o.split(',').map! do |s|
s.strip!
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
end
@@ -1121,6 +1134,13 @@ module ActiveRecord
end
end
+ def does_not_support_reverse?(order)
+ #uses sql function with multiple arguments
+ order =~ /\([^()]*,[^()]*\)/ ||
+ # uses "nulls first" like construction
+ order =~ /nulls (first|last)\Z/i
+ end
+
def build_order(arel)
orders = order_values.uniq
orders.reject!(&:blank?)
@@ -1156,12 +1176,10 @@ module ActiveRecord
order_args.map! do |arg|
case arg
when Symbol
- arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg)
- table[arg].asc
+ arel_attribute(arg).asc
when Hash
arg.map { |field, dir|
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
- table[field].send(dir.downcase)
+ arel_attribute(field).send(dir.downcase)
}
else
arg
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 67d7f83cb4..d5c18a2a4a 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# This is mainly intended for sharing common conditions between multiple associations.
def merge(other)
if other.is_a?(Array)
- to_a & other
+ records & other
elsif other
spawn.merge!(other)
else
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 2c2d6cfa47..89396b518c 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -158,8 +158,9 @@ module ActiveRecord
end
end
+ ARRAY_WITH_EMPTY_STRING = ['']
def non_empty_predicates
- predicates - ['']
+ predicates - ARRAY_WITH_EMPTY_STRING
end
def wrap_sql_literal(node)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 2bfc5ff7ae..a9e1fd0dad 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
# Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a ORDER clause.
+ # them into a valid SQL fragment for an ORDER clause.
#
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
# # => "field(id, 1,3,2)"
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 65005bd44b..d769376d1a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -50,10 +50,6 @@ module ActiveRecord
def header(stream)
define_params = @version ? "version: #{@version}" : ""
- if stream.respond_to?(:external_encoding) && stream.external_encoding
- stream.puts "# encoding: #{stream.external_encoding.name}"
- end
-
stream.puts <<HEADER
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
@@ -104,10 +100,7 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table).map do |column|
- column.instance_variable_set(:@table_name, table)
- column
- end
+ columns = @connection.columns(table)
begin
tbl = StringIO.new
@@ -126,7 +119,7 @@ HEADER
tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
pkcol = columns.detect { |c| c.name == pk }
pkcolspec = @connection.column_spec_for_primary_key(pkcol)
- if pkcolspec
+ if pkcolspec.present?
pkcolspec.each do |key, value|
tbl.print ", #{key}: #{value}"
end
@@ -141,6 +134,10 @@ HEADER
table_options = @connection.table_options(table)
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+ if comment = @connection.table_comment(table).presence
+ tbl.print ", comment: #{comment.inspect}"
+ end
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -178,7 +175,7 @@ HEADER
tbl.puts
end
- indexes(table, tbl)
+ indexes_in_create(table, tbl)
tbl.puts " end"
tbl.puts
@@ -194,31 +191,47 @@ HEADER
stream
end
+ # Keep it for indexing materialized views
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
- statement_parts = [
- "t.index #{index.columns.inspect}",
- "name: #{index.name.inspect}",
- ]
- statement_parts << 'unique: true' if index.unique
-
- index_lengths = (index.lengths || []).compact
- statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
-
- index_orders = index.orders || {}
- statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
- statement_parts << "where: #{index.where.inspect}" if index.where
- statement_parts << "using: #{index.using.inspect}" if index.using
- statement_parts << "type: #{index.type.inspect}" if index.type
-
- " #{statement_parts.join(', ')}"
+ table_name = remove_prefix_and_suffix(index.table).inspect
+ " add_index #{([table_name]+index_parts(index)).join(', ')}"
end
stream.puts add_index_statements.sort.join("\n")
+ stream.puts
+ end
+ end
+
+ def indexes_in_create(table, stream)
+ if (indexes = @connection.indexes(table)).any?
+ index_statements = indexes.map do |index|
+ " t.index #{index_parts(index).join(', ')}"
+ end
+ stream.puts index_statements.sort.join("\n")
end
end
+ def index_parts(index)
+ index_parts = [
+ index.columns.inspect,
+ "name: #{index.name.inspect}",
+ ]
+ index_parts << 'unique: true' if index.unique
+
+ index_lengths = (index.lengths || []).compact
+ index_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
+
+ index_orders = index.orders || {}
+ index_parts << "order: #{index.orders.inspect}" if index_orders.any?
+ index_parts << "where: #{index.where.inspect}" if index.where
+ index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "type: #{index.type.inspect}" if index.type
+ index_parts << "comment: #{index.comment.inspect}" if index.comment
+ index_parts
+ end
+
def foreign_keys(table, stream)
if (foreign_keys = @connection.foreign_keys(table)).any?
add_foreign_key_statements = foreign_keys.map do |foreign_key|
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index ee4c71f304..b6cb233e03 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -16,31 +16,22 @@ module ActiveRecord
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
end
- def index_name
- "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
- end
-
def table_exists?
ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) }
end
- def create_table(limit=nil)
+ def create_table
unless table_exists?
- version_options = {null: false}
- version_options[:limit] = limit if limit
+ version_options = connection.internal_string_options_for_primary_key
connection.create_table(table_name, id: false) do |t|
- t.column :version, :string, version_options
- t.index :version, unique: true, name: index_name
+ t.string :version, version_options
end
end
end
def drop_table
- if table_exists?
- connection.remove_index table_name, name: index_name
- connection.drop_table(table_name)
- end
+ connection.drop_table table_name, if_exists: true
end
def normalize_migration_number(number)
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index f6b6768ce3..9eab59ac78 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -115,7 +115,8 @@ module ActiveRecord
base_rel ||= relation
evaluate_default_scope do
default_scopes.inject(base_rel) do |default_scope, scope|
- default_scope.merge(base_rel.scoping { scope.call })
+ scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
+ default_scope.merge(base_rel.instance_exec(&scope))
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 103569c84d..5395bd6076 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -151,6 +151,7 @@ module ActiveRecord
"a class method with the same name."
end
+ valid_scope_name?(name)
extension = Module.new(&block) if block
if body.respond_to?(:to_proc)
@@ -169,6 +170,15 @@ module ActiveRecord
end
end
end
+
+ protected
+
+ def valid_scope_name?(name)
+ if respond_to?(name, true)
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index f6b0efb88a..6c896ccea6 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -106,7 +106,7 @@ module ActiveRecord
sql = query_builder.sql_for bind_values, connection
- klass.find_by_sql sql, bind_values
+ klass.find_by_sql(sql, bind_values, preparable: true)
end
alias :call :execute
end
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index b3644bf569..d9acb1a1dc 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -30,14 +30,19 @@ module ActiveRecord
module ClassMethods
def suppress(&block)
+ previous_state = SuppressorRegistry.suppressed[name]
SuppressorRegistry.suppressed[name] = true
yield
ensure
- SuppressorRegistry.suppressed[name] = false
+ SuppressorRegistry.suppressed[name] = previous_state
end
end
- def create_or_update(*args) # :nodoc:
+ def save(*) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+
+ def save!(*) # :nodoc:
SuppressorRegistry.suppressed[self.class.name] ? true : super
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index f9bb1cf5e0..a1326aa359 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -22,7 +22,11 @@ module ActiveRecord
end
def arel_attribute(column_name)
- arel_table[column_name]
+ if klass
+ klass.arel_attribute(column_name, arel_table)
+ else
+ arel_table[column_name]
+ end
end
def type(column_name)
@@ -40,7 +44,8 @@ module ActiveRecord
def associated_table(table_name)
return self if table_name == arel_table.name
- association = klass._reflect_on_association(table_name)
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
+
if association && !association.polymorphic?
association_klass = association.klass
arel_table = association_klass.arel_table.alias(table_name)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 8f52e9068a..e3e665e149 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -107,8 +107,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
+ $stdout.puts "Created database '#{configuration['database']}'"
rescue DatabaseAlreadyExists
- $stderr.puts "#{configuration['database']} already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists"
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -116,7 +117,11 @@ module ActiveRecord
end
def create_all
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
each_local_configuration { |configuration| create configuration }
+ if old_pool
+ ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash)
+ end
end
def create_current(environment = env)
@@ -129,11 +134,12 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ $stdout.puts "Dropped database '#{configuration['database']}'"
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't drop #{configuration['database']}"
+ $stderr.puts "Couldn't drop database '#{configuration['database']}'"
raise
end
@@ -155,6 +161,7 @@ module ActiveRecord
Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
end
+ ActiveRecord::Base.clear_cache!
ensure
Migration.verbose = verbose_was
end
@@ -278,8 +285,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- # add test environment only if no RAILS_ENV was specified.
- environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 4cf32c2abe..b1a0ad0115 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -131,7 +131,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
'sslca' => '--ssl-ca',
'sslcert' => '--ssl-cert',
'sslcapath' => '--ssl-capath',
- 'sslcipher' => '--ssh-cipher',
+ 'sslcipher' => '--ssl-cipher',
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 8b4874044c..b19ab57ee4 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Tasks # :nodoc:
class PostgreSQLDatabaseTasks # :nodoc:
DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+ ON_ERROR_STOP_1 = 'ON_ERROR_STOP=1'.freeze
delegate :connection, :establish_connection, :clear_active_connections!,
to: ActiveRecord::Base
@@ -67,7 +68,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- args = [ '-q', '-f', filename, configuration['database'] ]
+ args = [ '-v', ON_ERROR_STOP_1, '-q', '-f', filename, configuration['database'] ]
run_cmd('psql', args, 'loading' )
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e210e94f00..4911d93dd9 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -61,7 +61,7 @@ module ActiveRecord
register(:binary, Type::Binary, override: false)
register(:boolean, Type::Boolean, override: false)
register(:date, Type::Date, override: false)
- register(:date_time, Type::DateTime, override: false)
+ register(:datetime, Type::DateTime, override: false)
register(:decimal, Type::Decimal, override: false)
register(:float, Type::Float, override: false)
register(:integer, Type::Integer, override: false)
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 097d1bd363..513c938088 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,11 +17,7 @@ module ActiveRecord
end
def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
+ ::ActiveSupport::JSON.encode(value)
end
def accessor
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4ff0740cfb..a3a5241780 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -32,7 +32,7 @@ module ActiveRecord
def changed_in_place?(raw_old_value, value)
return false if value.nil?
- raw_new_value = serialize(value)
+ raw_new_value = encoded(value)
raw_old_value.nil? != raw_new_value.nil? ||
subtype.changed_in_place?(raw_old_value, raw_new_value)
end
@@ -52,6 +52,12 @@ module ActiveRecord
def default_value?(value)
value == coder.load(nil)
end
+
+ def encoded(value)
+ unless default_value?(value)
+ coder.dump(value)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 70988d84ff..7da49e43c7 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -2,6 +2,18 @@ module ActiveRecord
module Type
class Time < ActiveModel::Type::Time
include Internal::Timezone
+
+ class Value < DelegateClass(::Time) # :nodoc:
+ end
+
+ def serialize(value)
+ case value = super
+ when ::Time
+ Value.new(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 6677e6dc5f..ecaf04e39e 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
# Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
- # will raise a ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
+ # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise_validation_error
end
diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb
index 2e19e6dc5c..641d041f3d 100644
--- a/activerecord/lib/active_record/validations/absence.rb
+++ b/activerecord/lib/active_record/validations/absence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index 69e048eef1..0e0cebce4a 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,23 +2,11 @@ module ActiveRecord
module Validations
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record) || associations_are_dirty?(record)
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
end
super
end
-
- def associations_are_dirty?(record)
- attributes.any? do |attribute|
- value = record.read_attribute_for_validation(attribute)
- if value.respond_to?(:loaded?) && value.loaded?
- value.target.any?(&:marked_for_destruction?)
- else
- false
- end
- end
- end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 7e85ed43ac..ad82ea66c4 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a376e2a17f..ec9f498c40 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,15 +11,14 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
- return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
+ if record.persisted?
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id)
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
@@ -57,21 +56,15 @@ module ActiveRecord
value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
- attribute_name = attribute.to_s
-
# the attribute may be an aliased attribute
- if klass.attribute_aliases[attribute_name]
- attribute = klass.attribute_aliases[attribute_name]
- attribute_name = attribute.to_s
+ if klass.attribute_alias?(attribute)
+ attribute = klass.attribute_alias(attribute)
end
+ attribute_name = attribute.to_s
+
column = klass.columns_hash[attribute_name]
cast_type = klass.type_for_attribute(attribute_name)
- value = cast_type.serialize(value)
- value = klass.connection.type_cast(value)
- if value.is_a?(String) && column.limit
- value = value.to_s[0, column.limit]
- end
comparison = if !options[:case_sensitive] && !value.nil?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
@@ -82,11 +75,9 @@ module ActiveRecord
if value.nil?
klass.unscoped.where(comparison)
else
- bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new)
+ bind = Relation::QueryAttribute.new(attribute_name, value, cast_type)
klass.unscoped.where(comparison, bind)
end
- rescue RangeError
- klass.none
end
def scope_relation(record, table, relation)
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 107f107dc4..481c70201b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -19,7 +19,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
def change
create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t|
<%- attributes.each do |attribute| -%>
+ <%- if attribute.reference? -%>
+ t.references :<%= attribute.name %><%= attribute.inject_options %>
+ <%- else -%>
<%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
<%- end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 7395839fca..0d72913258 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -21,11 +21,13 @@ module ActiveRecord
end
def create_model_file
+ generate_application_record
template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
end
def create_module_file
return if regular_class_path.empty?
+ generate_application_record
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
@@ -37,26 +39,29 @@ module ActiveRecord
attributes.select { |a| !a.reference? && a.has_index? }
end
+ # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
+ def generate_application_record
+ if self.behavior == :invoke && !application_record_exist?
+ template 'application_record.rb', application_record_file_name
+ end
+ end
+
# Used by the migration template to determine the parent name of the model
def parent_class_name
- options[:parent] || determine_default_parent_class
+ options[:parent] || 'ApplicationRecord'
end
- def determine_default_parent_class
- application_record = nil
-
- in_root do
- application_record = if mountable_engine?
- File.exist?("app/models/#{namespaced_path}/application_record.rb")
- else
- File.exist?('app/models/application_record.rb')
- end
- end
+ def application_record_exist?
+ file_exist = nil
+ in_root { file_exist = File.exist?(application_record_file_name) }
+ file_exist
+ end
- if application_record
- "ApplicationRecord"
+ def application_record_file_name
+ @application_record_file_name ||= if mountable_engine?
+ "app/models/#{namespaced_path}/application_record.rb"
else
- "ActiveRecord::Base"
+ 'app/models/application_record.rb'
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
new file mode 100644
index 0000000000..60050e0bf8
--- /dev/null
+++ b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
@@ -0,0 +1,5 @@
+<% module_namespacing do -%>
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
+<% end -%>
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0ee147cdba..34e3bc9d66 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require "models/book"
require "models/post"
require "models/author"
+require "models/event"
module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
@@ -11,7 +12,8 @@ module ActiveRecord
##
# PostgreSQL does not support null bytes in strings
- unless current_adapter?(:PostgreSQLAdapter)
+ unless current_adapter?(:PostgreSQLAdapter) ||
+ (current_adapter?(:SQLite3Adapter) && !ActiveRecord::Base.connection.prepared_statements)
def test_update_prepared_statement
b = Book.create(name: "my \x00 book")
b.reload
@@ -22,6 +24,12 @@ module ActiveRecord
end
end
+ def test_create_record_with_pk_as_zero
+ Book.create(id: 0)
+ assert_equal 0, Book.find(0).id
+ assert_nothing_raised { Book.destroy(0) }
+ end
+
def test_tables
tables = nil
ActiveSupport::Deprecation.silence { tables = @connection.tables }
@@ -79,6 +87,17 @@ module ActiveRecord
@connection.remove_index(:accounts, :name => idx_name) rescue nil
end
+ def test_remove_index_when_name_and_wrong_column_name_specified
+ index_name = "accounts_idx"
+
+ @connection.add_index :accounts, :firm_id, :name => index_name
+ assert_raises ArgumentError do
+ @connection.remove_index :accounts, :name => index_name, :column => :wrong_column_name
+ end
+ ensure
+ @connection.remove_index(:accounts, :name => index_name)
+ end
+
def test_current_database
if @connection.respond_to?(:current_database)
assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database
@@ -193,6 +212,14 @@ module ActiveRecord
assert_not_nil error.cause
end
+
+ def test_value_limit_violations_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::ValueTooLong) do
+ Event.create(title: 'abcdefgh')
+ end
+
+ assert_not_nil error.cause
+ end
end
def test_disable_referential_integrity
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 99f97c7914..95d1f6b8a3 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -63,14 +63,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
assert_equal expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)) ) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
@@ -155,7 +155,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
t.index :zip
end
diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
index 8575df9e43..739bb275ce 100644
--- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb
@@ -43,11 +43,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "test type casting without emulated booleans" do
@@ -55,11 +60,16 @@ class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase
boolean = BooleanType.create!(archived: true, published: true)
attributes = boolean.reload.attributes_before_type_cast
-
assert_equal 1, attributes["archived"]
assert_equal "1", attributes["published"]
+ boolean = BooleanType.create!(archived: false, published: false)
+ attributes = boolean.reload.attributes_before_type_cast
+ assert_equal 0, attributes["archived"]
+ assert_equal "0", attributes["published"]
+
assert_equal 1, @connection.type_cast(true)
+ assert_equal 0, @connection.type_cast(false)
end
test "with booleans stored as 1 and 0" do
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 963116f08a..9cb05119a2 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -51,4 +51,13 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_no_match(/binary/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_binary_column
+ CollationTest.validates_uniqueness_of(:binary_column, case_sensitive: true)
+ CollationTest.create!(binary_column: 'A')
+ invalid = CollationTest.new(binary_column: 'A')
+ queries = assert_sql { invalid.save }
+ bin_uniqueness_query = queries.detect { |q| q.match(/binary_column/) }
+ assert_no_match(/\bBINARY\b/, bin_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
index 668c07dacb..c8028b6b36 100644
--- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb
@@ -49,6 +49,6 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase
test "schema dump includes collation" do
output = dump_table_schema("charset_collations")
assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output
- assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output
+ assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 575138eb2a..fe610ae951 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -69,40 +69,48 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_mysql_default_in_strict_mode
- result = @connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ result = @connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_match %r(STRICT_ALL_TABLES), result
end
def test_mysql_strict_mode_disabled
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:strict => false}))
- result = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal [['']], result.rows
+ ActiveRecord::Base.establish_connection(orig_connection.merge(strict: false))
+ result = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_no_match %r(STRICT_ALL_TABLES), result
+ end
+ end
+
+ def test_mysql_strict_mode_specified_default
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.merge(strict: :default))
+ global_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@GLOBAL.sql_mode")
+ session_sql_mode = ActiveRecord::Base.connection.select_value("SELECT @@SESSION.sql_mode")
+ assert_equal global_sql_mode, session_sql_mode
+ end
+ end
+
+ def test_mysql_sql_mode_variable_overrides_strict_mode
+ run_without_connection do |orig_connection|
+ ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
+ result = ActiveRecord::Base.connection.select_value('SELECT @@SESSION.sql_mode')
+ assert_no_match %r(STRICT_ALL_TABLES), result
end
end
def test_passing_arbitary_flags_to_adapter
- run_without_connection do |orig_connection|
+ run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: Mysql2::Client::COMPRESS}))
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
-
+
def test_passing_flags_by_array_to_adapter
- run_without_connection do |orig_connection|
+ run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge({flags: ['COMPRESS'] }))
assert_equal ["COMPRESS", "FOUND_ROWS"], ActiveRecord::Base.connection.raw_connection.query_options[:flags]
end
end
-
- def test_mysql_strict_mode_specified_default
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({strict: :default}))
- global_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@GLOBAL.sql_mode"
- session_sql_mode = ActiveRecord::Base.connection.exec_query "SELECT @@SESSION.sql_mode"
- assert_equal global_sql_mode.rows, session_sql_mode.rows
- end
- end
def test_mysql_set_session_variable
run_without_connection do |orig_connection|
@@ -112,14 +120,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
- def test_mysql_sql_mode_variable_overrides_strict_mode
- run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' }))
- result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode'
- assert_not_equal [['STRICT_ALL_TABLES']], result.rows
- end
- end
-
def test_mysql_set_session_variable_to_default
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:default_week_format => :default}}))
@@ -144,7 +144,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_get_and_release_advisory_lock
- lock_name = "test_lock_name"
+ lock_name = "test lock'n'name"
got_lock = @connection.get_advisory_lock(lock_name)
assert got_lock, "get_advisory_lock should have returned true but it didn't"
@@ -159,7 +159,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_release_non_existent_advisory_lock
- lock_name = "fake_lock_name"
+ lock_name = "fake lock'n'name"
released_non_existent_lock = @connection.release_advisory_lock(lock_name)
assert_equal released_non_existent_lock, false,
'expected release_advisory_lock to return false when there was no lock to release'
@@ -168,6 +168,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
protected
def test_lock_free(lock_name)
- @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1
+ @connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
new file mode 100644
index 0000000000..e349c67c93
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -0,0 +1,45 @@
+require "cases/helper"
+
+class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ end
+
+ test 'microsecond precision for MySQL gte 5.6.4' do
+ stub_version '5.6.4'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MySQL lt 5.6.4' do
+ stub_version '5.6.3'
+ assert_no_microsecond_precision
+ end
+
+ test 'microsecond precision for MariaDB gte 5.3.0' do
+ stub_version '5.5.5-10.1.8-MariaDB-log'
+ assert_microsecond_precision
+ end
+
+ test 'no microsecond precision for MariaDB lt 5.3.0' do
+ stub_version '5.2.9-MariaDB'
+ assert_no_microsecond_precision
+ end
+
+ private
+ def assert_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\.000001\z/)
+ end
+
+ def assert_no_microsecond_precision
+ assert_match_quoted_microsecond_datetime(/\d\z/)
+ end
+
+ def assert_match_quoted_microsecond_datetime(match)
+ assert_match match, @connection.quoted_date(Time.now.change(usec: 1))
+ end
+
+ def stub_version(full_version_string)
+ @connection.stubs(:full_version).returns(full_version_string)
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index bb89e893e0..35dbc76d1b 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -18,4 +18,9 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
column = EnumTest.columns_hash['enum_column']
assert_not column.unsigned?
end
+
+ def test_should_not_be_bigint
+ column = EnumTest.columns_hash['enum_column']
+ assert_not column.bigint?
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 4fc7414b18..b783b5fcd9 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class Mysql2Adapter
- class ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %r(developers |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
+ assert_match %r(audit_logs |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb
index c8c933af5e..9c3fef1b59 100644
--- a/activerecord/test/cases/adapters/mysql2/json_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/json_test.rb
@@ -161,12 +161,19 @@ class Mysql2JSONTest < ActiveRecord::Mysql2TestCase
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
index 4efd728754..61dd0828d0 100644
--- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb
@@ -1,10 +1,33 @@
require "cases/helper"
+require "support/ddl_helper"
class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
+ include DdlHelper
+
def setup
@conn = ActiveRecord::Base.connection
end
+ def test_exec_query_nothing_raises_with_no_result_queries
+ assert_nothing_raised do
+ with_example_table do
+ @conn.exec_query('INSERT INTO ex (number) VALUES (1)')
+ @conn.exec_query('DELETE FROM ex WHERE number = 1')
+ end
+ end
+ end
+
+ def test_valid_column
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+ end
+
+ def test_invalid_column
+ assert_not @conn.valid_type?(:foobar)
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@conn.columns_for_distinct("posts.id", [])
@@ -41,4 +64,10 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase
assert_equal "posts.id, posts.created_at AS alias_0",
@conn.columns_for_distinct("posts.id", [order])
end
+
+ private
+
+ def with_example_table(definition = 'id int auto_increment primary key, number int, data varchar(255)', &block)
+ super(@conn, 'ex', definition, &block)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/quoting_test.rb b/activerecord/test/cases/adapters/mysql2/quoting_test.rb
deleted file mode 100644
index 2de7e1b526..0000000000
--- a/activerecord/test/cases/adapters/mysql2/quoting_test.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require "cases/helper"
-
-class Mysql2QuotingTest < ActiveRecord::Mysql2TestCase
- setup do
- @connection = ActiveRecord::Base.connection
- end
-
- test 'quoted date precision for gte 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.4')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_match(/\.000001\z/, @connection.quoted_date(t))
- end
-
- test 'quoted date precision for lt 5.6.4' do
- @connection.stubs(:full_version).returns('5.6.3')
- @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
- t = Time.now.change(usec: 1)
- assert_no_match(/\.000001\z/, @connection.quoted_date(t))
- end
-end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 9a9a4fed42..7c89fda582 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -33,11 +33,6 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
end
end
- def test_key_limit_max_matches_max
- assert_equal ActiveRecord::InternalMetadata::KEY_LIMIT ,ActiveRecord::ConnectionAdapters::Mysql2Adapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN
- end
-
-
private
def with_encoding_utf8mb4
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
new file mode 100644
index 0000000000..0e37c70e5c
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -0,0 +1,62 @@
+require "cases/helper"
+require 'support/connection_helper'
+
+module ActiveRecord
+ class Mysql2TransactionTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ class Sample < ActiveRecord::Base
+ self.table_name = 'samples'
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.clear_cache!
+
+ @connection.transaction do
+ @connection.drop_table 'samples', if_exists: true
+ @connection.create_table('samples') do |t|
+ t.integer 'value'
+ end
+ end
+
+ Sample.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'samples', if_exists: true
+ end
+
+ test "raises error when a serialization failure occurs" do
+ assert_raises(ActiveRecord::TransactionSerializationError) do
+ thread = Thread.new do
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+ end
+ end
+
+ sleep 0.1
+
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+
+ sleep 1
+ end
+
+ thread.join
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index c95a64cc16..3df11ce11b 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -28,10 +28,10 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
end
test "minus value is out of range" do
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_integer: -10)
end
- assert_raise(RangeError) do
+ assert_raise(ActiveModel::RangeError) do
UnsignedType.create(unsigned_bigint: -10)
end
assert_raise(ActiveRecord::StatementInvalid) do
@@ -58,7 +58,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
test "schema dump includes unsigned option" do
schema = dump_table_schema "unsigned_types"
assert_match %r{t.integer\s+"unsigned_integer",\s+unsigned: true$}, schema
- assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema
+ assert_match %r{t.bigint\s+"unsigned_bigint",\s+unsigned: true$}, schema
assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema
assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index ed44bf7362..439e2ce6f7 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -28,7 +28,13 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false }
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
- assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+ assert_equal expected, add_index(:people, :last_name, unique: true, where: "state = 'active'")
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" (lower(last_name)))
+ assert_equal expected, add_index(:people, 'lower(last_name)', unique: true)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name_varchar_pattern_ops" ON "people" (last_name varchar_pattern_ops))
+ assert_equal expected, add_index(:people, 'last_name varchar_pattern_ops', unique: true)
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
@@ -39,16 +45,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, using: type, unique: true, where: "state = 'active'")
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_lower_last_name" ON "people" USING #{type} (lower(last_name)))
+ assert_equal expected, add_index(:people, 'lower(last_name)', using: type, unique: true)
end
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :copy)
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
- assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
-
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
- assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists?
end
@@ -69,6 +76,11 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove
end
+ def test_remove_index_when_name_is_specified
+ expected = %(DROP INDEX CONCURRENTLY "index_people_on_last_name")
+ assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently)
+ end
+
private
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index 6f72fa6e0f..cec6081aec 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -57,9 +57,11 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
assert_match %r{t\.bit_varying\s+"a_bit_varying",\s+limit: 4,\s+default: "0011"$}, output
end
- def test_assigning_invalid_hex_string_raises_exception
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
- assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "FF" }
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_assigning_invalid_hex_string_raises_exception
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit: "FF" }
+ assert_raises(ActiveRecord::StatementInvalid) { PostgresqlBitString.create! a_bit_varying: "F" }
+ end
end
def test_roundtrip
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index b6bb1929e6..7adc070430 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -1,6 +1,9 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
class ByteaDataType < ActiveRecord::Base
self.table_name = 'bytea_data_type'
end
@@ -122,4 +125,10 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
obj.reload
assert_equal "hello world", obj.serialized
end
+
+ def test_schema_dumping
+ output = dump_table_schema("bytea_data_type")
+ assert_match %r{t\.binary\s+"payload"$}, output
+ assert_match %r{t\.binary\s+"serialized"$}, output
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index d559de3e28..f8403bfe1a 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -125,14 +125,16 @@ module ActiveRecord
assert_equal 'SCHEMA', @subscriber.logged[0][1]
end
- def test_statement_key_is_logged
- bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
- @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
- name = @subscriber.payloads.last[:statement_name]
- assert name
- res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
- plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
- assert_operator plan.length, :>, 0
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_statement_key_is_logged
+ bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
+ @connection.exec_query('SELECT $1::integer', 'SQL', [bind], prepare: true)
+ name = @subscriber.payloads.last[:statement_name]
+ assert name
+ res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
+ plan = res.column_types['QUERY PLAN'].deserialize res.rows.first.first
+ assert_operator plan.length, :>, 0
+ end
end
# Must have PostgreSQL >= 9.2, or with_manual_interventions set to
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 4d0fd640aa..29bf2c15ea 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -6,15 +6,15 @@ class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
fixtures :developers
def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
assert_match %(QUERY PLAN), explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = \$?1), explain
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index b2a805333c..b56c226763 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -24,9 +24,9 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
return skip("no extension support")
end
- @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name
- @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix
- @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix
+ @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name
+ @old_table_name_prefix = ActiveRecord::Base.table_name_prefix
+ @old_table_name_suffix = ActiveRecord::Base.table_name_suffix
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
@@ -36,11 +36,11 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
end
def teardown
- ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix
- ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix
+ ActiveRecord::Base.table_name_prefix = @old_table_name_prefix
+ ActiveRecord::Base.table_name_suffix = @old_table_name_suffix
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
- ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name
+ ActiveRecord::SchemaMigration.table_name = @old_schema_migration_table_name
super
end
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index 9e250c2b7c..66f0a70394 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -104,6 +104,13 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
assert_equal ActiveRecord::Point.new(1, 2), p.x
end
+ def test_empty_string_assignment
+ assert_nothing_raised { PostgresqlPoint.new(x: "") }
+
+ p = PostgresqlPoint.new(x: "")
+ assert_equal nil, p.x
+ end
+
def test_array_of_points_round_trip
expected_value = [
ActiveRecord::Point.new(1, 2),
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index b3b121b4fb..663de680b5 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -38,7 +38,7 @@ module PostgresqlJSONSharedTestCases
end
def test_default
- @connection.add_column 'json_data_type', 'permissions', column_type, default: '{"users": "read", "posts": ["read", "write"]}'
+ @connection.add_column 'json_data_type', 'permissions', column_type, default: {"users": "read", "posts": ["read", "write"]}
JsonDataType.reset_column_information
assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.column_defaults['permissions'])
@@ -178,12 +178,19 @@ module PostgresqlJSONSharedTestCases
assert_not json.changed?
end
- def test_assigning_invalid_json
- json = JsonDataType.new
+ def test_assigning_string_literal
+ json = JsonDataType.create(payload: "foo")
+ assert_equal "foo", json.payload
+ end
- json.payload = 'foo'
+ def test_assigning_number
+ json = JsonDataType.create(payload: 1.234)
+ assert_equal 1.234, json.payload
+ end
- assert_nil json.payload
+ def test_assigning_boolean
+ json = JsonDataType.create(payload: true)
+ assert_equal true, json.payload
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index f1995b243a..9832df7839 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -5,6 +5,7 @@ require 'support/connection_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
include DdlHelper
include ConnectionHelper
@@ -53,62 +54,12 @@ module ActiveRecord
end
end
- def test_composite_primary_key
- with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do
- assert_nil @connection.primary_key('ex')
- end
- end
-
def test_primary_key_raises_error_if_table_not_found
assert_raises(ActiveRecord::StatementInvalid) do
@connection.primary_key('unobtainium')
end
end
- def test_insert_sql_with_proprietary_returning_clause
- with_example_table do
- id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number")
- assert_equal 5150, id
- end
- end
-
- def test_insert_sql_with_quoted_schema_and_table_name
- with_example_table do
- id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)')
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_insert_sql_with_no_space_after_table_name
- with_example_table do
- id = @connection.insert_sql("insert into ex(number) values(5150)")
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_multiline_insert_sql
- with_example_table do
- id = @connection.insert_sql(<<-SQL)
- insert into ex(
- number)
- values(
- 5152
- )
- SQL
- expect = @connection.query('select max(id) from ex').first.first
- assert_equal expect, id
- end
- end
-
- def test_insert_sql_with_returning_disabled
- connection = connection_without_insert_returning
- id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
- expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
- assert_equal expect.to_i, id
- end
-
def test_exec_insert_with_returning_disabled
connection = connection_without_insert_returning
result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq')
@@ -245,30 +196,6 @@ module ActiveRecord
@connection.drop_table 'ex2', if_exists: true
end
- def test_exec_insert_number
- with_example_table do
- insert(@connection, 'number' => 10)
-
- result = @connection.exec_query('SELECT number FROM ex WHERE number = 10')
-
- assert_equal 1, result.rows.length
- assert_equal 10, result.rows.last.last
- end
- end
-
- def test_exec_insert_string
- with_example_table do
- str = 'いただきます!'
- insert(@connection, 'number' => 10, 'data' => str)
-
- result = @connection.exec_query('SELECT number, data FROM ex WHERE number = 10')
-
- value = result.rows.last.last
-
- assert_equal str, value
- end
- end
-
def test_table_alias_length
assert_nothing_raised do
@connection.table_alias_length
@@ -292,33 +219,35 @@ module ActiveRecord
end
end
- def test_exec_with_binds
- with_example_table do
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [bind_param(1)])
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_exec_with_binds
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ bind = Relation::QueryAttribute.new("id", 1, Type::Value.new)
+ result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
- assert_equal [[1, 'foo']], result.rows
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [[1, 'foo']], result.rows
+ end
end
- end
- def test_exec_typecasts_bind_vals
- with_example_table do
- string = @connection.quote('foo')
- @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
+ def test_exec_typecasts_bind_vals
+ with_example_table do
+ string = @connection.quote('foo')
+ @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})")
- bind = ActiveRecord::Relation::QueryAttribute.new("id", "1-fuu", ActiveRecord::Type::Integer.new)
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = $1', nil, [bind])
+ bind = Relation::QueryAttribute.new("id", "1-fuu", Type::Integer.new)
+ result = @connection.exec_query('SELECT id, data FROM ex WHERE id = $1', nil, [bind])
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
- assert_equal [[1, 'foo']], result.rows
+ assert_equal [[1, 'foo']], result.rows
+ end
end
end
@@ -330,6 +259,22 @@ module ActiveRecord
end
end
+ def test_expression_index
+ with_example_table do
+ @connection.add_index 'ex', 'mod(id, 10), abs(number)', name: 'expression'
+ index = @connection.indexes('ex').find { |idx| idx.name == 'expression' }
+ assert_equal 'mod(id, 10), abs(number)', index.columns
+ end
+ end
+
+ def test_index_with_opclass
+ with_example_table do
+ @connection.add_index 'ex', 'data varchar_pattern_ops', name: 'with_opclass'
+ index = @connection.indexes('ex').find { |idx| idx.name == 'with_opclass' }
+ assert_equal 'data varchar_pattern_ops', index.columns
+ end
+ end
+
def test_columns_for_distinct_zero_orders
assert_equal "posts.id",
@connection.columns_for_distinct("posts.id", [])
@@ -444,19 +389,6 @@ module ActiveRecord
end
private
- def insert(ctx, data)
- binds = data.map { |name, value|
- bind_param(value, name)
- }
- columns = binds.map(&:name)
-
- bind_subs = columns.length.times.map { |x| "$#{x + 1}" }
-
- sql = "INSERT INTO ex (#{columns.join(", ")})
- VALUES (#{bind_subs.join(', ')})"
-
- ctx.exec_insert(sql, 'SQL', binds)
- end
def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
super(@connection, 'ex', definition, &block)
@@ -465,10 +397,6 @@ module ActiveRecord
def connection_without_insert_returning
ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
end
-
- def bind_param(value, name = nil)
- ActiveRecord::Relation::QueryAttribute.new(name, value, ActiveRecord::Type::Value.new)
- end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
new file mode 100644
index 0000000000..f1519db48b
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb
@@ -0,0 +1,22 @@
+require "cases/helper"
+require "models/developer"
+
+class PreparedStatementsTest < ActiveRecord::PostgreSQLTestCase
+ fixtures :developers
+
+ def setup
+ @default_prepared_statements = Developer.connection_config[:prepared_statements]
+ Developer.connection_config[:prepared_statements] = false
+ end
+
+ def teardown
+ Developer.connection_config[:prepared_statements] = @default_prepared_statements
+ end
+
+ def nothing_raised_with_falsy_prepared_statements
+ assert_nothing_raised do
+ Developer.where(id: 1)
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index a0afd922b2..285a92f60e 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -55,20 +55,22 @@ class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase
set_session_auth
USERS.each do |u|
set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
set_session_auth
end
end
end
- def test_auth_with_bind
- assert_nothing_raised do
- set_session_auth
- USERS.each do |u|
- @connection.clear_cache!
- set_session_auth u
- assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)]).first['name']
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_auth_with_bind
+ assert_nothing_raised do
set_session_auth
+ USERS.each do |u|
+ @connection.clear_cache!
+ set_session_auth u
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [bind_param(1)])
+ set_session_auth
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 4aeca4d709..52ef07f654 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -166,22 +166,24 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_raise_wraped_exception_on_bad_prepare
+ def test_raise_wrapped_exception_on_bad_prepare
assert_raises(ActiveRecord::StatementInvalid) do
@connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)]
end
end
- def test_schema_change_with_prepared_stmt
- altered = false
- @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
- @connection.exec_query "alter table developers add column zomg int", 'sql', []
- altered = true
- @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
- ensure
- # We are not using DROP COLUMN IF EXISTS because that syntax is only
- # supported by pg 9.X
- @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ if ActiveRecord::Base.connection.prepared_statements
+ def test_schema_change_with_prepared_stmt
+ altered = false
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
+ @connection.exec_query "alter table developers add column zomg int", 'sql', []
+ altered = true
+ @connection.exec_query "select * from developers where id = $1", 'sql', [bind_param(1)]
+ ensure
+ # We are not using DROP COLUMN IF EXISTS because that syntax is only
+ # supported by pg 9.X
+ @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ end
end
def test_data_source_exists?
@@ -323,7 +325,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_dump_indexes_for_table_with_scheme_specified_in_name
indexes = @connection.indexes("#{SCHEMA_NAME}.#{TABLE_NAME}")
- assert_equal 4, indexes.size
+ assert_equal 5, indexes.size
end
def test_with_uppercase_index_name
@@ -447,18 +449,22 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
indexes = @connection.indexes(TABLE_NAME).sort_by(&:name)
- assert_equal 4,indexes.size
-
- do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
- do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
-
- indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
- assert_equal :btree, index.using
- end
- assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
- assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
+ assert_equal 5, indexes.size
+
+ index_a, index_b, index_c, index_d, index_e = indexes
+
+ do_dump_index_assertions_for_one_index(index_a, INDEX_A_NAME, first_index_column_name)
+ do_dump_index_assertions_for_one_index(index_b, INDEX_B_NAME, second_index_column_name)
+ do_dump_index_assertions_for_one_index(index_d, INDEX_D_NAME, third_index_column_name)
+ do_dump_index_assertions_for_one_index(index_e, INDEX_E_NAME, fourth_index_column_name)
+
+ assert_equal :btree, index_a.using
+ assert_equal :btree, index_b.using
+ assert_equal :gin, index_c.using
+ assert_equal :btree, index_d.using
+ assert_equal :gin, index_e.using
+
+ assert_equal :desc, index_d.orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index 7d30db247b..8abe064bf1 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -10,6 +10,7 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
@connection.create_table "postgresql_serials", force: true do |t|
t.serial :seq
+ t.integer :serials_id, default: -> { "nextval('postgresql_serials_id_seq')" }
end
end
@@ -24,9 +25,21 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
assert column.serial?
end
+ def test_not_serial_column
+ column = PostgresqlSerial.columns_hash["serials_id"]
+ assert_equal :integer, column.type
+ assert_equal "integer", column.sql_type
+ assert_not column.serial?
+ end
+
def test_schema_dump_with_shorthand
output = dump_table_schema "postgresql_serials"
- assert_match %r{t\.serial\s+"seq"}, output
+ assert_match %r{t\.serial\s+"seq",\s+null: false$}, output
+ end
+
+ def test_schema_dump_with_not_serial
+ output = dump_table_schema "postgresql_serials"
+ assert_match %r{t\.integer\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_serials_id_seq'::regclass\)" \}$}, output
end
end
@@ -39,6 +52,7 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
@connection.create_table "postgresql_big_serials", force: true do |t|
t.bigserial :seq
+ t.bigint :serials_id, default: -> { "nextval('postgresql_big_serials_id_seq')" }
end
end
@@ -53,8 +67,20 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
assert column.serial?
end
+ def test_not_bigserial_column
+ column = PostgresqlBigSerial.columns_hash["serials_id"]
+ assert_equal :integer, column.type
+ assert_equal "bigint", column.sql_type
+ assert_not column.serial?
+ end
+
def test_schema_dump_with_shorthand
output = dump_table_schema "postgresql_big_serials"
- assert_match %r{t\.bigserial\s+"seq"}, output
+ assert_match %r{t\.bigserial\s+"seq",\s+null: false$}, output
+ end
+
+ def test_schema_dump_with_not_bigserial
+ output = dump_table_schema "postgresql_big_serials"
+ assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
new file mode 100644
index 0000000000..e76705a802
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -0,0 +1,72 @@
+require "cases/helper"
+require 'support/connection_helper'
+
+module ActiveRecord
+ class PostgresqlTransactionTest < ActiveRecord::PostgreSQLTestCase
+ self.use_transactional_tests = false
+
+ class Sample < ActiveRecord::Base
+ self.table_name = 'samples'
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+
+ @connection.transaction do
+ @connection.drop_table 'samples', if_exists: true
+ @connection.create_table('samples') do |t|
+ t.integer 'value'
+ end
+ end
+
+ Sample.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'samples', if_exists: true
+ end
+
+ test "raises error when a serialization failure occurs" do
+ with_warning_suppression do
+ assert_raises(ActiveRecord::TransactionSerializationError) do
+ thread = Thread.new do
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+ end
+ end
+
+ sleep 0.1
+
+ Sample.transaction isolation: :serializable do
+ Sample.delete_all
+
+ 10.times do |i|
+ sleep 0.1
+
+ Sample.create value: i
+ end
+
+ sleep 1
+ end
+
+ thread.join
+ end
+ end
+ end
+
+ protected
+
+ def with_warning_suppression
+ log_level = @connection.client_min_messages
+ @connection.client_min_messages = 'error'
+ yield
+ @connection.client_min_messages = log_level
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 77a99ca778..ea0f0b8fa5 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -18,7 +18,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
bigint_array = @connection.type_map.lookup(1016, -1, "bigint[]")
big_array = [123456789123456789]
- assert_raises(RangeError) { int_array.serialize(big_array) }
+ assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) }
assert_equal "{123456789123456789}", bigint_array.serialize(big_array)
end
@@ -27,7 +27,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
bigint_range = @connection.type_map.lookup(3926, -1, "int8range")
big_range = 0..123456789123456789
- assert_raises(RangeError) { int_range.serialize(big_range) }
+ assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) }
assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 2aec322582..a1a6e5f16a 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -2,26 +2,20 @@ require "cases/helper"
require 'models/developer'
require 'models/computer'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
+ fixtures :developers
- def test_explain_for_one_query
- explain = Developer.where(:id => 1).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- end
+ def test_explain_for_one_query
+ explain = Developer.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ end
- def test_explain_with_eager_loading
- explain = Developer.where(:id => 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = ?), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
- end
- end
- end
+ def test_explain_with_eager_loading
+ explain = Developer.where(id: 1).includes(:audit_logs).explain
+ assert_match %r(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = (?:\?|1)), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain
+ assert_match(/(SCAN )?TABLE audit_logs/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index 87a892db37..fe2425b845 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -3,93 +3,92 @@ require 'bigdecimal'
require 'yaml'
require 'securerandom'
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class QuotingTest < ActiveRecord::SQLite3TestCase
- def setup
- @conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 100
- end
-
- def test_type_cast_binary_encoding_without_logger
- @conn.extend(Module.new { def logger; end })
- binary = SecureRandom.hex
- expected = binary.dup.encode!(Encoding::UTF_8)
- assert_equal expected, @conn.type_cast(binary)
- end
-
- def test_type_cast_symbol
- assert_equal 'foo', @conn.type_cast(:foo)
- end
-
- def test_type_cast_date
- date = Date.today
- expected = @conn.quoted_date(date)
- assert_equal expected, @conn.type_cast(date)
- end
-
- def test_type_cast_time
- time = Time.now
- expected = @conn.quoted_date(time)
- assert_equal expected, @conn.type_cast(time)
- end
-
- def test_type_cast_numeric
- assert_equal 10, @conn.type_cast(10)
- assert_equal 2.2, @conn.type_cast(2.2)
- end
-
- def test_type_cast_nil
- assert_equal nil, @conn.type_cast(nil)
- end
-
- def test_type_cast_true
- assert_equal 't', @conn.type_cast(true)
- end
-
- def test_type_cast_false
- assert_equal 'f', @conn.type_cast(false)
- end
-
- def test_type_cast_bigdecimal
- bd = BigDecimal.new '10.0'
- assert_equal bd.to_f, @conn.type_cast(bd)
- end
-
- def test_type_cast_unknown_should_raise_error
- obj = Class.new.new
- assert_raise(TypeError) { @conn.type_cast(obj) }
- end
-
- def test_type_cast_object_which_responds_to_quoted_id
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
-
- def id
- 10
- end
- }.new
- assert_equal 10, @conn.type_cast(quoted_id_obj)
-
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
- }.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
- end
-
- def test_quoting_binary_strings
- value = "hello".encode('ascii-8bit')
- type = Type::String.new
-
- assert_equal "'hello'", @conn.quote(type.serialize(value))
- end
+class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
+ def setup
+ @conn = ActiveRecord::Base.connection
+ end
+
+ def test_type_cast_binary_encoding_without_logger
+ @conn.extend(Module.new { def logger; end })
+ binary = SecureRandom.hex
+ expected = binary.dup.encode!(Encoding::UTF_8)
+ assert_equal expected, @conn.type_cast(binary)
+ end
+
+ def test_type_cast_symbol
+ assert_equal 'foo', @conn.type_cast(:foo)
+ end
+
+ def test_type_cast_date
+ date = Date.today
+ expected = @conn.quoted_date(date)
+ assert_equal expected, @conn.type_cast(date)
+ end
+
+ def test_type_cast_time
+ time = Time.now
+ expected = @conn.quoted_date(time)
+ assert_equal expected, @conn.type_cast(time)
+ end
+
+ def test_type_cast_numeric
+ assert_equal 10, @conn.type_cast(10)
+ assert_equal 2.2, @conn.type_cast(2.2)
+ end
+
+ def test_type_cast_nil
+ assert_equal nil, @conn.type_cast(nil)
+ end
+
+ def test_type_cast_true
+ assert_equal 't', @conn.type_cast(true)
+ end
+
+ def test_type_cast_false
+ assert_equal 'f', @conn.type_cast(false)
+ end
+
+ def test_type_cast_bigdecimal
+ bd = BigDecimal.new '10.0'
+ assert_equal bd.to_f, @conn.type_cast(bd)
+ end
+
+ def test_type_cast_unknown_should_raise_error
+ obj = Class.new.new
+ assert_raise(TypeError) { @conn.type_cast(obj) }
+ end
+
+ def test_type_cast_object_which_responds_to_quoted_id
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+
+ def id
+ 10
end
- end
+ }.new
+ assert_equal 10, @conn.type_cast(quoted_id_obj)
+
+ quoted_id_obj = Class.new {
+ def quoted_id
+ "'zomg'"
+ end
+ }.new
+ assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
+ end
+
+ def test_quoting_binary_strings
+ value = "hello".encode('ascii-8bit')
+ type = ActiveRecord::Type::String.new
+
+ assert_equal "'hello'", @conn.quote(type.serialize(value))
+ end
+
+ def test_quoted_time_returns_date_qualified_time
+ value = ::Time.utc(2000, 1, 1, 12, 30, 0, 999999)
+ type = ActiveRecord::Type::Time.new
+
+ assert_equal "'2000-01-01 12:30:00.999999'", @conn.quote(type.serialize(value))
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 038d9e2d0f..bbc9f978bf 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -213,24 +213,12 @@ module ActiveRecord
assert_equal "''", @conn.quote_string("'")
end
- def test_insert_sql
- with_example_table do
- 2.times do |i|
- rv = @conn.insert_sql "INSERT INTO ex (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
- end
-
- records = @conn.execute "SELECT * FROM ex"
- assert_equal 2, records.length
- end
- end
-
- def test_insert_sql_logged
+ def test_insert_logged
with_example_table do
sql = "INSERT INTO ex (number) VALUES (10)"
name = "foo"
assert_logged [[sql, name, []]] do
- @conn.insert_sql sql, name
+ @conn.insert(sql, name)
end
end
end
@@ -239,7 +227,7 @@ module ActiveRecord
with_example_table do
sql = "INSERT INTO ex (number) VALUES (10)"
idval = 'vuvuzela'
- id = @conn.insert_sql sql, nil, nil, idval
+ id = @conn.insert(sql, nil, nil, idval)
assert_equal idval, id
end
end
@@ -400,12 +388,6 @@ module ActiveRecord
end
end
- def test_composite_primary_key
- with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do
- assert_nil @conn.primary_key('ex')
- end
- end
-
def test_supports_extensions
assert_not @conn.supports_extensions?, 'does not support extensions'
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index 559b951109..24cc6875ab 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,24 +1,20 @@
require 'cases/helper'
-module ActiveRecord::ConnectionAdapters
- class SQLite3Adapter
- class StatementPoolTest < ActiveRecord::SQLite3TestCase
- if Process.respond_to?(:fork)
- def test_cache_is_per_pid
+class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
+ if Process.respond_to?(:fork)
+ def test_cache_is_per_pid
- cache = StatementPool.new(10)
- cache['foo'] = 'bar'
- assert_equal 'bar', cache['foo']
+ cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10)
+ cache['foo'] = 'bar'
+ assert_equal 'bar', cache['foo']
- pid = fork {
- lookup = cache['foo'];
- exit!(!lookup)
- }
+ pid = fork {
+ lookup = cache['foo'];
+ exit!(!lookup)
+ }
- Process.waitpid pid
- assert $?.success?, 'process should exit successfully'
- end
- end
+ Process.waitpid pid
+ assert $?.success?, 'process should exit successfully'
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 5536702f58..8a728902a8 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -138,6 +138,11 @@ class AggregationsTest < ActiveRecord::TestCase
assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s
assert_kind_of Fullname, customers(:barney).fullname
end
+
+ def test_assigning_hash_to_custom_converter
+ customers(:barney).fullname = { first: "Barney", last: "Stinson" }
+ assert_equal "Barney STINSON", customers(:barney).name
+ end
end
class OverridingAggregationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 1f32c48b95..9c99689c1e 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migration.verbose = @original_verbose
end
- def test_has_has_primary_key
+ def test_has_primary_key
old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
assert_equal "version", ActiveRecord::SchemaMigration.primary_key
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 4f99c57c3c..9dadd114a1 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -168,7 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
e = assert_raise(ActiveRecord::AssociationTypeMismatch) {
Admin::RegionalUser.new(region: 'wrong value')
}
- assert_match(/^Region\([^)]+\) expected, got String\([^)]+\)$/, e.message)
+ assert_match(/^Region\([^)]+\) expected, got "wrong value" which is an instance of String\([^)]+\)$/, e.message)
ensure
Admin.send :remove_const, "Region" if Admin.const_defined?("Region")
Admin.send :remove_const, "RegionalUser" if Admin.const_defined?("RegionalUser")
@@ -700,6 +700,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 17, reply.replies.size
end
+ def test_replace_counter_cache
+ topic = Topic.create(title: "Zoom-zoom-zoom")
+ reply = Reply.create(title: "re: zoom", content: "speedy quick!")
+
+ reply.topic = topic
+ reply.save
+ topic.reload
+
+ assert_equal 1, topic.replies_count
+ end
+
def test_association_assignment_sticks
post = Post.first
@@ -726,7 +737,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert companies(:first_client).readonly_firm.readonly?
end
- def test_test_polymorphic_assignment_foreign_key_type_string
+ def test_polymorphic_assignment_foreign_key_type_string
comment = Comment.first
comment.author = Author.first
comment.resource = Member.first
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index a531e0e02c..a4298a25a6 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -177,14 +177,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
end
def test_dont_add_if_before_callback_raises_exception
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
begin
- @david.unchangable_posts << @authorless
+ @david.unchangeable_posts << @authorless
rescue Exception
end
assert @david.post_log.empty?
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
@david.reload
- assert !@david.unchangable_posts.include?(@authorless)
+ assert !@david.unchangeable_posts.include?(@authorless)
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 874d53c51f..80d9a6083b 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -90,6 +90,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { authors.map(&:post) }
end
+ def test_calculate_with_string_in_from_and_eager_loading
+ assert_equal 10, Post.from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").count
+ end
+
def test_with_two_tables_in_from_without_getting_double_quoted
posts = Post.select("posts.*").from("authors, posts").eager_load(:comments).where("posts.author_id = authors.id").order("posts.id").to_a
assert_equal 2, posts.first.comments.size
@@ -749,6 +753,38 @@ class EagerAssociationTest < ActiveRecord::TestCase
}
end
+ def test_eager_has_many_through_with_order
+ tag = OrderedTag.create(name: 'Foo')
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable_type: 'Post', taggable_id: post1.id, tag: tag)
+ Tagging.create!(taggable_type: 'Post', taggable_id: post2.id, tag: tag)
+
+ tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id)
+ assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title))
+ end
+
+ def test_eager_has_many_through_multiple_with_order
+ tag1 = OrderedTag.create!(name: 'Bar')
+ tag2 = OrderedTag.create!(name: 'Foo')
+
+ post1 = Post.create!(title: 'Beaches', body: "I like beaches!")
+ post2 = Post.create!(title: 'Pools', body: "I like pools!")
+
+ Tagging.create!(taggable: post1, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag1)
+ Tagging.create!(taggable: post2, tag: tag2)
+ Tagging.create!(taggable: post1, tag: tag2)
+
+ tags_with_includes = OrderedTag.where(id: [tag1, tag2].map(&:id)).includes(:tagged_posts).order(:id).to_a
+ tag1_with_includes = tags_with_includes.first
+ tag2_with_includes = tags_with_includes.last
+
+ assert_equal([post2, post1].map(&:title), tag1_with_includes.tagged_posts.map(&:title))
+ assert_equal([post1, post2].map(&:title), tag2_with_includes.tagged_posts.map(&:title))
+ end
+
def test_eager_with_default_scope
developer = EagerDeveloperWithDefaultScope.where(:name => 'David').first
projects = Project.order(:id).to_a
@@ -1216,7 +1252,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_join_eager_with_empty_order_should_generate_valid_sql
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.includes(:comments).order("").where(:comments => {:body => "Thank you for the welcome"}).first
end
end
@@ -1309,6 +1345,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_nothing_raised do
authors(:david).essays.includes(:writer).any?
+ authors(:david).essays.includes(:writer).exists?
end
end
@@ -1360,6 +1397,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal('10 was not recognized for preload', exception.message)
end
+ test "associations with extensions are not instance dependent" do
+ assert_nothing_raised do
+ Author.includes(:posts_with_extension).to_a
+ end
+ end
+
+ test "including associations with extensions and an instance dependent scope is not supported" do
+ e = assert_raises(ArgumentError) do
+ Author.includes(:posts_with_extension_and_instance).to_a
+ end
+ assert_match(/Preloading instance dependent scopes is not supported/, e.message)
+ end
test "preloading readonly association" do
# has-one
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index ccb062f991..1bbca84bb2 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -146,6 +146,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, country.treaties.count
end
+ def test_join_table_composite_primary_key_should_not_warn
+ country = Country.new(:name => 'India')
+ country.country_id = 'c1'
+ country.save!
+
+ treaty = Treaty.new(:name => 'peace')
+ treaty.treaty_id = 't1'
+ warning = capture(:stderr) do
+ country.treaties << treaty
+ end
+ assert_no_match(/WARNING: Rails does not support composite primary key\./, warning)
+ end
+
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -827,12 +840,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_no_queries { david.projects.columns }
end
- def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
+ def test_attributes_are_being_set_when_initialized_from_habtm_association_with_where_clause
new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build
assert_equal new_developer.name, "Marcelo"
end
- def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses
+ def test_attributes_are_being_set_when_initialized_from_habtm_association_with_multiple_where_clauses
new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build
assert_equal new_developer.name, "Marcelo"
assert_equal new_developer.salary, 90_000
@@ -925,7 +938,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_with_symbol_class_name
- assert_nothing_raised NoMethodError do
+ assert_nothing_raised do
DeveloperWithSymbolClassName.new
end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ad157582a4..7ec0dfce7a 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -408,6 +408,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
assert_no_queries do
+ bulbs.third_to_last()
+ bulbs.third_to_last({})
+ end
+
+ assert_no_queries do
+ bulbs.second_to_last()
+ bulbs.second_to_last({})
+ end
+
+ assert_no_queries do
bulbs.last()
bulbs.last({})
end
@@ -1305,7 +1315,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, summit.client_of
end
- def test_deleting_by_fixnum_id
+ def test_deleting_by_integer_id
david = Developer.find(1)
assert_difference 'david.projects.count', -1 do
@@ -1342,7 +1352,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, companies(:first_firm).clients_of_firm.reload.size
end
- def test_destroying_by_fixnum_id
+ def test_destroying_by_integer_id
force_signal37_to_load_all_clients_of_firm
assert_difference "Client.count", -1 do
@@ -2271,7 +2281,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [], authors(:david).posts_with_signature.map(&:title)
end
- test 'associations autosaves when object is already persited' do
+ test 'associations autosaves when object is already persisted' do
bulb = Bulb.create!
tyre = Tyre.create!
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 226ecf5447..aa35844a03 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -11,7 +11,9 @@ require 'models/tagging'
require 'models/author'
require 'models/owner'
require 'models/pet'
+require 'models/pet_treasure'
require 'models/toy'
+require 'models/treasure'
require 'models/contract'
require 'models/company'
require 'models/developer'
@@ -63,7 +65,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
end
- def test_ordered_habtm
+ def test_ordered_has_many_through
person_prime = Class.new(ActiveRecord::Base) do
def self.name; 'Person'; end
@@ -884,7 +886,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
ids = [Developer.first.id, -9999]
- assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
+ assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids}
end
def test_build_a_model_from_hm_through_association_with_where_clause
@@ -1082,6 +1084,18 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [], person.posts
end
+ def test_preloading_empty_through_with_polymorphic_source_association
+ owner = Owner.create!(name: "Rainbow Unicat")
+ pet = Pet.create!(owner: owner)
+ person = Person.create!(first_name: "Gaga")
+ treasure = Treasure.create!(looter: person)
+ non_looted_treasure = Treasure.create!()
+ PetTreasure.create!(pet: pet, treasure: treasure, rainbow_color: "Ultra violet indigo")
+ PetTreasure.create!(pet: pet, treasure: non_looted_treasure, rainbow_color: "Ultra violet indigo")
+
+ assert_equal [person], Owner.where(name: "Rainbow Unicat").includes(pets: :persons).first.persons.to_a
+ end
+
def test_explicitly_joining_join_table
assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index c9d9e29f09..1574f373c2 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -659,4 +659,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_deprecated { firm.account(true) }
end
+
+ class SpecialBook < ActiveRecord::Base
+ self.table_name = 'books'
+ belongs_to :author, class_name: 'SpecialAuthor'
+ end
+
+ class SpecialAuthor < ActiveRecord::Base
+ self.table_name = 'authors'
+ has_one :book, class_name: 'SpecialBook', foreign_key: 'author_id'
+ end
+
+ def test_assocation_enum_works_properly
+ author = SpecialAuthor.create!(name: 'Test')
+ book = SpecialBook.create!(status: 'published')
+ author.book = book
+
+ refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: 'published' } ).count
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 57d1c8feda..c9743e80d3 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -130,15 +130,15 @@ end
class InverseAssociationTests < ActiveRecord::TestCase
def test_should_allow_for_inverse_of_options_in_associations
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car)
end
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car)
end
- assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do
+ assert_nothing_raised do
Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver)
end
end
@@ -666,7 +666,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error
# Ideally this would, if only for symmetry's sake with other association types
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man }
+ assert_nothing_raised { Face.first.horrible_polymorphic_man }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error
@@ -676,7 +676,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error
# passes because Man does have the correct inverse_of
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first }
+ assert_nothing_raised { Face.first.polymorphic_man = Man.first }
# fails because Interest does have the correct inverse_of
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
@@ -688,7 +688,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
fixtures :men, :interests, :zines
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
- assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ assert_nothing_raised do
i = Interest.first
i.zine
i.man
@@ -696,7 +696,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
end
def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
- assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
+ assert_nothing_raised do
i = Interest.first
i.build_zine(:title => 'Get Some in Winter! 2008')
i.build_man(:name => 'Gordon')
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index f6dddaf5b4..3047914b70 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -88,7 +88,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
- assert_nothing_raised(NoMethodError) { tag.author_id }
+ assert_nothing_raised { tag.author_id }
end
def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
@@ -363,6 +363,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id)
end
+ def test_has_many_polymorphic_associations_merges_through_scope
+ Tag.has_many :null_taggings, -> { none }, class_name: :Tagging
+ Tag.has_many :null_tagged_posts, :through => :null_taggings, :source => 'taggable', :source_type => 'Post'
+ assert_equal [], tags(:general).null_tagged_posts
+ refute_equal [], tags(:general).tagged_posts
+ end
+
def test_eager_has_many_polymorphic_with_source_type
tag_with_include = Tag.all.merge!(:includes => :tagged_posts).find(tags(:general).id)
desired = posts(:welcome, :thinking)
@@ -591,7 +598,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete(Object.new) }
end
- def test_deleting_by_fixnum_id_from_has_many_through
+ def test_deleting_by_integer_id_from_has_many_through
post = posts(:thinking)
assert_difference 'post.tags.count', -1 do
@@ -740,6 +747,23 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal aircraft.engines, [engine]
end
+ def test_proper_error_message_for_eager_load_and_includes_association_errors
+ includes_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.includes(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_error.message)
+
+ eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.eager_load(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", eager_load_error.message)
+
+ includes_and_eager_load_error = assert_raises(ActiveRecord::ConfigurationError) {
+ Post.eager_load(:nonexistent_relation).includes(:nonexistent_relation).where(nonexistent_relation: {name: 'Rochester'}).find(1)
+ }
+ assert_equal("Can't join 'Post' to association named 'nonexistent_relation'; perhaps you misspelled it?", includes_and_eager_load_error.message)
+ end
+
private
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index 4af791b758..eee135cfb8 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -50,7 +50,7 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
def test_join_conditions_added_to_join_clause
queries = capture_sql { Author.left_outer_joins(:essays).to_a }
- assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1)/i =~ sql }
+ assert queries.any? { |sql| /writer_type.*?=.*?(Author|\?|\$1|\:a1)/i =~ sql }
assert queries.none? { |sql| /WHERE/i =~ sql }
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 94dfbc3346..1db52af59b 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -714,6 +714,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new(bonus_time: [])
+ assert_equal nil, record.bonus_time
+ end
+ end
+
def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable
Topic.skip_time_zone_conversion_for_attributes = [:field_a]
Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b]
@@ -791,7 +798,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_nil computer.system
end
- def test_global_methods_are_overwritte_when_subclassing
+ def test_global_methods_are_overwritten_when_subclassing
klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }
subklass = Class.new(klass) do
diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb
index a24a4fc6a4..b1b8639696 100644
--- a/activerecord/test/cases/attribute_test.rb
+++ b/activerecord/test/cases/attribute_test.rb
@@ -242,5 +242,12 @@ module ActiveRecord
attribute.with_value_from_user(1)
end
end
+
+ test "with_type preserves mutations" do
+ attribute = Attribute.from_database(:foo, "", Type::Value.new)
+ attribute.value << "1"
+
+ assert_equal 1, attribute.with_type(Type::Integer.new).value
+ end
end
end
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 2991ca8b76..7bcaa53aa2 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -38,7 +38,7 @@ module ActiveRecord
data.reload
assert_equal 2, data.overloaded_float
- assert_kind_of Fixnum, OverloadedType.last.overloaded_float
+ assert_kind_of Integer, OverloadedType.last.overloaded_float
assert_equal 2.0, UnoverloadedType.last.overloaded_float
assert_kind_of Float, UnoverloadedType.last.overloaded_float
end
@@ -63,6 +63,15 @@ module ActiveRecord
end
end
+ test "model with nonexistent attribute with default value can be saved" do
+ klass = Class.new(OverloadedType) do
+ attribute :non_existent_string_with_default, :string, default: 'nonexistent'
+ end
+
+ model = klass.new
+ assert model.save
+ end
+
test "changing defaults" do
data = OverloadedType.new
unoverloaded_data = UnoverloadedType.new
@@ -135,6 +144,17 @@ module ActiveRecord
assert_equal 2, klass.new.counter
end
+ test "procs are memoized before type casting" do
+ klass = Class.new(OverloadedType) do
+ @@counter = 0
+ attribute :counter, :integer, default: -> { @@counter += 1 }
+ end
+
+ model = klass.new
+ assert_equal 1, model.counter_before_type_cast
+ assert_equal 1, model.counter_before_type_cast
+ end
+
test "user provided defaults are persisted even if unchanged" do
model = OverloadedType.create!
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 3608063b01..9e3266b7d6 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -749,7 +749,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
# has_one
- def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
+ def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
assert !@pirate.ship.marked_for_destruction?
@pirate.ship.mark_for_destruction
@@ -809,7 +809,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
# belongs_to
- def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal
+ def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
assert !@ship.pirate.marked_for_destruction?
@ship.pirate.mark_for_destruction
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ecdf508e3e..00e4e50ea1 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -14,7 +14,6 @@ require 'models/auto_id'
require 'models/boolean'
require 'models/column_name'
require 'models/subscriber'
-require 'models/keyboard'
require 'models/comment'
require 'models/minimalistic'
require 'models/warehouse_thing'
@@ -25,7 +24,6 @@ require 'models/joke'
require 'models/bird'
require 'models/car'
require 'models/bulb'
-require 'rexml/document'
require 'concurrent/atomic/count_down_latch'
class FirstAbstractClass < ActiveRecord::Base
@@ -804,7 +802,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal dev.salary.amount, dup.salary.amount
assert !dup.persisted?
- # test if the attributes have been dupd
+ # test if the attributes have been duped
original_amount = dup.salary.amount
dev.salary.amount = 1
assert_equal original_amount, dup.salary.amount
@@ -940,7 +938,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
@@ -968,7 +966,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_kind_of Integer, m1.world_population
assert_equal 6000000000, m1.world_population
- assert_kind_of Fixnum, m1.my_house_population
+ assert_kind_of Integer, m1.my_house_population
assert_equal 3, m1.my_house_population
assert_kind_of BigDecimal, m1.bank_balance
@@ -1504,6 +1502,10 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal Post.new.hash, Post.new.hash
end
+ test "records of different classes have different hashes" do
+ assert_not_equal Post.new(id: 1).hash, Developer.new(id: 1).hash
+ end
+
test "resetting column information doesn't remove attribute methods" do
topic = topics(:first)
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 3602ee7ba2..db71840658 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -108,7 +108,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_find_in_batches_should_finish_the_end_option
+ def test_find_in_batches_should_end_at_the_finish_option
assert_queries(6) do
Post.find_in_batches(batch_size: 1, finish: 5) do |batch|
assert_kind_of Array, batch
@@ -164,6 +164,42 @@ class EachTest < ActiveRecord::TestCase
assert_equal posts(:welcome).id, posts.first.id
end
+ def test_find_in_batches_should_error_on_ignore_the_order
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: true){}
+ end
+ end
+
+ def test_find_in_batches_should_not_error_if_config_overridden
+ # Set the config option which will be overridden
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(error_on_ignore: false){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_error_on_config_specified_to_error
+ # Set the config option
+ prev = ActiveRecord::Base.error_on_ignored_order_or_limit
+ ActiveRecord::Base.error_on_ignored_order_or_limit = true
+ assert_raise(ArgumentError) do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ ensure
+ # Set back to default
+ ActiveRecord::Base.error_on_ignored_order_or_limit = prev
+ end
+
+ def test_find_in_batches_should_not_error_by_default
+ assert_nothing_raised do
+ PostWithDefaultScope.find_in_batches(){}
+ end
+ end
+
def test_find_in_batches_should_not_ignore_the_default_scope_if_it_is_other_then_order
special_posts_ids = SpecialPostWithDefaultScope.all.map(&:id).sort
posts = []
@@ -316,7 +352,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_in_batches_should_finish_the_end_option
+ def test_in_batches_should_end_at_the_finish_option
post = Post.order('id DESC').where('id <= ?', 5).first
assert_queries(7) do
relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index cd9c76f1f0..fa924fe4cb 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -31,7 +31,8 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@subscription)
end
- if ActiveRecord::Base.connection.supports_statement_cache?
+ if ActiveRecord::Base.connection.supports_statement_cache? &&
+ ActiveRecord::Base.connection.prepared_statements
def test_bind_from_join_in_subquery
subquery = Author.joins(:thinking_posts).where(name: 'David')
scope = Author.from(subquery, 'authors').where(id: 1)
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index c922a8d1c2..cfae700159 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -124,7 +124,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_generate_valid_sql_with_joins_and_group
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
AuditLog.joins(:developer).group(:id).count
end
end
@@ -482,6 +482,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count
end
+ def test_count_with_block
+ assert_equal 4, Account.count { |account| account.credit_limit.modulo(10).zero? }
+ end
+
def test_should_sum_expression
# Oracle adapter returns floating point value 636.0 after SUM
if current_adapter?(:OracleAdapter)
@@ -742,7 +746,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
developer = Developer.create!(name: 'developer')
developer.ratings.includes(comment: :post).where(posts: { id: 1 }).count
end
diff --git a/activerecord/test/cases/coders/json_test.rb b/activerecord/test/cases/coders/json_test.rb
new file mode 100644
index 0000000000..d22d93d129
--- /dev/null
+++ b/activerecord/test/cases/coders/json_test.rb
@@ -0,0 +1,15 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Coders
+ class JSONTest < ActiveRecord::TestCase
+ def test_returns_nil_if_empty_string_given
+ assert_nil JSON.load("")
+ end
+
+ def test_returns_nil_if_nil_given
+ assert_nil JSON.load(nil)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
new file mode 100644
index 0000000000..839fdbe578
--- /dev/null
+++ b/activerecord/test/cases/comment_test.rb
@@ -0,0 +1,139 @@
+require 'cases/helper'
+require 'support/schema_dumping_helper'
+
+if ActiveRecord::Base.connection.supports_comments?
+
+class CommentTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ class Commented < ActiveRecord::Base
+ self.table_name = 'commenteds'
+ end
+
+ class BlankComment < ActiveRecord::Base
+ end
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+
+ @connection.create_table('commenteds', comment: 'A table with comment', force: true) do |t|
+ t.string 'name', comment: 'Comment should help clarify the column purpose'
+ t.boolean 'obvious', comment: 'Question is: should you comment obviously named objects?'
+ t.string 'content'
+ t.index 'name', comment: %Q["Very important" index that powers all the performance.\nAnd it's fun!]
+ end
+
+ @connection.create_table('blank_comments', comment: ' ', force: true) do |t|
+ t.string :space_comment, comment: ' '
+ t.string :empty_comment, comment: ''
+ t.string :nil_comment, comment: nil
+ t.string :absent_comment
+ t.index :space_comment, comment: ' '
+ t.index :empty_comment, comment: ''
+ t.index :nil_comment, comment: nil
+ t.index :absent_comment
+ end
+
+ Commented.reset_column_information
+ BlankComment.reset_column_information
+ end
+
+ teardown do
+ @connection.drop_table 'commenteds', if_exists: true
+ @connection.drop_table 'blank_comments', if_exists: true
+ end
+
+ def test_column_created_in_block
+ column = Commented.columns_hash['name']
+ assert_equal :string, column.type
+ assert_equal 'Comment should help clarify the column purpose', column.comment
+ end
+
+ def test_blank_columns_created_in_block
+ %w[ space_comment empty_comment nil_comment absent_comment ].each do |field|
+ column = BlankComment.columns_hash[field]
+ assert_equal :string, column.type
+ assert_nil column.comment
+ end
+ end
+
+ def test_blank_indexes_created_in_block
+ @connection.indexes('blank_comments').each do |index|
+ assert_nil index.comment
+ end
+ end
+
+ def test_add_column_with_comment_later
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ Commented.reset_column_information
+ column = Commented.columns_hash['rating']
+
+ assert_equal :integer, column.type
+ assert_equal 'I am running out of imagination', column.comment
+ end
+
+ def test_add_index_with_comment_later
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+ index = @connection.indexes('commenteds').find { |idef| idef.name == 'idx_obvious' }
+ assert_equal 'We need to see obvious comments', index.comment
+ end
+
+ def test_add_comment_to_column
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['content']
+
+ assert_equal :string, column.type
+ assert_equal 'Whoa, content describes itself!', column.comment
+ end
+
+ def test_remove_comment_from_column
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+
+ Commented.reset_column_information
+ column = Commented.columns_hash['obvious']
+
+ assert_equal :string, column.type
+ assert_nil column.comment
+ end
+
+ def test_schema_dump_with_comments
+ # Do all the stuff from other tests
+ @connection.add_column :commenteds, :rating, :integer, comment: 'I am running out of imagination'
+ @connection.change_column :commenteds, :content, :string, comment: 'Whoa, content describes itself!'
+ @connection.change_column :commenteds, :obvious, :string, comment: nil
+ @connection.add_index :commenteds, :obvious, name: 'idx_obvious', comment: 'We need to see obvious comments'
+
+ # And check that these changes are reflected in dump
+ output = dump_table_schema 'commenteds'
+ assert_match %r[create_table "commenteds",.+\s+comment: "A table with comment"], output
+ assert_match %r[t\.string\s+"name",\s+comment: "Comment should help clarify the column purpose"], output
+ assert_match %r[t\.string\s+"obvious"\n], output
+ assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
+ assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
+ assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
+ end
+
+ def test_schema_dump_omits_blank_comments
+ output = dump_table_schema 'blank_comments'
+
+ assert_match %r[create_table "blank_comments"], output
+ assert_no_match %r[create_table "blank_comments",.+comment:], output
+
+ assert_match %r[t\.string\s+"space_comment"\n], output
+ assert_no_match %r[t\.string\s+"space_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"empty_comment"\n], output
+ assert_no_match %r[t\.string\s+"empty_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"nil_comment"\n], output
+ assert_no_match %r[t\.string\s+"nil_comment", comment:\n], output
+
+ assert_match %r[t\.string\s+"absent_comment"\n], output
+ assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output
+ end
+end
+
+end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 580568c8ac..c7ca428ab7 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
def test_close
- pool = Pool.new(ConnectionSpecification.new({}, nil))
+ pool = Pool.new(ConnectionSpecification.new("primary", {}, nil))
pool.insert_connection_for_test! @adapter
@adapter.pool = pool
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 9b1865e8bb..a019cc6490 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -4,43 +4,40 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionHandlerTest < ActiveRecord::TestCase
def setup
- @klass = Class.new(Base) { def self.name; 'klass'; end }
- @subklass = Class.new(@klass) { def self.name; 'subklass'; end }
-
@handler = ConnectionHandler.new
- @pool = @handler.establish_connection(@klass, Base.connection_pool.spec)
+ @spec_name = "primary"
+ @pool = @handler.establish_connection(ActiveRecord::Base.configurations['arunit'])
+ end
+
+ def test_establish_connection_uses_spec_name
+ config = {"readonly" => {"adapter" => 'sqlite3'}}
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(config)
+ spec = resolver.spec(:readonly)
+ @handler.establish_connection(spec.to_hash)
+
+ assert_not_nil @handler.retrieve_connection_pool('readonly')
+ ensure
+ @handler.remove_connection('readonly')
end
def test_retrieve_connection
- assert @handler.retrieve_connection(@klass)
+ assert @handler.retrieve_connection(@spec_name)
end
def test_active_connections?
assert !@handler.active_connections?
- assert @handler.retrieve_connection(@klass)
+ assert @handler.retrieve_connection(@spec_name)
assert @handler.active_connections?
@handler.clear_active_connections!
assert !@handler.active_connections?
end
- def test_retrieve_connection_pool_with_ar_base
- assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base)
- end
-
def test_retrieve_connection_pool
- assert_not_nil @handler.retrieve_connection_pool(@klass)
+ assert_not_nil @handler.retrieve_connection_pool(@spec_name)
end
- def test_retrieve_connection_pool_uses_superclass_when_no_subclass_connection
- assert_not_nil @handler.retrieve_connection_pool(@subklass)
- end
-
- def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove
- sub_pool = @handler.establish_connection(@subklass, Base.connection_pool.spec)
- assert_same sub_pool, @handler.retrieve_connection_pool(@subklass)
-
- @handler.remove_connection @subklass
- assert_same @pool, @handler.retrieve_connection_pool(@subklass)
+ def test_retrieve_connection_pool_with_invalid_id
+ assert_nil @handler.retrieve_connection_pool("foo")
end
def test_connection_pools
@@ -79,7 +76,7 @@ module ActiveRecord
pid = fork {
rd.close
- pool = @handler.retrieve_connection_pool(@klass)
+ pool = @handler.retrieve_connection_pool(@spec_name)
wr.write Marshal.dump pool.schema_cache.size
wr.close
exit!
@@ -91,6 +88,36 @@ module ActiveRecord
assert_equal @pool.schema_cache.size, Marshal.load(rd.read)
rd.close
end
+
+ def test_a_class_using_custom_pool_and_switching_back_to_primary
+ klass2 = Class.new(Base) { def self.name; 'klass2'; end }
+
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+
+ pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config)
+ assert_equal klass2.connection.object_id, pool.connection.object_id
+ refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+
+ klass2.remove_connection
+
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ end
+
+ def test_connection_specification_name_should_fallback_to_parent
+ klassA = Class.new(Base)
+ klassB = Class.new(klassA)
+
+ assert_equal klassB.connection_specification_name, klassA.connection_specification_name
+ klassA.connection_specification_name = "readonly"
+ assert_equal "readonly", klassB.connection_specification_name
+ end
+
+ def test_remove_connection_should_not_remove_parent
+ klass2 = Class.new(Base) { def self.name; 'klass2'; end }
+ klass2.remove_connection
+ refute_nil ActiveRecord::Base.connection.object_id
+ assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
index ea2196cda2..d204fce59f 100644
--- a/activerecord/test/cases/connection_adapters/connection_specification_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
@@ -4,7 +4,7 @@ module ActiveRecord
module ConnectionAdapters
class ConnectionSpecificationTest < ActiveRecord::TestCase
def test_dup_deep_copy_config
- spec = ConnectionSpecification.new({ :a => :b }, "bar")
+ spec = ConnectionSpecification.new("primary", { :a => :b }, "bar")
assert_not_equal(spec.config.object_id, spec.dup.config.object_id)
end
end
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 9ee92a3cd2..f25b85e8a7 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
@@ -37,7 +37,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -47,7 +47,7 @@ module ActiveRecord
config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
actual = resolve_spec(:foo, config)
- expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost" }
+ expected = { "adapter" => "postgresql", "database" => "foo", "host" => "localhost","name"=>"foo" }
assert_equal expected, actual
end
@@ -55,7 +55,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "postgres://localhost/foo"
config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:production, config)
- expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" }
+ expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost", "name"=>"production" }
assert_equal expected, actual
end
@@ -93,7 +93,7 @@ module ActiveRecord
ENV['DATABASE_URL'] = "ibm-db://localhost/foo"
config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } }
actual = resolve_spec(:default_env, config)
- expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" }
+ expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost", "name"=>"default_env" }
assert_equal expected, actual
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index 7566863653..3acbafbff4 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -1,6 +1,6 @@
require "cases/helper"
-unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup
+unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strings for lookup
module ActiveRecord
module ConnectionAdapters
class TypeLookupTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index d43668e57c..1f9b6add7a 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -4,6 +4,8 @@ require "rack"
module ActiveRecord
module ConnectionAdapters
class ConnectionManagementTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
class App
attr_reader :calls
def initialize
@@ -19,7 +21,7 @@ module ActiveRecord
def setup
@env = {}
@app = App.new
- @management = ConnectionManagement.new(@app)
+ @management = middleware(@app)
# make sure we have an active connection
assert ActiveRecord::Base.connection
@@ -27,17 +29,12 @@ module ActiveRecord
end
def test_app_delegation
- manager = ConnectionManagement.new(@app)
+ manager = middleware(@app)
manager.call @env
assert_equal [@env], @app.calls
end
- def test_connections_are_active_after_call
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
def test_body_responds_to_each
_, _, body = @management.call(@env)
bits = []
@@ -51,46 +48,41 @@ module ActiveRecord
assert !ActiveRecord::Base.connection_handler.active_connections?
end
- def test_active_connections_are_not_cleared_on_body_close_during_test
- @env['rack.test'] = true
- _, _, body = @management.call(@env)
- body.close
- assert ActiveRecord::Base.connection_handler.active_connections?
+ def test_active_connections_are_not_cleared_on_body_close_during_transaction
+ ActiveRecord::Base.transaction do
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
def test_connections_closed_if_exception
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
+ explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
assert !ActiveRecord::Base.connection_handler.active_connections?
end
- def test_connections_not_closed_if_exception_and_test
- @env['rack.test'] = true
- app = Class.new(App) { def call(env); raise; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(RuntimeError) { explosive.call(@env) }
- assert ActiveRecord::Base.connection_handler.active_connections?
- end
-
- def test_connections_closed_if_exception_and_explicitly_not_test
- @env['rack.test'] = false
- app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
- explosive = ConnectionManagement.new(app)
- assert_raises(NotImplementedError) { explosive.call(@env) }
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ def test_connections_not_closed_if_exception_inside_transaction
+ ActiveRecord::Base.transaction do
+ app = Class.new(App) { def call(env); raise RuntimeError; end }.new
+ explosive = middleware(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
+ executor.wrap do
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
end
test "proxy is polite to its body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
- response_body = ConnectionManagement.new(app).call(@env)[2]
+ response_body = middleware(app).call(@env)[2]
assert response_body.respond_to?(:to_path)
assert_equal "/path", response_body.to_path
end
@@ -98,9 +90,23 @@ module ActiveRecord
test "doesn't mutate the original response" do
original_response = [200, {}, 'hi']
app = lambda { |_| original_response }
- ConnectionManagement.new(app).call(@env)[2]
+ middleware(app).call(@env)[2]
assert_equal 'hi', original_response.last
end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor).tap do |exe|
+ ActiveRecord::QueryCache.install_executor_hooks(exe)
+ end
+ end
+
+ def middleware(app)
+ lambda do |env|
+ a, b, c = executor.wrap { app.call(env) }
+ [a, b, Rack::BodyProxy.new(c) { }]
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index efa3e0455e..a45ee281c7 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -335,11 +335,10 @@ module ActiveRecord
# is called with an anonymous class
def test_anonymous_class_exception
anonymous = Class.new(ActiveRecord::Base)
- handler = ActiveRecord::Base.connection_handler
- assert_raises(RuntimeError) {
- handler.establish_connection anonymous, nil
- }
+ assert_raises(RuntimeError) do
+ anonymous.establish_connection
+ end
end
def test_pool_sets_connection_schema_cache
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 3c2f5d4219..b30a83d9ce 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -28,7 +28,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key
@@ -36,7 +37,8 @@ module ActiveRecord
assert_equal({
"adapter" => "abstract",
"host" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
def test_url_sub_key_merges_correctly
@@ -46,7 +48,8 @@ module ActiveRecord
"adapter" => "abstract",
"host" => "foo",
"encoding" => "utf8",
- "pool" => "3" }, spec)
+ "pool" => "3",
+ "name" => "production"}, spec)
end
def test_url_host_no_db
@@ -57,6 +60,12 @@ module ActiveRecord
"encoding" => "utf8" }, spec)
end
+ def test_url_missing_scheme
+ spec = resolve 'foo'
+ assert_equal({
+ "database" => "foo" }, spec)
+ end
+
def test_url_host_db
spec = resolve 'abstract://foo/bar?encoding=utf8'
assert_equal({
@@ -107,9 +116,19 @@ module ActiveRecord
assert_equal({
"adapter" => "sqlite3",
"database" => "foo",
- "encoding" => "utf8" }, spec)
+ "encoding" => "utf8",
+ "name" => "production"}, spec)
end
+ def test_spec_name_on_key_lookup
+ spec = spec(:readonly, 'readonly' => {'adapter' => 'sqlite3'})
+ assert_equal "readonly", spec.name
+ end
+
+ def test_spec_name_with_inline_config
+ spec = spec({'adapter' => 'sqlite3'})
+ assert_equal "primary", spec.name, "should default to primary id"
+ end
end
end
end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 922cb59280..66b4c3f1ff 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -151,7 +151,7 @@ class CounterCacheTest < ActiveRecord::TestCase
test "reset the right counter if two have the same foreign key" do
michael = people(:michael)
- assert_nothing_raised(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Person.reset_counters(michael.id, :friends_too)
end
end
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index ba085991e0..3169408ac0 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -13,6 +13,12 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
assert_not_nil return_the_inserted_id(method: :create)
end
+ def test_insert_update_delete_sql_is_deprecated
+ assert_deprecated { @connection.insert_sql("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") }
+ assert_deprecated { @connection.update_sql("UPDATE accounts SET credit_limit = 6000 WHERE firm_id = 42") }
+ assert_deprecated { @connection.delete_sql("DELETE FROM accounts WHERE firm_id = 42") }
+ end
+
private
def return_the_inserted_id(method:)
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index e996d142a2..f8664d83bd 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class DateTimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 69b0487dd8..067513e24c 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -201,8 +201,7 @@ if current_adapter?(:Mysql2Adapter)
assert_equal '0', klass.columns_hash['zero'].default
assert !klass.columns_hash['zero'].null
- # 0 in MySQL 4, nil in 5.
- assert [0, nil].include?(klass.columns_hash['omit'].default)
+ assert_equal nil, klass.columns_hash['omit'].default
assert !klass.columns_hash['omit'].null
assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index cd1967c373..f9794518c7 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -5,23 +5,6 @@ require 'models/parrot'
require 'models/person' # For optimistic locking
require 'models/aircraft'
-class Pirate # Just reopening it, not defining it
- attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
- attr_accessor :changes_detected_in_after_update # Actual changes
-
- after_update :check_changes
-
-private
- # after_save/update and the model itself
- # can end up checking dirty status and acting on the results
- def check_changes
- if self.changed?
- self.detected_changes_in_after_update = true
- self.changes_detected_in_after_update = self.changes
- end
- end
-end
-
class NumericData < ActiveRecord::Base
self.table_name = 'numeric_data'
end
@@ -37,8 +20,8 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_changes
# New record - no changes.
pirate = Pirate.new
- assert !pirate.catchphrase_changed?
- assert_nil pirate.catchphrase_change
+ assert_equal false, pirate.catchphrase_changed?
+ assert_equal false, pirate.non_validated_parrot_id_changed?
# Change catchphrase.
pirate.catchphrase = 'arrr'
@@ -734,6 +717,17 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal "arr", pirate.catchphrase
end
+ test "attributes assigned but not selected are dirty" do
+ person = Person.select(:id).first
+ refute person.changed?
+
+ person.first_name = "Sean"
+ assert person.changed?
+
+ person.first_name = nil
+ assert person.changed?
+ end
+
private
def with_partial_writes(klass, on = true)
old = klass.partial_writes?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 75a74c052d..6eaaa30cd0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -43,7 +43,7 @@ class FinderTest < ActiveRecord::TestCase
end
assert_equal "should happen", exception.message
- assert_nothing_raised(RuntimeError) do
+ assert_nothing_raised do
Topic.all.find(-> { raise "should not happen" }) { |e| e.title == topics(:first).title }
end
end
@@ -101,7 +101,7 @@ class FinderTest < ActiveRecord::TestCase
def test_find_with_ids_where_and_limit
# Please note that Topic 1 is the only not approved so
- # if it were among the first 3 it would raise a ActiveRecord::RecordNotFound
+ # if it were among the first 3 it would raise an ActiveRecord::RecordNotFound
records = Topic.where(approved: true).limit(3).find([3,2,5,1,4])
assert_equal 3, records.size
assert_equal 'The Third Topic of the day', records[0].title
@@ -173,11 +173,9 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_exists_fails_when_parameter_has_invalid_type
- assert_raises(RangeError) do
- assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
- end
+ def test_exists_returns_false_when_parameter_has_invalid_type
assert_equal false, Topic.exists?("foo")
+ assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
end
def test_exists_does_not_select_columns_without_alias
@@ -486,6 +484,66 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_second_to_last
+ assert_equal topics(:fourth).title, Topic.second_to_last.title
+
+ # test with offset
+ assert_equal topics(:fourth), Topic.offset(1).second_to_last
+ assert_equal topics(:fourth), Topic.offset(2).second_to_last
+ assert_equal topics(:fourth), Topic.offset(3).second_to_last
+ assert_equal nil, Topic.offset(4).second_to_last
+ assert_equal nil, Topic.offset(5).second_to_last
+
+ #test with limit
+ # assert_equal nil, Topic.limit(1).second # TODO: currently failing
+ assert_equal nil, Topic.limit(1).second_to_last
+ end
+
+ def test_second_to_last_have_primary_key_order_by_default
+ expected = topics(:fourth)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.second_to_last
+ end
+
+ def test_model_class_responds_to_second_to_last_bang
+ assert Topic.second_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.second_to_last!
+ end
+ end
+
+ def test_third_to_last
+ assert_equal topics(:third).title, Topic.third_to_last.title
+
+ # test with offset
+ assert_equal topics(:third), Topic.offset(1).third_to_last
+ assert_equal topics(:third), Topic.offset(2).third_to_last
+ assert_equal nil, Topic.offset(3).third_to_last
+ assert_equal nil, Topic.offset(4).third_to_last
+ assert_equal nil, Topic.offset(5).third_to_last
+
+ # test with limit
+ # assert_equal nil, Topic.limit(1).third # TODO: currently failing
+ assert_equal nil, Topic.limit(1).third_to_last
+ # assert_equal nil, Topic.limit(2).third # TODO: currently failing
+ assert_equal nil, Topic.limit(2).third_to_last
+ end
+
+ def test_third_to_last_have_primary_key_order_by_default
+ expected = topics(:third)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.third_to_last
+ end
+
+ def test_model_class_responds_to_third_to_last_bang
+ assert Topic.third_to_last!
+ Topic.delete_all
+ assert_raises_with_message ActiveRecord::RecordNotFound, "Couldn't find Topic" do
+ Topic.third_to_last!
+ end
+ end
+
def test_last_bang_present
assert_nothing_raised do
assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
@@ -516,16 +574,44 @@ class FinderTest < ActiveRecord::TestCase
assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2)
end
- def test_last_with_integer_and_order_should_not_use_sql_limit
- query = assert_sql { Topic.order("title").last(5).entries }
- assert_equal 1, query.length
- assert_no_match(/LIMIT/, query.first)
+ def test_last_with_integer_and_order_should_use_sql_limit
+ relation = Topic.order("title")
+ assert_queries(1) { relation.last(5) }
+ assert !relation.loaded?
+ end
+
+ def test_last_with_integer_and_reorder_should_use_sql_limit
+ relation = Topic.reorder("title")
+ assert_queries(1) { relation.last(5) }
+ assert !relation.loaded?
+ end
+
+ def test_last_on_loaded_relation_should_not_use_sql
+ relation = Topic.limit(10).load
+ assert_no_queries do
+ relation.last
+ relation.last(2)
+ end
+ end
+
+ def test_last_with_irreversible_order
+ assert_deprecated do
+ Topic.order("coalesce(author_name, title)").last
+ end
end
- def test_last_with_integer_and_reorder_should_not_use_sql_limit
- query = assert_sql { Topic.reorder("title").last(5).entries }
- assert_equal 1, query.length
- assert_no_match(/LIMIT/, query.first)
+ def test_last_on_relation_with_limit_and_offset
+ post = posts('sti_comments')
+
+ comments = post.comments.order(id: :asc)
+ assert_equal comments.limit(2).to_a.last, comments.limit(2).last
+ assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
+ assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
+
+ comments = comments.offset(1)
+ assert_equal comments.limit(2).to_a.last, comments.limit(2).last
+ assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
+ assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
end
def test_take_and_first_and_last_with_integer_should_return_an_array
@@ -564,11 +650,16 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(approved: true).find(1) }
end
- def test_find_on_hash_conditions_with_explicit_table_name
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_string
assert Topic.where('topics.approved' => false).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved' => true).find(1) }
end
+ def test_find_on_hash_conditions_with_qualified_attribute_dot_notation_symbol
+ assert Topic.where('topics.approved': false).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.where('topics.approved': true).find(1) }
+ end
+
def test_find_on_hash_conditions_with_hashed_table_name
assert Topic.where(topics: { approved: false }).find(1)
assert_raise(ActiveRecord::RecordNotFound) { Topic.where(topics: { approved: true }).find(1) }
@@ -1058,7 +1149,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_finder_with_offset_string
- assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.offset("3").to_a }
+ assert_nothing_raised { Topic.offset("3").to_a }
end
test "find_by with hash conditions returns the first matching record" do
diff --git a/activerecord/test/cases/fixture_set/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb
index 242e7a9bec..e64b90507e 100644
--- a/activerecord/test/cases/fixture_set/file_test.rb
+++ b/activerecord/test/cases/fixture_set/file_test.rb
@@ -135,6 +135,12 @@ END
end
end
+ def test_erb_filename
+ filename = 'filename.yaml'
+ erb = File.new(filename).send(:prepare_erb, "<% Rails.env %>\n")
+ assert_equal erb.filename, filename
+ end
+
private
def tmp_yaml(name, contents)
t = Tempfile.new name
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index da934ab8fe..9455d4886c 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -857,7 +857,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("X marks the spot!", pirates(:mark).catchphrase)
end
- def test_supports_label_interpolation_for_fixnum_label
+ def test_supports_label_interpolation_for_integer_label
assert_equal("#1 pirate!", pirates(1).catchphrase)
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 95f8706d73..d2fdf03e9d 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -1,5 +1,3 @@
-require File.expand_path('../../../../load_paths', __FILE__)
-
require 'config'
require 'active_support/testing/autorun'
diff --git a/activerecord/test/cases/hot_compatibility_test.rb b/activerecord/test/cases/hot_compatibility_test.rb
index 5ba9a1029a..9fc75b7377 100644
--- a/activerecord/test/cases/hot_compatibility_test.rb
+++ b/activerecord/test/cases/hot_compatibility_test.rb
@@ -1,7 +1,9 @@
require 'cases/helper'
+require 'support/connection_helper'
class HotCompatibilityTest < ActiveRecord::TestCase
self.use_transactional_tests = false
+ include ConnectionHelper
setup do
@klass = Class.new(ActiveRecord::Base) do
@@ -51,4 +53,90 @@ class HotCompatibilityTest < ActiveRecord::TestCase
record.reload
assert_equal 'bar', record.foo
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ test "cleans up after prepared statement failure in a transaction" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ record.reload
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+
+ test "cleans up after prepared statement failure in nested transactions" do
+ with_two_connections do |original_connection, ddl_connection|
+ record = @klass.create! bar: 'bar'
+
+ # prepare the reload statement in a transaction
+ @klass.transaction do
+ record.reload
+ end
+
+ assert get_prepared_statement_cache(@klass.connection).any?,
+ "expected prepared statement cache to have something in it"
+
+ # add a new column
+ ddl_connection.add_column :hot_compatibilities, :baz, :string
+
+ assert_raise(ActiveRecord::PreparedStatementCacheExpired) do
+ @klass.transaction do
+ @klass.transaction do
+ @klass.transaction do
+ record.reload
+ end
+ end
+ end
+ end
+
+ assert_empty get_prepared_statement_cache(@klass.connection),
+ "expected prepared statement cache to be empty but it wasn't"
+ end
+ end
+ end
+
+ private
+
+ def get_prepared_statement_cache(connection)
+ connection.instance_variable_get(:@statements)
+ .instance_variable_get(:@cache)[Process.pid]
+ end
+
+ # Rails will automatically clear the prepared statements on the connection
+ # that runs the migration, so we use two connections to simulate what would
+ # actually happen on a production system; we'd have one connection running the
+ # migration from the rake task ("ddl_connection" here), and we'd have another
+ # connection in a web worker.
+ def with_two_connections
+ run_without_connection do |original_connection|
+ ActiveRecord::Base.establish_connection(original_connection.merge(pool_size: 2))
+ begin
+ ddl_connection = ActiveRecord::Base.connection_pool.checkout
+ begin
+ yield original_connection, ddl_connection
+ ensure
+ ActiveRecord::Base.connection_pool.checkin ddl_connection
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 7da6842047..e234b9a6a9 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -441,12 +441,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
include InheritanceTestHelper
fixtures :companies
- def setup
- ActiveSupport::Dependencies.log_activity = true
- end
-
teardown do
- ActiveSupport::Dependencies.log_activity = false
self.class.const_remove :FirmOnTheFly rescue nil
Firm.const_remove :FirmOnTheFly rescue nil
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index e030f6c588..aba854820b 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -151,6 +151,14 @@ module ActiveRecord
end
end
+ class RevertCustomForeignKeyTable < SilentMigration
+ def change
+ change_table(:horses) do |t|
+ t.references :owner, foreign_key: { to_table: :developers }
+ end
+ end
+ end
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -353,6 +361,13 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
+ def test_migrations_can_handle_foreign_keys_to_specific_tables
+ migration = RevertCustomForeignKeyTable.new
+ InvertibleMigration.migrate(:up)
+ migration.migrate(:up)
+ migration.migrate(:down)
+ end
+
# MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
unless current_adapter?(:Mysql2Adapter, :OracleAdapter)
def test_migrate_revert_add_index_with_name
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 4fe76e563a..9fc0041892 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -169,6 +169,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 1, p1.lock_version
end
+ def test_lock_new_when_explicitly_passing_nil
+ p1 = Person.new(:first_name => 'anika', lock_version: nil)
+ p1.save!
+ assert_equal 0, p1.lock_version
+ end
+
def test_touch_existing_lock
p1 = Person.find(1)
assert_equal 0, p1.lock_version
@@ -441,7 +447,7 @@ unless in_memory_db?
def test_lock_sending_custom_lock_statement
Person.transaction do
person = Person.find(1)
- assert_sql(/LIMIT \$\d FOR SHARE NOWAIT/) do
+ assert_sql(/LIMIT \$?\d FOR SHARE NOWAIT/) do
person.lock!('FOR SHARE NOWAIT')
end
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 707a2d1da1..c97960a412 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -215,5 +215,11 @@ class LogSubscriberTest < ActiveRecord::TestCase
wait
assert_match(/<16 bytes of binary data>/, @logger.logged(:debug).join)
end
+
+ def test_binary_data_hash
+ Binary.create(data: { a: 1 })
+ wait
+ assert_match(/<7 bytes of binary data>/, @logger.logged(:debug).join)
+ end
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index c7a1b81a75..29546525f3 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -154,7 +154,7 @@ module ActiveRecord
assert_equal String, bob.first_name.class
assert_equal String, bob.last_name.class
assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
+ assert_kind_of Integer, bob.age
assert_equal Time, bob.birthday.class
if current_adapter?(:OracleAdapter)
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 6a9cdd9d29..60ca90464d 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -21,7 +21,7 @@ module ActiveRecord
teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Migration.verbose = @verbose_was
- ActiveRecord::SchemaMigration.delete_all
+ ActiveRecord::SchemaMigration.delete_all rescue nil
end
def test_migration_doesnt_remove_named_index
@@ -71,6 +71,48 @@ module ActiveRecord
ensure
connection.drop_table :more_testings rescue nil
end
+
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table
+ migration = Class.new(ActiveRecord::Migration) {
+ def migrate(x)
+ create_table :more_testings do |t|
+ t.timestamps
+ end
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:more_testings).find { |c| c.name == 'created_at' }.null
+ assert connection.columns(:more_testings).find { |c| c.name == 'updated_at' }.null
+ ensure
+ connection.drop_table :more_testings rescue nil
+ end
+
+ def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table
+ migration = Class.new(ActiveRecord::Migration) {
+ def migrate(x)
+ add_timestamps :testings
+ end
+ }.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert connection.columns(:testings).find { |c| c.name == 'created_at' }.null
+ assert connection.columns(:testings).find { |c| c.name == 'updated_at' }.null
+ end
+
+ def test_legacy_migrations_get_deprecation_warning_when_run
+ migration = Class.new(ActiveRecord::Migration) {
+ def up
+ add_column :testings, :baz, :string
+ end
+ }
+
+ assert_deprecated do
+ migration.migrate :up
+ end
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 0a7b57455c..920c472c73 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -132,6 +132,13 @@ module ActiveRecord
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_create_join_table_with_uuid
+ connection.create_join_table :artists, :musics, column_options: { type: :uuid }
+ assert_equal [:uuid, :uuid], connection.columns(:artists_musics).map(&:type)
+ end
+ end
+
private
def with_table_cleanup
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index b01415afb2..9e19eb9f73 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -144,6 +144,52 @@ module ActiveRecord
@connection.drop_table "testing", if_exists: true
end
end
+
+ class CreateDogsMigration < ActiveRecord::Migration::Current
+ def change
+ create_table :dog_owners
+
+ create_table :dogs do |t|
+ t.references :dog_owner, foreign_key: true
+ end
+ end
+ end
+
+ def test_references_foreign_key_with_prefix
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ migration = CreateDogsMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("p_dogs").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_prefix = nil
+ end
+
+ def test_references_foreign_key_with_suffix
+ ActiveRecord::Base.table_name_suffix = '_s'
+ migration = CreateDogsMigration.new
+ silence_stream($stdout) { migration.migrate(:up) }
+ assert_equal 1, @connection.foreign_keys("dogs_s").size
+ ensure
+ silence_stream($stdout) { migration.migrate(:down) }
+ ActiveRecord::Base.table_name_suffix = nil
+ end
+
+ test "multiple foreign keys can be added to the same table" do
+ @connection.create_table :testings do |t|
+ t.integer :col_1
+ t.integer :col_2
+
+ t.foreign_key :testing_parents, column: :col_1
+ t.foreign_key :testing_parents, column: :col_2
+ end
+
+ fks = @connection.foreign_keys("testings")
+
+ fk_definitions = fks.map {|fk| [fk.from_table, fk.to_table, fk.column] }
+ assert_equal([["testings", "testing_parents", "col_1"],
+ ["testings", "testing_parents", "col_2"]], fk_definitions)
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb
index b9ce6bbc55..70c64f3e71 100644
--- a/activerecord/test/cases/migration/references_statements_test.rb
+++ b/activerecord/test/cases/migration/references_statements_test.rb
@@ -55,6 +55,11 @@ module ActiveRecord
assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id')
end
+ def test_creates_named_unique_index
+ add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id', unique: true }
+ assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id', unique: true )
+ end
+
def test_creates_reference_id_with_specified_type
add_reference table_name, :user, type: :string
assert column_exists?(table_name, :user_id, :string)
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
deleted file mode 100644
index 24cba84a09..0000000000
--- a/activerecord/test/cases/migration/table_and_index_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class Migration
- class TableAndIndexTest < ActiveRecord::TestCase
- def test_add_schema_info_respects_prefix_and_suffix
- conn = ActiveRecord::Base.connection
-
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
- # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
- ActiveRecord::Base.table_name_prefix = 'p_'
- ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true)
-
- conn.initialize_schema_migrations_table
-
- assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- end
- end
- end
-end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f51e366b1d..36b6662820 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -69,6 +69,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
+ def test_migration_version_matches_component_version
+ assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version
+ end
+
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
@@ -176,7 +180,7 @@ class MigrationTest < ActiveRecord::TestCase
# is_a?(Bignum)
assert_kind_of Integer, b.world_population
assert_equal 6000000000, b.world_population
- assert_kind_of Fixnum, b.my_house_population
+ assert_kind_of Integer, b.my_house_population
assert_equal 3, b.my_house_population
assert_kind_of BigDecimal, b.bank_balance
assert_equal BigDecimal("1586.43"), b.bank_balance
@@ -192,8 +196,6 @@ class MigrationTest < ActiveRecord::TestCase
# of 0, they take on the compile-time limit for precision and scale,
# so the following should succeed unless you have used really wacky
# compilation options
- # - SQLite2 has the default behavior of preserving all data sent in,
- # so this happens there too
assert_kind_of BigDecimal, b.value_of_e
assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
elsif current_adapter?(:SQLite3Adapter)
@@ -202,7 +204,7 @@ class MigrationTest < ActiveRecord::TestCase
assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
else
# - SQL standard is an integer
- assert_kind_of Fixnum, b.value_of_e
+ assert_kind_of Integer, b.value_of_e
assert_equal 2, b.value_of_e
end
@@ -357,14 +359,14 @@ class MigrationTest < ActiveRecord::TestCase
def test_internal_metadata_table_name
original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
- assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
+ assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name
+ ActiveRecord::Base.table_name_prefix = "p_"
+ ActiveRecord::Base.table_name_suffix = "_s"
Reminder.reset_table_name
- assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name
+ assert_equal "p_ar_internal_metadata_s", ActiveRecord::InternalMetadata.table_name
ActiveRecord::Base.internal_metadata_table_name = "changed"
Reminder.reset_table_name
- assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name
+ assert_equal "p_changed_s", ActiveRecord::InternalMetadata.table_name
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
Reminder.reset_table_name
@@ -426,6 +428,38 @@ class MigrationTest < ActiveRecord::TestCase
ENV["RACK_ENV"] = original_rack_env
end
+ def test_internal_metadata_stores_environment_when_other_data_exists
+ ActiveRecord::InternalMetadata.delete_all
+ ActiveRecord::InternalMetadata[:foo] = 'bar'
+
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+ assert_equal 'bar', ActiveRecord::InternalMetadata[:foo]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ end
+
+ def test_rename_internal_metadata_table
+ original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name
+
+ ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas"
+ Reminder.reset_table_name
+
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+
+ assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name
+ ensure
+ ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name
+ Reminder.reset_table_name
+ end
+
def test_proper_table_name_on_migration
reminder_class = new_isolated_reminder_class
migration = ActiveRecord::Migration.new
@@ -503,13 +537,12 @@ class MigrationTest < ActiveRecord::TestCase
data_column = columns.detect { |c| c.name == "data" }
assert_nil data_column.default
-
+ ensure
Person.connection.drop_table :binary_testings, if_exists: true
end
unless mysql_enforcing_gtid_consistency?
def test_create_table_with_query
- Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)
Person.connection.create_table :table_from_query_testings, as: "SELECT id FROM person"
@@ -517,12 +550,11 @@ class MigrationTest < ActiveRecord::TestCase
columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name
-
+ ensure
Person.connection.drop_table :table_from_query_testings rescue nil
end
def test_create_table_with_query_from_relation
- Person.connection.drop_table :table_from_query_testings rescue nil
Person.connection.create_table(:person, force: true)
Person.connection.create_table :table_from_query_testings, as: Person.select(:id)
@@ -530,7 +562,7 @@ class MigrationTest < ActiveRecord::TestCase
columns = Person.connection.columns(:table_from_query_testings)
assert_equal 1, columns.length
assert_equal "id", columns.first.name
-
+ ensure
Person.connection.drop_table :table_from_query_testings rescue nil
end
end
@@ -573,8 +605,7 @@ class MigrationTest < ActiveRecord::TestCase
end
if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
- def test_out_of_range_limit_should_raise
- Person.connection.drop_table :test_limits rescue nil
+ def test_out_of_range_integer_limit_should_raise
e = assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
Person.connection.create_table :test_integer_limits, :force => true do |t|
t.column :bigone, :integer, :limit => 10
@@ -582,16 +613,22 @@ class MigrationTest < ActiveRecord::TestCase
end
assert_match(/No integer type has byte size 10/, e.message)
+ ensure
+ Person.connection.drop_table :test_integer_limits, if_exists: true
+ end
+ end
- unless current_adapter?(:PostgreSQLAdapter)
- assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
- Person.connection.create_table :test_text_limits, :force => true do |t|
- t.column :bigtext, :text, :limit => 0xfffffffff
- end
+ if current_adapter?(:Mysql2Adapter)
+ def test_out_of_range_text_limit_should_raise
+ e = assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
+ Person.connection.create_table :test_text_limits, force: true do |t|
+ t.text :bigtext, limit: 0xfffffffff
end
end
- Person.connection.drop_table :test_limits rescue nil
+ assert_match(/No text type has byte length #{0xfffffffff}/, e.message)
+ ensure
+ Person.connection.drop_table :test_text_limits, if_exists: true
end
end
@@ -713,7 +750,7 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
connection.add_index :values, :value
connection.remove_index :values, :column => :value
end
-
+ ensure
connection.drop_table :values rescue nil
end
end
@@ -725,11 +762,11 @@ class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase
t.integer :value
end
- assert_nothing_raised ArgumentError do
+ assert_nothing_raised do
connection.add_index :values, :value, name: 'a_different_name'
connection.remove_index :values, column: :value, name: 'a_different_name'
end
-
+ ensure
connection.drop_table :values rescue nil
end
end
@@ -1091,4 +1128,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = old
end
+ def test_unknown_migration_version_should_raise_an_argument_error
+ assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
+ end
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 7f31325f47..486bcc22df 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -63,7 +63,7 @@ class ModulesTest < ActiveRecord::TestCase
def test_assign_ids
firm = MyApplication::Business::Firm.first
- assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ assert_nothing_raised do
firm.client_ids = [MyApplication::Business::Client.first.id]
end
end
@@ -72,7 +72,7 @@ class ModulesTest < ActiveRecord::TestCase
def test_eager_loading_in_modules
clients = []
- assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ assert_nothing_raised do
clients << MyApplication::Business::Client.references(:accounts).merge!(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3)
clients << MyApplication::Business::Client.includes(:firm => :account).find(3)
end
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index ae18573126..d05cb22740 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -11,15 +11,13 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic.attributes = attributes
# note that extra #to_date call allows test to pass for Oracle, which
# treats dates/times the same
- assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
+ assert_equal Date.new(2004, 6, 24), topic.last_read.to_date
end
def test_multiparameter_attributes_on_date_with_empty_year
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -27,8 +25,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -36,8 +32,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -45,8 +39,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -54,8 +46,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
@@ -63,8 +53,6 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
topic = Topic.find(1)
topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
assert_nil topic.last_read
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index 39cdcf5403..a4fbf579a1 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -24,6 +24,13 @@ class MultipleDbTest < ActiveRecord::TestCase
assert_equal(ActiveRecord::Base.connection, Entrant.connection)
end
+ def test_swapping_the_connection
+ old_spec_name, Course.connection_specification_name = Course.connection_specification_name, "primary"
+ assert_equal(Entrant.connection, Course.connection)
+ ensure
+ Course.connection_specification_name = old_spec_name
+ end
+
def test_find
c1 = Course.find(1)
assert_equal "Ruby Development", c1.name
@@ -89,8 +96,8 @@ class MultipleDbTest < ActiveRecord::TestCase
end
def test_connection
- assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection
- assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection
+ assert_equal Entrant.arel_engine.connection.object_id, Bird.arel_engine.connection.object_id
+ assert_not_equal Entrant.arel_engine.connection.object_id, Course.arel_engine.connection.object_id
end
unless in_memory_db?
@@ -104,7 +111,7 @@ class MultipleDbTest < ActiveRecord::TestCase
def test_associations_should_work_when_model_has_no_connection
begin
ActiveRecord::Base.remove_connection
- assert_nothing_raised ActiveRecord::ConnectionNotEstablished do
+ assert_nothing_raised do
College.first.courses.first
end
ensure
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 6fbc6196cc..11fb164d50 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -61,6 +61,13 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
end
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes
+ exception = assert_raise ActiveModel::UnknownAttributeError do
+ Pirate.new(:ship_attributes => { :sail => true })
+ end
+ assert_equal "unknown attribute 'sail' for Ship.", exception.message
+ end
+
def test_should_disable_allow_destroy_by_default
Pirate.accepts_nested_attributes_for :ship
@@ -69,7 +76,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.update(ship_attributes: { '_destroy' => true, :id => ship.id })
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.ship.reload }
+ assert_nothing_raised { pirate.ship.reload }
end
def test_a_model_should_respond_to_underscore_destroy_and_return_if_it_is_marked_for_destruction
@@ -173,7 +180,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate = Pirate.new(:catchphrase => "Stop wastin' me time")
pirate.ship_attributes = { :id => "" }
- assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! }
+ assert_nothing_raised { pirate.save! }
end
def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_attributes_are_set_to_update_existing_record
@@ -504,7 +511,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_not_destroy_an_existing_record_if_destroy_is_not_truthy
[nil, '0', 0, 'false', false].each do |not_truth|
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: not_truth })
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
+ assert_nothing_raised { @ship.pirate.reload }
end
end
@@ -512,7 +519,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?)
@ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' })
- assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload }
+ assert_nothing_raised { @ship.pirate.reload }
ensure
Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?)
end
@@ -529,7 +536,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
pirate = @ship.pirate
@ship.attributes = { :pirate_attributes => { :id => pirate.id, '_destroy' => true } }
- assert_nothing_raised(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
+ assert_nothing_raised { Pirate.find(pirate.id) }
@ship.save
assert_raise(ActiveRecord::RecordNotFound) { Pirate.find(pirate.id) }
end
@@ -582,6 +589,13 @@ module NestedAttributesOnACollectionAssociationTests
assert_respond_to @pirate, association_setter
end
+ def test_should_raise_an_UnknownAttributeError_for_non_existing_nested_attributes_for_has_many
+ exception = assert_raise ActiveModel::UnknownAttributeError do
+ @pirate.parrots_attributes = [{ peg_leg: true }]
+ end
+ assert_equal "unknown attribute 'peg_leg' for Parrot.", exception.message
+ end
+
def test_should_save_only_one_association_on_create
pirate = Pirate.create!({
:catchphrase => 'Arr',
@@ -699,7 +713,7 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_not_assign_destroy_key_to_a_record
- assert_nothing_raised ActiveRecord::UnknownAttributeError do
+ assert_nothing_raised do
@pirate.send(association_setter, { 'foo' => { '_destroy' => '0' }})
end
end
@@ -734,8 +748,8 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
+ assert_nothing_raised { @pirate.send(association_setter, {}) }
+ assert_nothing_raised { @pirate.send(association_setter, Hash.new) }
exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
@@ -810,7 +824,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_can_use_symbols_as_object_identifier
@pirate.attributes = { :parrots_attributes => { :foo => { :name => 'Lovely Day' }, :bar => { :name => 'Blown Away' } } }
- assert_nothing_raised(NoMethodError) { @pirate.save! }
+ assert_nothing_raised { @pirate.save! }
end
def test_numeric_column_changes_from_zero_to_no_empty_string
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index af15e63d9c..56092aaa0c 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase
end
end
- def test_dupd_becomes_persists_changes_from_the_original
+ def test_duped_becomes_persists_changes_from_the_original
original = topics(:first)
copy = original.dup.becomes(Reply)
copy.save!
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 7e18313c00..52eac4a124 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -130,27 +130,25 @@ class PrimaryKeysTest < ActiveRecord::TestCase
end
def test_supports_primary_key
- assert_nothing_raised NoMethodError do
+ assert_nothing_raised do
ActiveRecord::Base.connection.supports_primary_key?
end
end
- def test_primary_key_returns_value_if_it_exists
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'developers'
- end
+ if ActiveRecord::Base.connection.supports_primary_key?
+ def test_primary_key_returns_value_if_it_exists
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'developers'
+ end
- if ActiveRecord::Base.connection.supports_primary_key?
assert_equal 'id', klass.primary_key
end
- end
- def test_primary_key_returns_nil_if_it_does_not_exist
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = 'developers_projects'
- end
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'developers_projects'
+ end
- if ActiveRecord::Base.connection.supports_primary_key?
assert_nil klass.primary_key
end
end
@@ -230,9 +228,10 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
def test_any_type_primary_key
assert_equal "code", Barcode.primary_key
- column_type = Barcode.type_for_attribute(Barcode.primary_key)
- assert_equal :string, column_type.type
- assert_equal 42, column_type.limit
+ column = Barcode.column_for_attribute(Barcode.primary_key)
+ assert_not column.null
+ assert_equal :string, column.type
+ assert_equal 42, column.limit
end
test "schema dump primary key includes type and options" do
@@ -262,6 +261,13 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end
+ def test_primary_key_issues_warning
+ warning = capture(:stderr) do
+ assert_nil @connection.primary_key("barcodes")
+ end
+ assert_match(/WARNING: Rails does not support composite primary key\./, warning)
+ end
+
def test_collectly_dump_composite_primary_key
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
@@ -345,9 +351,9 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
test "schema dump primary key with bigserial" do
schema = dump_table_schema "widgets"
if current_adapter?(:PostgreSQLAdapter)
- assert_match %r{create_table "widgets", id: :bigserial}, schema
+ assert_match %r{create_table "widgets", id: :bigserial, force: :cascade}, schema
else
- assert_match %r{create_table "widgets", id: :bigint}, schema
+ assert_match %r{create_table "widgets", id: :bigint, force: :cascade}, schema
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d84653e4c9..d5c01315c1 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -16,7 +16,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_clears_and_disables_cache_on_error
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -31,7 +31,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_leaves_enabled_cache_alone
ActiveRecord::Base.connection.enable_query_cache!
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
raise "lol borked"
}
assert_raises(RuntimeError) { mw.call({}) }
@@ -42,7 +42,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_exceptional_middleware_assigns_original_connection_id_on_error
connection_id = ActiveRecord::Base.connection_id
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
ActiveRecord::Base.connection_id = self.object_id
raise "lol borked"
}
@@ -53,7 +53,7 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_middleware_delegates
called = false
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
called = true
[200, {}, nil]
}
@@ -62,7 +62,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_middleware_caches
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
@@ -74,50 +74,13 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_enabled_during_call
assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
- mw = ActiveRecord::QueryCache.new lambda { |env|
+ mw = middleware { |env|
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
[200, {}, nil]
}
mw.call({})
end
- def test_cache_on_during_body_write
- streaming = Class.new do
- def each
- yield ActiveRecord::Base.connection.query_cache_enabled
- end
- end
-
- mw = ActiveRecord::QueryCache.new lambda { |env|
- [200, {}, streaming.new]
- }
- body = mw.call({}).last
- body.each { |x| assert x, 'cache should be on' }
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_off_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
- body = mw.call({}).last
-
- assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
- body.close
- assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled'
- end
-
- def test_cache_clear_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env|
- Post.first
- [200, {}, nil]
- }
- body = mw.call({}).last
-
- assert !ActiveRecord::Base.connection.query_cache.empty?, 'cache not empty'
- body.close
- assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
- end
-
def test_cache_passing_a_relation
post = Post.first
Post.cache do
@@ -170,7 +133,6 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_is_flat
Task.cache do
- Topic.columns # don't count this query
assert_queries(1) { Topic.find(1); Topic.find(1); }
end
@@ -181,13 +143,12 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_does_not_wrap_string_results_in_arrays
Task.cache do
- # Oracle adapter returns count() as Fixnum or Float
+ # Oracle adapter returns count() as Integer or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
# Future versions of the sqlite3 adapter will return numeric
- assert_instance_of Fixnum,
- Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ assert_instance_of Fixnum, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
@@ -213,6 +174,22 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.configurations = conf
end
+ def test_cache_is_not_available_when_using_a_not_connected_connection
+ spec_name = Task.connection_specification_name
+ conf = ActiveRecord::Base.configurations['arunit'].merge('name' => 'test2')
+ ActiveRecord::Base.connection_handler.establish_connection(conf)
+ Task.connection_specification_name = "test2"
+ refute Task.connected?
+
+ Task.cache do
+ Task.connection # warmup postgresql connection setup queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ end
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
+ end
+
def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
ActiveRecord::Base.connection.enable_query_cache!
post = Post.first
@@ -244,6 +221,13 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_equal 0, Post.where(title: 'rollback').to_a.count
end
end
+
+ private
+ def middleware(&app)
+ executor = Class.new(ActiveSupport::Executor)
+ ActiveRecord::QueryCache.install_executor_hooks executor
+ lambda { |env| executor.wrap { app.call(env) } }
+ end
end
class QueryCacheExpiryTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 6d91f96bf6..c01c82f4f5 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -102,9 +102,9 @@ module ActiveRecord
assert_equal float.to_s, @quoter.quote(float, nil)
end
- def test_quote_fixnum
- fixnum = 1
- assert_equal fixnum.to_s, @quoter.quote(fixnum, nil)
+ def test_quote_integer
+ integer = 1
+ assert_equal integer.to_s, @quoter.quote(integer, nil)
end
def test_quote_bignum
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index d0f60a84b5..ffb2da7a26 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -26,6 +26,10 @@ module ActiveRecord
def sanitize_sql_for_order(sql)
sql
end
+
+ def arel_attribute(name, table)
+ table[name]
+ end
end
def relation
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 28a0862f91..ce8c5ca489 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -82,5 +82,11 @@ module ActiveRecord
assert_equal p.loaded?, true
assert_equal expected, p.or(Post.where('id = 2')).to_a
end
+
+ def test_or_with_non_relation_object_raises_error
+ assert_raises ArgumentError do
+ Post.where(id: [1, 2, 3]).or(title: 'Rails')
+ end
+ end
end
end
diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb
index 62f0a7cc49..0e0e23b24b 100644
--- a/activerecord/test/cases/relation/record_fetch_warning_test.rb
+++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb
@@ -1,28 +1,40 @@
require 'cases/helper'
require 'models/post'
+require 'active_record/relation/record_fetch_warning'
module ActiveRecord
class RecordFetchWarningTest < ActiveRecord::TestCase
fixtures :posts
- def test_warn_on_records_fetched_greater_than
- original_logger = ActiveRecord::Base.logger
- orginal_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than
+ def setup
+ @original_logger = ActiveRecord::Base.logger
+ @original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than
+ @log = StringIO.new
+ end
+
+ def teardown
+ ActiveRecord::Base.logger = @original_logger
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = @original_warn_on_records_fetched_greater_than
+ end
- log = StringIO.new
- ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
+ def test_warn_on_records_fetched_greater_than_allowed_limit
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = 1
- require 'active_record/relation/record_fetch_warning'
+ Post.all.to_a
- ActiveRecord::Base.warn_on_records_fetched_greater_than = 1
+ assert_match(/Query fetched/, @log.string)
+ end
+
+ def test_does_not_warn_on_records_fetched_less_than_allowed_limit
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.warn_on_records_fetched_greater_than = 100
Post.all.to_a
- assert_match(/Query fetched/, log.string)
- ensure
- ActiveRecord::Base.logger = original_logger
- ActiveRecord::Base.warn_on_records_fetched_greater_than = orginal_warn_on_records_fetched_greater_than
+ assert_no_match(/Query fetched/, @log.string)
end
end
end
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index bc6378b90e..56a2b5b8c6 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -2,6 +2,7 @@ require "cases/helper"
require "models/author"
require "models/binary"
require "models/cake_designer"
+require "models/car"
require "models/chef"
require "models/comment"
require "models/edge"
@@ -14,7 +15,7 @@ require "models/vertex"
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
- fixtures :posts, :edges, :authors, :binaries, :essays
+ fixtures :posts, :edges, :authors, :binaries, :essays, :cars, :treasures, :price_estimates
def test_where_copies_bind_params
author = authors(:david)
@@ -114,6 +115,17 @@ module ActiveRecord
assert_equal expected.to_sql, actual.to_sql
end
+ def test_polymorphic_array_where_multiple_types
+ treasure_1 = treasures(:diamond)
+ treasure_2 = treasures(:sapphire)
+ car = cars(:honda)
+
+ expected = [price_estimates(:diamond), price_estimates(:sapphire_1), price_estimates(:sapphire_2), price_estimates(:honda)].sort
+ actual = PriceEstimate.where(estimate_of: [treasure_1, treasure_2, car]).to_a.sort
+
+ assert_equal expected, actual
+ end
+
def test_polymorphic_nested_relation_where
expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: Treasure.where(id: [1,2]))
actual = PriceEstimate.where(estimate_of: Treasure.where(id: [1,2]))
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 0638edacbd..5604124bb3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -19,6 +19,7 @@ require 'models/aircraft'
require "models/possession"
require "models/reader"
require "models/categorization"
+require "models/edge"
class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
@@ -223,6 +224,39 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fifth).title, topics.first.title
end
+ def test_reverse_order_with_function
+ topics = Topic.order("length(title)").reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ end
+
+ def test_reverse_order_with_function_other_predicates
+ topics = Topic.order("author_name, length(title), id").reverse_order
+ assert_equal topics(:second).title, topics.first.title
+ topics = Topic.order("length(author_name), id, length(title)").reverse_order
+ assert_equal topics(:fifth).title, topics.first.title
+ end
+
+ def test_reverse_order_with_multiargument_function
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(author_name, title)").reverse_order
+ end
+ end
+
+ def test_reverse_order_with_nulls_first_or_last
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("title NULLS FIRST").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("title nulls last").reverse_order
+ end
+ end
+
+ def test_default_reverse_order_on_table_without_primary_key
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Edge.all.reverse_order
+ end
+ end
+
def test_order_with_hash_and_symbol_generates_the_same_sql
assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
end
@@ -324,6 +358,12 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_sanitized_order
query = Tag.order(["field(id, ?)", [1,3,2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
+
+ query = Tag.order(["field(id, ?)", []]).to_sql
+ assert_match(/field\(id, NULL\)/, query)
+
+ query = Tag.order(["field(id, ?)", nil]).to_sql
+ assert_match(/field\(id, NULL\)/, query)
end
def test_finding_with_order_limit_and_offset
@@ -1046,6 +1086,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_with_block
+ posts = Post.all
+ assert_equal 10, posts.count { |p| p.comments_count.even? }
+ end
+
def test_count_on_association_relation
author = Author.last
another_author = Author.first
@@ -1239,6 +1284,16 @@ class RelationTest < ActiveRecord::TestCase
assert posts.loaded?
end
+ def test_to_a_should_dup_target
+ posts = Post.all
+
+ original_size = posts.size
+ removed = posts.to_a.pop
+
+ assert_equal original_size, posts.size
+ assert_includes posts.to_a, removed
+ end
+
def test_build
posts = Post.all
@@ -1939,4 +1994,22 @@ class RelationTest < ActiveRecord::TestCase
def test_relation_join_method
assert_equal 'Thank you for the welcome,Thank you again for the welcome', Post.first.comments.join(",")
end
+
+ def test_connection_adapters_can_reorder_binds
+ posts = Post.limit(1).offset(2)
+
+ stubbed_connection = Post.connection.dup
+ def stubbed_connection.combine_bind_parameters(**kwargs)
+ offset = kwargs[:offset]
+ kwargs[:offset] = kwargs[:limit]
+ kwargs[:limit] = offset
+ super(**kwargs)
+ end
+
+ posts.define_singleton_method(:connection) do
+ stubbed_connection
+ end
+
+ assert_equal 2, posts.to_a.length
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 7b93d20e05..12f4196724 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -29,8 +29,22 @@ class SchemaDumperTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.delete_all
end
- def test_magic_comment
- assert_match "# encoding: #{Encoding.default_external.name}", standard_dump
+ if current_adapter?(:SQLite3Adapter)
+ %w{3.7.8 3.7.11 3.7.12}.each do |version_string|
+ test "dumps schema version for sqlite version #{version_string}" do
+ version = ActiveRecord::ConnectionAdapters::SQLite3Adapter::Version.new(version_string)
+ ActiveRecord::Base.connection.stubs(:sqlite_version).returns(version)
+
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
+ versions.reverse_each do |v|
+ ActiveRecord::SchemaMigration.create!(:version => v)
+ end
+
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_match(/20100201010101.*20100301010101/m, schema_info)
+ ActiveRecord::SchemaMigration.delete_all
+ end
+ end
end
def test_schema_dump
@@ -38,7 +52,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dump_uses_force_cascade_on_create_table
@@ -74,7 +88,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
next if column_set.empty?
lengths = column_set.map do |column|
- if match = column.match(/\bt\.\w+\s+"/)
+ if match = column.match(/\bt\.\w+\s+(?="\w+?")/)
match[0].length
end
end.compact
@@ -147,10 +161,10 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_7.*limit: 7}, output
assert_match %r{c_int_8.*limit: 8}, output
else
- assert_match %r{c_int_5.*limit: 8}, output
- assert_match %r{c_int_6.*limit: 8}, output
- assert_match %r{c_int_7.*limit: 8}, output
- assert_match %r{c_int_8.*limit: 8}, output
+ assert_match %r{t\.bigint\s+"c_int_5"$}, output
+ assert_match %r{t\.bigint\s+"c_int_6"$}, output
+ assert_match %r{t\.bigint\s+"c_int_7"$}, output
+ assert_match %r{t\.bigint\s+"c_int_8"$}, output
end
end
@@ -159,7 +173,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dump_with_regexp_ignored_table
@@ -167,7 +181,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "accounts"}, output
assert_match %r{create_table "authors"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
end
def test_schema_dumps_index_columns_in_right_order
@@ -204,12 +218,17 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output
end
- if current_adapter?(:Mysql2Adapter)
- def test_schema_dump_should_add_default_value_for_mysql_text_field
- output = standard_dump
- assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output
- end
+ def test_schema_dump_does_not_include_limit_for_text_field
+ output = standard_dump
+ assert_match %r{t\.text\s+"params"$}, output
+ end
+
+ def test_schema_dump_does_not_include_limit_for_binary_field
+ output = standard_dump
+ assert_match %r{t\.binary\s+"data"$}, output
+ end
+ if current_adapter?(:Mysql2Adapter)
def test_schema_dump_includes_length_for_mysql_binary_fields
output = standard_dump
assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output
@@ -219,11 +238,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
output = standard_dump
assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output
- assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output
+ assert_match %r{t\.binary\s+"normal_blob"$}, output
assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output
assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output
assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output
- assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output
+ assert_match %r{t\.text\s+"normal_text"$}, output
assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output
end
@@ -248,12 +267,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter)
def test_schema_dump_includes_bigint_default
output = standard_dump
- assert_match %r{t\.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ assert_match %r{t\.bigint\s+"bigint_default",\s+default: 0}, output
end
def test_schema_dump_includes_limit_on_array_type
output = standard_dump
- assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output
+ assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output
end
def test_schema_dump_allows_array_of_decimal_defaults
@@ -261,6 +280,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
+ def test_schema_dump_expression_indices
+ index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition
+ end
+
if ActiveRecord::Base.connection.supports_extensions?
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
@@ -323,9 +347,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
create_table("dogs") do |t|
t.column :name, :string
t.column :owner_id, :integer
+ t.index [:name]
+ t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys?
end
- add_index "dogs", [:name]
- add_foreign_key :dogs, :dog_owners, column: "owner_id" if supports_foreign_keys?
end
def down
drop_table("dogs")
@@ -345,7 +369,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_no_match %r{create_table "foo_.+_bar"}, output
assert_no_match %r{add_index "foo_.+_bar"}, output
assert_no_match %r{create_table "schema_migrations"}, output
- assert_no_match %r{create_table "active_record_internal_metadatas"}, output
+ assert_no_match %r{create_table "ar_internal_metadata"}, output
if ActiveRecord::Base.connection.supports_foreign_keys?
assert_no_match %r{add_foreign_key "foo_.+_bar"}, output
diff --git a/activerecord/test/cases/schema_loading_test.rb b/activerecord/test/cases/schema_loading_test.rb
new file mode 100644
index 0000000000..3d92a5e104
--- /dev/null
+++ b/activerecord/test/cases/schema_loading_test.rb
@@ -0,0 +1,52 @@
+require "cases/helper"
+
+module SchemaLoadCounter
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ attr_accessor :load_schema_calls
+
+ def load_schema!
+ self.load_schema_calls ||= 0
+ self.load_schema_calls +=1
+ super
+ end
+ end
+end
+
+class SchemaLoadingTest < ActiveRecord::TestCase
+ def test_basic_model_is_loaded_once
+ klass = define_model
+ klass.new
+ assert_equal 1, klass.load_schema_calls
+ end
+
+ def test_model_with_custom_lock_is_loaded_once
+ klass = define_model do |c|
+ c.table_name = :lock_without_defaults_cust
+ c.locking_column = :custom_lock_version
+ end
+ klass.new
+ assert_equal 1, klass.load_schema_calls
+ end
+
+ def test_model_with_changed_custom_lock_is_loaded_twice
+ klass = define_model do |c|
+ c.table_name = :lock_without_defaults_cust
+ end
+ klass.new
+ klass.locking_column = :custom_lock_version
+ klass.new
+ assert_equal 2, klass.load_schema_calls
+ end
+
+ private
+
+ def define_model
+ Class.new(ActiveRecord::Base) do
+ include SchemaLoadCounter
+ self.table_name = :lock_without_defaults
+ yield self if block_given?
+ end
+ end
+end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index ad5ca70f36..dcd09b6973 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -4,6 +4,7 @@ require 'models/comment'
require 'models/developer'
require 'models/computer'
require 'models/vehicle'
+require 'models/cat'
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
@@ -374,6 +375,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
+ def test_default_scope_with_joins
+ assert_equal Comment.where(post_id: SpecialPostWithDefaultScope.pluck(:id)).count,
+ Comment.joins(:special_post_with_default_scope).count
+ assert_equal Comment.where(post_id: Post.pluck(:id)).count,
+ Comment.joins(:post).count
+ end
+
+ def test_unscoped_with_joins_should_not_have_default_scope
+ assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a },
+ Comment.joins(:post).to_a
+ end
+
def test_default_scope_select_ignored_by_aggregations
assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count
end
@@ -473,4 +486,15 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 1, SubConditionalStiPost.all.to_a.size
assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size
end
+
+ def test_with_abstract_class_scope_should_be_executed_in_correct_context
+ vegetarian_pattern, gender_pattern = if current_adapter?(:Mysql2Adapter)
+ [/`lions`.`is_vegetarian`/, /`lions`.`gender`/]
+ else
+ [/"lions"."is_vegetarian"/, /"lions"."gender"/]
+ end
+
+ assert_match vegetarian_pattern, Lion.all.to_sql
+ assert_match gender_pattern, Lion.female.to_sql
+ end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 7a8eaeccb7..0e277ed235 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -301,7 +301,7 @@ class NamedScopingTest < ActiveRecord::TestCase
:relation, # private class method on AR::Base
:new, # redefined class method on AR::Base
:all, # a default scope
- :public, # some imporant methods on Module and Class
+ :public, # some important methods on Module and Class
:protected,
:private,
:name,
@@ -440,6 +440,25 @@ class NamedScopingTest < ActiveRecord::TestCase
end
end
+ def test_scopes_with_reserved_names
+ class << Topic
+ def public_method; end
+ public :public_method
+
+ def protected_method; end
+ protected :protected_method
+
+ def private_method; end
+ private :private_method
+ end
+
+ [:public_method, :protected_method, :private_method].each do |reserved_method|
+ assert Topic.respond_to?(reserved_method, true)
+ ActiveRecord::Base.logger.expects(:warn)
+ silence_warnings { Topic.scope(reserved_method, -> { }) }
+ end
+ end
+
def test_scopes_on_relations
# Topic.replied
approved_topics = Topic.all.approved.order('id DESC')
@@ -525,4 +544,18 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
end
+ def test_model_class_should_respond_to_none
+ assert !Topic.none?
+ Topic.delete_all
+ assert Topic.none?
+ end
+
+ def test_model_class_should_respond_to_one
+ assert !Topic.one?
+ Topic.delete_all
+ assert !Topic.one?
+ Topic.create!
+ assert Topic.one?
+ end
+
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 6056156698..846be857d0 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -295,4 +295,37 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic.update_attribute :content, nil
assert_equal [topic], Topic.where(content: nil)
end
+
+ def test_mutation_detection_does_not_double_serialize
+ coder = Object.new
+ def coder.dump(value)
+ return if value.nil?
+ value + " encoded"
+ end
+ def coder.load(value)
+ return if value.nil?
+ value.gsub(" encoded", "")
+ end
+ type = Class.new(ActiveModel::Type::Value) do
+ include ActiveModel::Type::Helpers::Mutable
+
+ def serialize(value)
+ return if value.nil?
+ value + " serialized"
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ value.gsub(" serialized", "")
+ end
+ end.new
+ model = Class.new(Topic) do
+ attribute :foo, type
+ serialize :foo, coder
+ end
+
+ topic = model.create!(foo: "bar")
+ topic.foo
+ refute topic.changed?
+ end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index a704b861cb..104226010a 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -94,5 +94,17 @@ module ActiveRecord
additional_books = cache.execute([], Book, Book.connection)
assert first_books != additional_books
end
+
+ def test_unprepared_statements_dont_share_a_cache_with_prepared_statements
+ Book.create(name: "my book")
+ Book.create(name: "my other book")
+
+ book = Book.find_by(name: "my book")
+ other_book = Book.connection.unprepared_statement do
+ Book.find_by(name: "my other book")
+ end
+
+ refute_equal book, other_book
+ end
end
end
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index e9cdf94c99..bce86875e1 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -104,7 +104,7 @@ class StoreTest < ActiveRecord::TestCase
assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess)
end
- test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do
+ test "convert store attributes from any format other than Hash or HashWithIndifferentAccess losing the data" do
@john.json_data = "somedata"
@john.height = 'low'
assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess)
@@ -177,6 +177,7 @@ class StoreTest < ActiveRecord::TestCase
assert_equal [:color], first_model.stored_attributes[:data]
assert_equal [:color, :width, :height], second_model.stored_attributes[:data]
assert_equal [:color, :area, :volume], third_model.stored_attributes[:data]
+ assert_equal [:color], first_model.stored_attributes[:data]
end
test "YAML coder initializes the store when a Nil value is given" do
diff --git a/activerecord/test/cases/suppressor_test.rb b/activerecord/test/cases/suppressor_test.rb
index 72c5c16555..2f00241de2 100644
--- a/activerecord/test/cases/suppressor_test.rb
+++ b/activerecord/test/cases/suppressor_test.rb
@@ -46,7 +46,30 @@ class SuppressorTest < ActiveRecord::TestCase
Notification.suppress { UserWithNotification.create! }
assert_difference -> { Notification.count } do
- Notification.create!
+ Notification.create!(message: "New Comment")
+ end
+ end
+
+ def test_suppresses_validations_on_create
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ User.create
+ User.create!
+ User.new.save
+ User.new.save!
+ end
+ end
+ end
+
+ def test_suppresses_when_nested_multiple_times
+ assert_no_difference -> { Notification.count } do
+ Notification.suppress do
+ Notification.suppress { }
+ Notification.create
+ Notification.create!
+ Notification.new.save
+ Notification.new.save!
+ end
end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 49df6628eb..510bb088c8 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -8,6 +8,13 @@ module ActiveRecord
ActiveRecord::Tasks::MySQLDatabaseTasks.stubs(:new).returns @mysql_tasks
ActiveRecord::Tasks::PostgreSQLDatabaseTasks.stubs(:new).returns @postgresql_tasks
ActiveRecord::Tasks::SQLiteDatabaseTasks.stubs(:new).returns @sqlite_tasks
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
end
@@ -161,21 +168,25 @@ module ActiveRecord
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'test-db')
- ENV.expects(:[]).with('RAILS_ENV').returns(nil)
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
)
end
- def test_creates_only_development_database_when_rails_env_is_development
+ def test_creates_test_and_development_databases_when_rails_env_is_development
+ old_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = 'development'
ActiveRecord::Tasks::DatabaseTasks.expects(:create).
with('database' => 'dev-db')
- ENV.expects(:[]).with('RAILS_ENV').returns('development')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:create).
+ with('database' => 'test-db')
ActiveRecord::Tasks::DatabaseTasks.create_current(
ActiveSupport::StringInquirer.new('development')
)
+ ensure
+ ENV['RAILS_ENV'] = old_env
end
def test_establishes_connection_for_the_given_environment
@@ -282,38 +293,55 @@ module ActiveRecord
with('database' => 'dev-db')
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'test-db')
- ENV.expects(:[]).with('RAILS_ENV').returns(nil)
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new('development')
)
end
- def test_drops_only_development_database_when_rails_env_is_development
+ def test_drops_testand_development_databases_when_rails_env_is_development
+ old_env = ENV['RAILS_ENV']
+ ENV['RAILS_ENV'] = 'development'
ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
with('database' => 'dev-db')
- ENV.expects(:[]).with('RAILS_ENV').returns('development')
+ ActiveRecord::Tasks::DatabaseTasks.expects(:drop).
+ with('database' => 'test-db')
ActiveRecord::Tasks::DatabaseTasks.drop_current(
ActiveSupport::StringInquirer.new('development')
)
+ ensure
+ ENV['RAILS_ENV'] = old_env
end
end
class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path'
+ end
+
+ def teardown
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
+ end
+
def test_migrate_receives_correct_env_vars
verbose, version = ENV['VERBOSE'], ENV['VERSION']
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = 'custom/path'
ENV['VERBOSE'] = 'false'
ENV['VERSION'] = '4'
ActiveRecord::Migrator.expects(:migrate).with('custom/path', 4)
ActiveRecord::Tasks::DatabaseTasks.migrate
ensure
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
ENV['VERBOSE'], ENV['VERSION'] = verbose, version
end
+
+ def test_migrate_clears_schema_cache_afterward
+ ActiveRecord::Base.expects(:clear_cache!)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
end
class DatabaseTasksPurgeTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 7c1da51990..70e406038f 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
if current_adapter?(:Mysql2Adapter)
module ActiveRecord
@@ -12,6 +13,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_without_database
@@ -48,14 +56,20 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new("Can't create database 'dev'; database exists:")
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -77,6 +91,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:establish_connection).
raises(@error).
then.returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_root_password_is_requested
@@ -160,6 +181,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_mysql_database
@@ -173,6 +201,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class MySQLPurgeTest < ActiveRecord::TestCase
@@ -307,6 +341,5 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
end
-
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index ba53f340ae..99d73e91a4 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
if current_adapter?(:PostgreSQLAdapter)
module ActiveRecord
@@ -12,6 +13,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -63,14 +71,20 @@ module ActiveRecord
assert_raises(Exception) { ActiveRecord::Tasks::DatabaseTasks.create @configuration }
end
- def test_create_when_database_exists_outputs_info_to_stderr
- $stderr.expects(:puts).with("my-app-db already exists").once
+ def test_when_database_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ assert_equal $stdout.string, "Created database 'my-app-db'\n"
+ end
+
+ def test_create_when_database_exists_outputs_info_to_stderr
ActiveRecord::Base.connection.stubs(:create_database).raises(
- ActiveRecord::StatementInvalid.new('database "my-app-db" already exists')
+ ActiveRecord::Tasks::DatabaseAlreadyExists
)
ActiveRecord::Tasks::DatabaseTasks.create @configuration
+
+ assert_equal $stderr.string, "Database 'my-app-db' already exists\n"
end
end
@@ -84,6 +98,13 @@ module ActiveRecord
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_establishes_connection_to_postgresql_database
@@ -101,6 +122,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration
end
+
+ def test_when_database_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+
+ assert_equal $stdout.string, "Dropped database 'my-app-db'\n"
+ end
end
class PostgreSQLPurgeTest < ActiveRecord::TestCase
@@ -261,18 +288,17 @@ module ActiveRecord
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
def test_structure_load_accepts_path_with_spaces
filename = "awesome file.sql"
- Kernel.expects(:system).with('psql', '-q', '-f', filename, @configuration['database']).returns(true)
+ Kernel.expects(:system).with('psql', '-v', 'ON_ERROR_STOP=1', '-q', '-f', filename, @configuration['database']).returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
end
-
end
end
diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb
index 0aea0c3b38..4be03c7f61 100644
--- a/activerecord/test/cases/tasks/sqlite_rake_test.rb
+++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb
@@ -1,4 +1,5 @@
require 'cases/helper'
+require 'active_record/tasks/database_tasks'
require 'pathname'
if current_adapter?(:SQLite3Adapter)
@@ -15,6 +16,13 @@ module ActiveRecord
File.stubs(:exist?).returns(false)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_db_checks_database_exists
@@ -23,12 +31,18 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
end
+ def test_when_db_created_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Created database '#{@database}'\n"
+ end
+
def test_db_create_when_file_exists
File.stubs(:exist?).returns(true)
- $stderr.expects(:puts).with("#{@database} already exists")
-
ActiveRecord::Tasks::DatabaseTasks.create @configuration, '/rails/root'
+
+ assert_equal $stderr.string, "Database '#{@database}' already exists\n"
end
def test_db_create_with_file_does_nothing
@@ -69,6 +83,13 @@ module ActiveRecord
Pathname.stubs(:new).returns(@path)
File.stubs(:join).returns('/former/relative/path')
FileUtils.stubs(:rm).returns(true)
+
+ $stdout, @original_stdout = StringIO.new, $stdout
+ $stderr, @original_stderr = StringIO.new, $stderr
+ end
+
+ def teardown
+ $stdout, $stderr = @original_stdout, @original_stderr
end
def test_creates_path_from_database
@@ -103,6 +124,12 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
end
+
+ def test_when_db_dropped_successfully_outputs_info_to_stdout
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration, '/rails/root'
+
+ assert_equal $stdout.string, "Dropped database '#{@database}'\n"
+ end
end
class SqliteDBCharsetTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 87299c0dab..c8adc21bbc 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -12,10 +12,6 @@ module ActiveRecord
SQLCounter.clear_log
end
- def assert_date_from_db(expected, actual, message = nil)
- assert_equal expected.to_s, actual.to_s, message
- end
-
def capture_sql
SQLCounter.clear_log
yield
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 3b6e4dcc2b..628a8eb771 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'support/schema_dumping_helper'
-if ActiveRecord::Base.connection.supports_datetime_with_precision?
+if subsecond_precision_supported?
class TimePrecisionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 970f6bcf4a..937b84bccc 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -98,8 +98,11 @@ class TimestampTest < ActiveRecord::TestCase
task = Task.first
previous_value = task.ending
task.touch(:ending)
+
+ now = Time.now.change(usec: 0)
+
assert_not_equal previous_value, task.ending
- assert_in_delta Time.now, task.ending, 1
+ assert_in_delta now, task.ending, 1
end
def test_touching_an_attribute_updates_timestamp_with_given_time
@@ -120,10 +123,12 @@ class TimestampTest < ActiveRecord::TestCase
previous_ending = task.ending
task.touch(:starting, :ending)
+ now = Time.now.change(usec: 0)
+
assert_not_equal previous_starting, task.starting
assert_not_equal previous_ending, task.ending
- assert_in_delta Time.now, task.starting, 1
- assert_in_delta Time.now, task.ending, 1
+ assert_in_delta now, task.starting, 1
+ assert_in_delta now, task.ending, 1
end
def test_touching_a_record_without_timestamps_is_unexceptional
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 637f89196e..8a7f19293d 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -34,6 +34,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id"
+ before_commit { |record| record.do_before_commit(nil) }
after_commit { |record| record.do_after_commit(nil) }
after_create_commit { |record| record.do_after_commit(:create) }
after_update_commit { |record| record.do_after_commit(:update) }
@@ -47,6 +48,12 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@history ||= []
end
+ def before_commit_block(on = nil, &block)
+ @before_commit ||= {}
+ @before_commit[on] ||= []
+ @before_commit[on] << block
+ end
+
def after_commit_block(on = nil, &block)
@after_commit ||= {}
@after_commit[on] ||= []
@@ -59,6 +66,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@after_rollback[on] << block
end
+ def do_before_commit(on)
+ blocks = @before_commit[on] if defined?(@before_commit)
+ blocks.each{|b| b.call(self)} if blocks
+ end
+
def do_after_commit(on)
blocks = @after_commit[on] if defined?(@after_commit)
blocks.each{|b| b.call(self)} if blocks
@@ -74,6 +86,20 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
@first = TopicWithCallbacks.find(1)
end
+ # FIXME: Test behavior, not implementation.
+ def test_before_commit_exception_should_pop_transaction_stack
+ @first.before_commit_block { raise 'better pop this txn from the stack!' }
+
+ original_txn = @first.class.connection.current_transaction
+
+ begin
+ @first.save!
+ fail
+ rescue
+ assert_equal original_txn, @first.class.connection.current_transaction
+ end
+ end
+
def test_call_after_commit_after_transaction_commits
@first.after_commit_block{|r| r.history << :after_commit}
@first.after_rollback_block{|r| r.history << :after_rollback}
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index dd43ee358c..c0b3750bcc 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -57,19 +57,17 @@ class AbsenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { boy_klass.new(face: face_with_to_a).valid? }
end
- def test_does_not_validate_if_parent_record_is_validate_false
+ def test_validates_absence_of_virtual_attribute_on_model
repair_validations(Interest) do
- Interest.validates_absence_of(:topic)
- interest = Interest.new(topic: Topic.new(title: "Math"))
- interest.save!(validate: false)
- assert interest.persisted?
+ Interest.send(:attr_accessor, :token)
+ Interest.validates_absence_of(:token)
- man = Man.new(interest_ids: [interest.id])
- man.save!
-
- assert_equal man.interests.size, 1
+ interest = Interest.create!(topic: 'Thought Leadering')
assert interest.valid?
- assert man.valid?
+
+ interest.token = 'tl'
+
+ assert interest.invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 981239c4d6..5b5307489a 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -41,14 +41,10 @@ class I18nValidationTest < ActiveRecord::TestCase
[ "given custom message", {:message => "custom"}, {:message => "custom"}],
[ "given if condition", {:if => lambda { true }}, {}],
[ "given unless condition", {:unless => lambda { false }}, {}],
- [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
- # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason
- # even when using .save instead .valid?
- # [ "given on condition", {on: :save}, {}]
+ [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }],
+ [ "given on condition", {on: [:create, :update] }, {}]
]
- # validates_uniqueness_of w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_uniqueness_of on generated message #{name}" do
Topic.validates_uniqueness_of :title, validation_options
@@ -59,8 +55,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # validates_associated w/ mocha
-
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_associated on generated message #{name}" do
Topic.validates_associated :replies, validation_options
@@ -70,8 +64,6 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # validates_associated w/o mocha
-
def test_validates_associated_finds_custom_model_key_translation
I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}}
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index c5d8f8895c..78263fd955 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -61,17 +61,19 @@ class LengthValidationTest < ActiveRecord::TestCase
assert_equal pet_count, Pet.count
end
- def test_does_not_validate_length_of_if_parent_record_is_validate_false
- @owner.validates_length_of :name, minimum: 1
- owner = @owner.new
- owner.save!(validate: false)
- assert owner.persisted?
+ def test_validates_length_of_virtual_attribute_on_model
+ repair_validations(Pet) do
+ Pet.send(:attr_accessor, :nickname)
+ Pet.validates_length_of(:name, minimum: 1)
+ Pet.validates_length_of(:nickname, minimum: 1)
- pet = Pet.new(owner_id: owner.id)
- pet.save!
+ pet = Pet.create!(name: 'Fancy Pants', nickname: 'Fancy')
- assert_equal owner.pets.size, 1
- assert owner.valid?
- assert pet.valid?
+ assert pet.valid?
+
+ pet.nickname = ''
+
+ assert pet.invalid?
+ end
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 6f8ad06ab6..868d111b8c 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -65,19 +65,39 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { s.valid? }
end
- def test_does_not_validate_presence_of_if_parent_record_is_validate_false
+ def test_validates_presence_of_virtual_attribute_on_model
repair_validations(Interest) do
+ Interest.send(:attr_accessor, :abbreviation)
Interest.validates_presence_of(:topic)
+ Interest.validates_presence_of(:abbreviation)
+
+ interest = Interest.create!(topic: 'Thought Leadering', abbreviation: 'tl')
+ assert interest.valid?
+
+ interest.abbreviation = ''
+
+ assert interest.invalid?
+ end
+ end
+
+ def test_validations_run_on_persisted_record
+ repair_validations(Interest) do
interest = Interest.new
- interest.save!(validate: false)
- assert interest.persisted?
+ interest.save!
+ assert_predicate interest, :valid?
- man = Man.new(interest_ids: [interest.id])
- man.save!
+ Interest.validates_presence_of(:topic)
- assert_equal man.interests.size, 1
- assert interest.valid?
- assert man.valid?
+ assert_not_predicate interest, :valid?
+ end
+ end
+
+ def test_validates_presence_with_on_context
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:topic, on: :required_name)
+ interest = Interest.new
+ interest.save!
+ assert_not interest.valid?(:required_name)
end
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 7502a55391..4b0a590adb 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -5,6 +5,7 @@ require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
require 'models/dashboard'
+require 'models/uuid_item'
class Wizard < ActiveRecord::Base
self.abstract_class = true
@@ -48,6 +49,18 @@ class BigIntReverseTest < ActiveRecord::Base
validates :engines_count, uniqueness: true
end
+class CoolTopic < Topic
+ validates_uniqueness_of :id
+end
+
+class TopicWithAfterCreate < Topic
+ after_create :set_author
+
+ def set_author
+ update_attributes!(:author_name => "#{title} #{id}")
+ end
+end
+
class UniquenessValidationTest < ActiveRecord::TestCase
INT_MAX_VALUE = 2147483647
@@ -336,19 +349,41 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_limit
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "abcde")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "abcdefgh")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ if current_adapter?(:SQLite3Adapter)
+ # Event.title has limit 5, but SQLite doesn't truncate.
+ e1 = Event.create(title: "abcdefgh")
+ assert e1.valid?, "Could not create an event with a unique 8 characters title"
+
+ e2 = Event.create(title: "abcdefgh")
+ assert_not e2.valid?, "Created an event whose title is not unique"
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ assert_raise(ActiveRecord::ValueTooLong) do
+ Event.create(title: "abcdefgh")
+ end
+ else
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Event.create(title: "abcdefgh")
+ end
+ end
end
def test_validate_uniqueness_with_limit_and_utf8
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "一二三四五")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "一二三四五六七八")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
+ if current_adapter?(:SQLite3Adapter)
+ # Event.title has limit 5, but does SQLite doesn't truncate.
+ e1 = Event.create(title: "一二三四五六七八")
+ assert e1.valid?, "Could not create an event with a unique 8 characters title"
+
+ e2 = Event.create(title: "一二三四五六七八")
+ assert_not e2.valid?, "Created an event whose title is not unique"
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ assert_raise(ActiveRecord::ValueTooLong) do
+ Event.create(title: "一二三四五六七八")
+ end
+ else
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Event.create(title: "一二三四五六七八")
+ end
+ end
end
def test_validate_straight_inheritance_uniqueness
@@ -412,23 +447,6 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert topic.valid?
end
- def test_does_not_validate_uniqueness_of_if_parent_record_is_validate_false
- Reply.validates_uniqueness_of(:content)
-
- Reply.create!(content: "Topic Title")
-
- reply = Reply.new(content: "Topic Title")
- reply.save!(validate: false)
- assert reply.persisted?
-
- topic = Topic.new(reply_ids: [reply.id])
- topic.save!
-
- assert_equal topic.replies.size, 1
- assert reply.valid?
- assert topic.valid?
- end
-
def test_validate_uniqueness_of_custom_primary_key
klass = Class.new(ActiveRecord::Base) do
self.table_name = "keyboards"
@@ -469,4 +487,46 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert_match(/\AUnknown primary key for table dashboards in model/, e.message)
assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message)
end
+
+ def test_validate_uniqueness_ignores_itself_when_primary_key_changed
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => "This is a unique title")
+ assert t.save, "Should save t as unique"
+
+ t.id += 1
+ assert t.valid?, "Should be valid"
+ assert t.save, "Should still save t as unique"
+ end
+
+ def test_validate_uniqueness_with_after_create_performing_save
+ TopicWithAfterCreate.validates_uniqueness_of(:title)
+ topic = TopicWithAfterCreate.create!(:title => "Title1")
+ assert topic.author_name.start_with?("Title1")
+
+ topic2 = TopicWithAfterCreate.new(:title => "Title1")
+ refute topic2.valid?
+ assert_equal(["has already been taken"], topic2.errors[:title])
+ end
+
+ def test_validate_uniqueness_uuid
+ skip unless current_adapter?(:PostgreSQLAdapter)
+ item = UuidItem.create!(uuid: SecureRandom.uuid, title: 'item1')
+ item.update(title: 'item1-title2')
+ assert_empty item.errors
+
+ item2 = UuidValidatingItem.create!(uuid: SecureRandom.uuid, title: 'item2')
+ item2.update(title: 'item2-title2')
+ assert_empty item2.errors
+ end
+
+ def test_validate_uniqueness_regular_id
+ item = CoolTopic.create!(title: 'MyItem')
+ assert_empty item.errors
+
+ item2 = CoolTopic.new(id: item.id, title: 'MyItem2')
+ refute item2.valid?
+
+ assert_equal(["has already been taken"], item2.errors[:id])
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index d04f4f7ce7..85e33d2218 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -130,7 +130,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_validates_acceptance_of_with_non_existent_table
Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
- assert_nothing_raised ActiveRecord::StatementInvalid do
+ assert_nothing_raised do
IncorporealModel.validates_acceptance_of(:incorporeal_column)
end
end
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 56909a8630..d1c9a00786 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -109,6 +109,16 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal("Have a nice day", topic.content)
end
+ def test_yaml_encoding_keeps_mutations
+ author = Author.first
+ author.name = "Sean"
+ dumped = YAML.load(YAML.dump(author))
+
+ assert_equal "Sean", dumped.name
+ assert_equal author.name_was, dumped.name_was
+ assert_equal author.changes, dumped.changes
+ end
+
private
def yaml_fixture(file_name)
diff --git a/activerecord/test/fixtures/price_estimates.yml b/activerecord/test/fixtures/price_estimates.yml
index 1149ab17a2..406d65a142 100644
--- a/activerecord/test/fixtures/price_estimates.yml
+++ b/activerecord/test/fixtures/price_estimates.yml
@@ -1,7 +1,16 @@
-saphire_1:
+sapphire_1:
price: 10
estimate_of: sapphire (Treasure)
sapphire_2:
price: 20
estimate_of: sapphire (Treasure)
+
+diamond:
+ price: 30
+ estimate_of: diamond (Treasure)
+
+honda:
+ price: 40
+ estimate_of_type: Car
+ estimate_of_id: 1
diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb
index 2e9f5ec6bc..d361847d4b 100644
--- a/activerecord/test/migrations/to_copy2/2_create_comments.rb
+++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb
@@ -1,4 +1,4 @@
-class CreateArticles < ActiveRecord::Migration::Current
+class CreateComments < ActiveRecord::Migration::Current
def self.up
end
diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
index 8f81805fe1..1a863367dd 100644
--- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :string
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
index 607113b091..76734bcd7d 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveHobbies < ActiveRecord::Migration::Current
def self.up
add_column "people", "hobbies", :text
end
diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
index d4cbddab50..7f883dbb45 100644
--- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
+++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration::Current
+class PeopleHaveDescriptions < ActiveRecord::Migration::Current
def self.up
add_column "people", "description", :text
end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 0d90cbb110..38b983eda0 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -75,7 +75,7 @@ class Author < ActiveRecord::Base
has_many :posts_with_multiple_callbacks, :class_name => "Post",
:before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}"}],
:after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}"}]
- has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding
+ has_many :unchangeable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding
has_many :categorizations
has_many :categories, :through => :categorizations
@@ -144,6 +144,14 @@ class Author < ActiveRecord::Base
has_many :posts_with_signature, ->(record) { where("posts.title LIKE ?", "%by #{record.name.downcase}%") }, class_name: "Post"
+ has_many :posts_with_extension, -> { order(:title) }, class_name: "Post" do
+ def extension_method; end
+ end
+
+ has_many :posts_with_extension_and_instance, ->(record) { order(:title) }, class_name: "Post" do
+ def extension_method; end
+ end
+
attr_accessor :post_log
after_initialize :set_post_log
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index 778c22b1f6..0f37e9a289 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -12,6 +12,8 @@ class Car < ActiveRecord::Base
has_many :engines, :dependent => :destroy, inverse_of: :my_car
has_many :wheels, :as => :wheelable, :dependent => :destroy
+ has_many :price_estimates, :as => :estimate_of
+
scope :incl_tyres, -> { includes(:tyres) }
scope :incl_engines, -> { includes(:engines) }
diff --git a/activerecord/test/models/cat.rb b/activerecord/test/models/cat.rb
new file mode 100644
index 0000000000..dfdde18641
--- /dev/null
+++ b/activerecord/test/models/cat.rb
@@ -0,0 +1,10 @@
+class Cat < ActiveRecord::Base
+ self.abstract_class = true
+
+ enum gender: [:female, :male]
+
+ default_scope -> { where(is_vegetarian: false) }
+end
+
+class Lion < Cat
+end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index b38b17e90e..dcc5c5a310 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -14,6 +14,7 @@ class Comment < ActiveRecord::Base
has_many :ratings
belongs_to :first_post, :foreign_key => :post_id
+ belongs_to :special_post_with_default_scope, foreign_key: :post_id
has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index afe4b3d707..3338aaf7e1 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -64,7 +64,12 @@ class Fullname
def self.parse(str)
return nil unless str
- new(*str.to_s.split)
+
+ if str.is_a?(Hash)
+ new(str[:first], str[:last])
+ else
+ new(*str.to_s.split)
+ end
end
def initialize(first, last = nil)
diff --git a/activerecord/test/models/notification.rb b/activerecord/test/models/notification.rb
index b4b4b8f1b6..82edc64b68 100644
--- a/activerecord/test/models/notification.rb
+++ b/activerecord/test/models/notification.rb
@@ -1,2 +1,3 @@
class Notification < ActiveRecord::Base
+ validates_presence_of :message
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index cedb774b10..ce8242cf2f 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,7 +1,8 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
has_many :pets, -> { order 'pets.name desc' }
- has_many :toys, :through => :pets
+ has_many :toys, through: :pets
+ has_many :persons, through: :pets
belongs_to :last_pet, class_name: 'Pet'
scope :including_last_pet, -> {
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index f7970d7aab..53489fa1b3 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -4,6 +4,9 @@ class Pet < ActiveRecord::Base
self.primary_key = :pet_id
belongs_to :owner, :touch => true
has_many :toys
+ has_many :pet_treasures
+ has_many :treasures, through: :pet_treasures
+ has_many :persons, through: :treasures, source: :looter, source_type: 'Person'
class << self
attr_accessor :after_destroy_output
diff --git a/activerecord/test/models/pet_treasure.rb b/activerecord/test/models/pet_treasure.rb
new file mode 100644
index 0000000000..1fe7807ffe
--- /dev/null
+++ b/activerecord/test/models/pet_treasure.rb
@@ -0,0 +1,6 @@
+class PetTreasure < ActiveRecord::Base
+ self.table_name = "pets_treasures"
+
+ belongs_to :pet
+ belongs_to :treasure
+end
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index 80d4725f7e..b48b9a2155 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -5,3 +5,9 @@ class Tag < ActiveRecord::Base
has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post'
end
+
+class OrderedTag < Tag
+ self.table_name = "tags"
+
+ has_many :taggings, -> { order('taggings.id DESC') }, foreign_key: 'tag_id'
+end
diff --git a/activerecord/test/models/uuid_item.rb b/activerecord/test/models/uuid_item.rb
new file mode 100644
index 0000000000..2353e40213
--- /dev/null
+++ b/activerecord/test/models/uuid_item.rb
@@ -0,0 +1,6 @@
+class UuidItem < ActiveRecord::Base
+end
+
+class UuidValidatingItem < UuidItem
+ validates_uniqueness_of :uuid
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 701e6f45b3..5a49b38457 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -21,19 +21,19 @@ ActiveRecord::Schema.define do
t.index :var_binary
end
- create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ create_table :key_tests, force: true, options: 'ENGINE=MyISAM' do |t|
t.string :awesome
t.string :pizza
t.string :snacks
+ t.index :awesome, type: :fulltext, name: 'index_key_tests_on_awesome'
+ t.index :pizza, using: :btree, name: 'index_key_tests_on_pizza'
+ t.index :snacks, name: 'index_key_tests_on_snack'
end
- add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
- add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
- add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
-
create_table :collation_tests, id: false, force: true do |t|
t.string :string_cs_column, limit: 1, collation: 'utf8_bin'
t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci'
+ t.binary :binary_column, limit: 1
end
ActiveRecord::Base.connection.execute <<-SQL
@@ -62,7 +62,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('text','blob','tiny','medium','long','unsigned')
+ enum_column ENUM('text','blob','tiny','medium','long','unsigned','bigint')
)
SQL
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 3a5d73a0ed..24713f722a 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -106,4 +106,9 @@ _SQL
t.integer :big_int_data_points, limit: 8, array: true
t.decimal :decimal_array_default, array: true, default: [1.23, 3.45]
end
+
+ create_table :uuid_items, force: true, id: false do |t|
+ t.uuid :uuid, primary_key: true
+ t.string :title
+ end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index fcc4eee79c..628a59c2e3 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1,10 +1,4 @@
ActiveRecord::Schema.define do
- def except(adapter_names_to_exclude)
- unless [adapter_names_to_exclude].flatten.include?(adapter_name)
- yield
- end
- end
-
# ------------------------------------------------------------------- #
# #
# Please keep these create table statements in alphabetical order #
@@ -202,12 +196,12 @@ ActiveRecord::Schema.define do
t.integer :rating, default: 1
t.integer :account_id
t.string :description, default: ""
+ t.index [:firm_id, :type, :rating], name: "company_index"
+ t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
+ t.index :name, name: 'company_name_index', using: :btree
+ t.index 'lower(name)', name: "company_expression_index" if supports_expression_index?
end
- add_index :companies, [:firm_id, :type, :rating], name: "company_index"
- add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10"
- add_index :companies, :name, name: 'company_name_index', using: :btree
-
create_table :content, force: true do |t|
t.string :title
end
@@ -304,8 +298,8 @@ ActiveRecord::Schema.define do
create_table :edges, force: true, id: false do |t|
t.column :source_id, :integer, null: false
t.column :sink_id, :integer, null: false
+ t.index [:source_id, :sink_id], unique: true, name: 'unique_edge_index'
end
- add_index :edges, [:source_id, :sink_id], unique: true, name: 'unique_edge_index'
create_table :engines, force: true do |t|
t.integer :car_id
@@ -422,6 +416,11 @@ ActiveRecord::Schema.define do
t.integer :amount
end
+ create_table :lions, force: true do |t|
+ t.integer :gender
+ t.boolean :is_vegetarian, default: false
+ end
+
create_table :lock_without_defaults, force: true do |t|
t.column :lock_version, :integer
end
@@ -616,6 +615,12 @@ ActiveRecord::Schema.define do
end
end
+ create_table :pets_treasures, force: true do |t|
+ t.column :treasure_id, :integer
+ t.column :pet_id, :integer
+ t.column :rainbow_color, :string
+ end
+
create_table :pirates, force: true do |t|
t.column :catchphrase, :string
t.column :parrot_id, :integer
@@ -782,8 +787,8 @@ ActiveRecord::Schema.define do
t.string :nick, null: false
t.string :name
t.column :books_count, :integer, null: false, default: 0
+ t.index :nick, unique: true
end
- add_index :subscribers, :nick, unique: true
create_table :subscriptions, force: true do |t|
t.string :subscriber_id
@@ -930,7 +935,7 @@ ActiveRecord::Schema.define do
t.string :treaty_id
t.string :name
end
- create_table :countries_treaties, force: true, id: false do |t|
+ create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t|
t.string :country_id, null: false
t.string :treaty_id, null: false
end
@@ -987,7 +992,7 @@ ActiveRecord::Schema.define do
create_table :records, force: true do |t|
end
- except 'SQLite' do
+ if supports_foreign_keys?
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, force: true do |t|
t.integer :fk_id, null: false
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
index b5552c2755..cc7c36fe2b 100644
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -1,8 +1,4 @@
ActiveRecord::Schema.define do
- create_table :table_with_autoincrement, :force => true do |t|
- t.column :name, :string
- end
-
execute "DROP TABLE fk_test_has_fk" rescue nil
execute "DROP TABLE fk_test_has_pk" rescue nil
execute <<_SQL