aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md1866
-rw-r--r--activerecord/lib/active_record/associations.rb38
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb35
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb7
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb89
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb61
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb38
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb103
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb158
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb207
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb292
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_handling.rb18
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb10
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/gem_version.rb15
-rw-r--r--activerecord/lib/active_record/persistence.rb27
-rw-r--r--activerecord/lib/active_record/railtie.rb23
-rw-r--r--activerecord/lib/active_record/railties/databases.rake5
-rw-r--r--activerecord/lib/active_record/reflection.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb11
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb22
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb47
-rw-r--r--activerecord/lib/active_record/scoping/default.rb15
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/validations.rb4
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb10
-rw-r--r--activerecord/lib/active_record/version.rb11
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb10
-rw-r--r--activerecord/test/cases/adapter_test.rb20
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb87
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb108
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb19
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb27
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb80
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb50
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb53
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb78
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb33
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb56
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb116
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb35
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb145
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb32
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb367
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb22
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb23
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb44
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb46
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb9
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb63
-rw-r--r--activerecord/test/cases/autosave_association_test.rb39
-rw-r--r--activerecord/test/cases/base_test.rb15
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb14
-rw-r--r--activerecord/test/cases/column_definition_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb79
-rw-r--r--activerecord/test/cases/connection_pool_test.rb32
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb27
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/disconnected_test.rb2
-rw-r--r--activerecord/test/cases/enum_test.rb28
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb38
-rw-r--r--activerecord/test/cases/fixtures_test.rb6
-rw-r--r--activerecord/test/cases/helper.rb7
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/invalid_connection_test.rb2
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb25
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb3
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb3
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb3
-rw-r--r--activerecord/test/cases/migration/index_test.rb3
-rw-r--r--activerecord/test/cases/migration/logger_test.rb3
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb3
-rw-r--r--activerecord/test/cases/migration_test.rb4
-rw-r--r--activerecord/test/cases/migrator_test.rb3
-rw-r--r--activerecord/test/cases/modules_test.rb2
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb42
-rw-r--r--activerecord/test/cases/persistence_test.rb6
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb8
-rw-r--r--activerecord/test/cases/reaper_test.rb20
-rw-r--r--activerecord/test/cases/relation/predicate_builder_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_chain_test.rb30
-rw-r--r--activerecord/test/cases/relations_test.rb46
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb11
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb18
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb3
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb5
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb3
-rw-r--r--activerecord/test/cases/test_case.rb8
-rw-r--r--activerecord/test/cases/timestamp_test.rb30
-rw-r--r--activerecord/test/cases/transactions_test.rb6
-rw-r--r--activerecord/test/cases/unconnected_test.rb2
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/cases/validations_test.rb15
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb1
-rw-r--r--activerecord/test/fixtures/computers.yml1
-rw-r--r--activerecord/test/fixtures/pirates.yml3
-rw-r--r--activerecord/test/models/category.rb1
-rw-r--r--activerecord/test/models/college.rb5
-rw-r--r--activerecord/test/models/developer.rb2
-rw-r--r--activerecord/test/models/movie.rb2
-rw-r--r--activerecord/test/models/person.rb13
-rw-r--r--activerecord/test/models/pirate.rb8
-rw-r--r--activerecord/test/models/post.rb4
-rw-r--r--activerecord/test/models/ship.rb8
-rw-r--r--activerecord/test/models/student.rb1
-rw-r--r--activerecord/test/models/treasure.rb1
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb11
-rw-r--r--activerecord/test/schema/schema.rb9
-rw-r--r--activerecord/test/support/connection_helper.rb14
-rw-r--r--activerecord/test/support/ddl_helper.rb8
155 files changed, 2688 insertions, 3082 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 7efd75a239..fff66f21fb 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1858 +1,248 @@
-* Default scopes are no longer overriden by chained conditions.
+* `to_sql` on an association now matches the query that is actually executed, where it
+ could previously have incorrectly accrued additional conditions (e.g. as a result of
+ a previous query). CollectionProxy now always defers to the association scope's
+ `arel` method so the (incorrect) inherited one should be entirely concealed.
- Before this change when you defined a `default_scope` in a model
- it was overriden by chained conditions in the same field. Now it
- is merged like any other scope.
+ Fixes #14003.
- Before:
+ *Jefferson Lai*
- class User < ActiveRecord::Base
- default_scope { where state: 'pending' }
- scope :active, -> { where state: 'active' }
- scope :inactive, -> { where state: 'inactive' }
- end
-
- User.all
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
-
- User.active
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
-
- User.where(state: 'inactive')
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
-
- After:
-
- class User < ActiveRecord::Base
- default_scope { where state: 'pending' }
- scope :active, -> { where state: 'active' }
- scope :inactive, -> { where state: 'inactive' }
- end
-
- User.all
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
-
- User.active
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'
-
- User.where(state: 'inactive')
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'
-
- To get the previous behavior it is needed to explicitly remove the
- `default_scope` condition using `unscoped`, `unscope`, `rewhere` or
- `except`.
-
- Example:
-
- class User < ActiveRecord::Base
- default_scope { where state: 'pending' }
- scope :active, -> { unscope(where: :state).where(state: 'active') }
- scope :inactive, -> { rewhere state: 'inactive' }
- end
-
- User.all
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'
-
- User.active
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'active'
-
- User.inactive
- # SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
-
-* Perform necessary deeper encoding when hstore is inside an array.
-
- Fixes #11135.
-
- *Josh Goodall*, *Genadi Samokovarov*
-
-* Properly detect if a connection is still active before using it
- in multi-threaded environments.
+* Block a few default Class methods as scope name.
- Fixes #12867.
+ For instance, this will raise:
- *Kevin Casey*, *Matthew Draper*, *William (B.J.) Snow Orvis*
-
-* When inverting add_index use the index name if present instead of
- the columns.
-
- If there are two indices with matching columns and one of them is
- explicitly named then reverting the migration adding the named one
- would instead drop the unnamed one.
-
- The inversion of add_index will now drop the index by its name if
- it is present.
-
- *Hubert Dąbrowski*
-
-* Add flag to disable schema dump after migration.
-
- Add a config parameter on Active Record named `dump_schema_after_migration`
- which is true by default. Now schema dump does not happen at the
- end of migration rake task if `dump_schema_after_migration` is false.
-
- *Emil Soman*
-
-* `find_in_batches`, `find_each`, `Result#each` and `Enumerable#index_by` now
- return an `Enumerator` that can calculate its size.
-
- See also #13938.
-
- *Marc-André Lafortune*
-
-* Make sure transaction state gets reset after a commit operation on the record.
-
- If a new transaction was open inside a callback, the record was loosing track
- of the transaction level state, and it was leaking that state.
-
- Fixes #12566.
+ scope :public, -> { where(status: 1) }
*arthurnn*
-* Pass `has_and_belongs_to_many` `:autosave` option to
- the underlying `has_many :through` association.
-
- Fixes #13923.
-
- *Yves Senn*
-
-* PostgreSQL implementation of `SchemaStatements#index_name_exists?`.
-
- The database agnostic implementation does not detect with indexes that are
- not supported by the ActiveRecord schema dumper. For example, expressions
- indexes would not be detected.
-
- Fixes #11018.
-
- *Jonathan Baudanza*
-
-* Parsing PostgreSQL arrays with empty strings now works correctly.
-
- Previously, if you tried to parse `{"1","","2","","3"}` the result
- would be `["1","2","3"]`, removing the empty strings from the array,
- which would be incorrect. Now it will correctly produce `["1","","2","","3"]`
- as the result of parsing the above PostgreSQL array.
+* Fixed error when using `with_options` with lambda.
- Fixes #13907.
-
- *Maurício Linhares*
-
-* Associations now raise `ArgumentError` on name conflicts.
-
- Dangerous association names conflicts include instance or class methods already
- defined by `ActiveRecord::Base`.
-
- Example:
-
- class Car < ActiveRecord::Base
- has_many :errors
- end
- # Will raise ArgumentError.
-
- Fixes #13217.
+ Fixes #9805.
*Lauro Caetano*
-* Fix regressions on `select_*` methods.
- When `select_*` methods receive a `Relation` object, they should be able to
- get the arel/binds from it.
- Also fix regressions on `select_rows` that was ignoring the binds.
-
- Fixes #7538, #12017, #13731, #12056.
-
- *arthurnn*
-
-* Active Record objects can now be correctly dumped, loaded and dumped again
- without issues.
-
- Previously, if you did `YAML.dump`, `YAML.load` and then `YAML.dump` again
- in an Active Record model that used serialization it would fail at the last
- dump due to the fields not being correctly serialized before being dumped
- to YAML. Now it is possible to dump and load the same object as many times
- as needed without any issues.
-
- Fixes #13861.
-
- *Maurício Linhares*
-
-* `find_in_batches` now returns an `Enumerator` when called without a block, so that it
- can be chained with other `Enumerable` methods.
-
- *Marc-André Lafortune*
-
-* `enum` now raises on "dangerous" name conflicts.
-
- Dangerous name conflicts includes instance or class method conflicts
- with methods defined within `ActiveRecord::Base` but not its ancestors,
- as well as conflicts with methods generated by other enums on the same
- class.
-
- Fixes #13389.
-
- *Godfrey Chan*
-
-* `scope` now raises on "dangerous" name conflicts.
-
- Similar to dangerous attribute methods, a scope name conflict is
- dangerous if it conflicts with an existing class method defined within
- `ActiveRecord::Base` but not its ancestors.
-
- See also #13389.
-
- *Godfrey Chan*, *Philippe Creux*
-
-* Correctly send an user provided statement to a `lock!()` call.
-
- person.lock! 'FOR SHARE NOWAIT'
- # Before: SELECT * ... LIMIT 1 FOR UPDATE
- # After: SELECT * ... LIMIT 1 FOR SHARE NOWAIT
-
- Fixes #13788.
-
- *Maurício Linhares*
-
-* Handle aliased attributes `select()`, `order()` and `reorder()`.
-
- *Tsutomu Kuroda*
-
-* Reset the collection association when calling `reset` on it.
-
- Before:
-
- post.comments.loaded? # => true
- post.comments.reset
- post.comments.loaded? # => true
-
- After:
-
- post.comments.loaded? # => true
- post.comments.reset
- post.comments.loaded? # => false
-
- Fixes #13777.
-
- *Kelsey Schlarman*
-
-* Make enum fields work as expected with the `ActiveModel::Dirty` API.
-
- Before this change, using the dirty API would have surprising results:
-
- conversation = Conversation.new
- conversation.status = :active
- conversation.status = :archived
- conversation.status_was # => 0
-
- After this change, the same code would result in:
+* Switch `sqlite3:///` URLs (which were temporarily
+ deprecated in 4.1) from relative to absolute.
- conversation = Conversation.new
- conversation.status = :active
- conversation.status = :archived
- conversation.status_was # => "active"
+ If you still want the previous interpretation, you should replace
+ `sqlite3:///my/path` with `sqlite3:my/path`.
- *Rafael Mendonça França*
-
-* `has_one` and `belongs_to` accessors don't add ORDER BY to the queries
- anymore.
-
- Since Rails 4.0, we add an ORDER BY in the `first` method to ensure
- consistent results among different database engines. But for singular
- associations this behavior is not needed since we will have one record to
- return. As this ORDER BY option can lead some performance issues we are
- removing it for singular associations accessors.
-
- Fixes #12623.
-
- *Rafael Mendonça França*
-
-* Prepend table name for column names passed to `Relation#select`.
-
- Example:
-
- Post.select(:id)
- # Before: => SELECT id FROM "posts"
- # After: => SELECT "posts"."id" FROM "posts"
-
- *Yves Senn*
-
-* Fail early with "Primary key not included in the custom select clause"
- in `find_in_batches`.
-
- Before this patch, the exception was raised after the first batch was
- yielded to the block. This means that you only get it, when you hit the
- `batch_size` treshold. This could shadow the issue in development.
-
- *Alexander Balashov*
-
-* Ensure `second` through `fifth` methods act like the `first` finder.
-
- The famous ordinal Array instance methods defined in ActiveSupport
- (`first`, `second`, `third`, `fourth`, and `fifth`) are now available as
- full-fledged finders in ActiveRecord. The biggest benefit of this is ordering
- of the records returned now defaults to the table's primary key in ascending order.
-
- Fixes #13743.
-
- Example:
-
- User.all.second
-
- # Before
- # => 'SELECT "users".* FROM "users"'
+ *Matthew Draper*
- # After
- # => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1'
-
- User.offset(3).second
-
- # Before
- # => 'SELECT "users".* FROM "users" LIMIT -1 OFFSET 3' # sqlite3 gem
- # => 'SELECT "users".* FROM "users" OFFSET 3' # pg gem
- # => 'SELECT `users`.* FROM `users` LIMIT 18446744073709551615 OFFSET 3' # mysql2 gem
-
- # After
- # => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 OFFSET 4'
-
- *Jason Meller*
-
-* ActiveRecord states are now correctly restored after a rollback for
- models that did not define any transactional callbacks (i.e.
- `after_commit`, `after_rollback` or `after_create`).
-
- Fixes #13744.
-
- *Godfrey Chan*
-
-* Make `touch` fire the `after_commit` and `after_rollback` callbacks.
-
- *Harry Brundage*
-
-* Enable partial indexes for `sqlite >= 3.8.0`.
-
- See http://www.sqlite.org/partialindex.html
-
- *Cody Cutrer*
-
-* Don't try to get the subclass if the inheritance column doesn't exist
-
- The `subclass_from_attrs` method is called even if the column specified by
- the `inheritance_column` setting doesn't exist. This prevents setting associations
- via the attributes hash if the association name clashes with the value of the setting,
- typically `:type`. This worked previously in Rails 3.2.
-
- *Ujjwal Thaakar*
-
-* Enum mappings are now exposed via class methods instead of constants.
+* Treat blank UUID values as `nil`.
Example:
- class Conversation < ActiveRecord::Base
- enum status: [ :active, :archived ]
- end
-
- Before:
-
- Conversation::STATUS # => { "active" => 0, "archived" => 1 }
+ Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil>
- After:
+ *Dmitry Lavrov*
- Conversation.statuses # => { "active" => 0, "archived" => 1 }
-
- *Godfrey Chan*
+* Enable support for materialized views on PostgreSQL >= 9.3.
-* Set `NameError#name` when STI-class-lookup fails.
+ *Dave Lee*
- *Chulki Lee*
-
-* Fix bug in `becomes!` when changing from the base model to a STI sub-class.
-
- Fixes #13272.
-
- *the-web-dev*, *Yves Senn*
-
-* Currently Active Record can be configured via the environment variable
- `DATABASE_URL` or by manually injecting a hash of values which is what Rails does,
- reading in `database.yml` and setting Active Record appropriately. Active Record
- expects to be able to use `DATABASE_URL` without the use of Rails, and we cannot
- rip out this functionality without deprecating. This presents a problem though
- when both config is set, and a `DATABASE_URL` is present. Currently the
- `DATABASE_URL` should "win" and none of the values in `database.yml` are
- used. This is somewhat unexpected, if one were to set values such as
- `pool` in the `production:` group of `database.yml` they are ignored.
-
- There are many ways that Active Record initiates a connection today:
-
- - Stand Alone (without rails)
- - `rake db:<tasks>`
- - `ActiveRecord.establish_connection`
-
- - With Rails
- - `rake db:<tasks>`
- - `rails <server> | <console>`
- - `rails dbconsole`
-
- Now all of these behave exactly the same way. The best way to do
- this is to put all of this logic in one place so it is guaranteed to be used.
-
- Here is the matrix of how this behavior works:
-
- ```
- No database.yml
- No DATABASE_URL
- => Error
- ```
-
- ```
- database.yml present
- No DATABASE_URL
- => Use database.yml configuration
- ```
-
- ```
- No database.yml
- DATABASE_URL present
- => use DATABASE_URL configuration
- ```
-
- ```
- database.yml present
- DATABASE_URL present
- => Merged into `url` sub key. If both specify `url` sub key, the `database.yml` `url`
- sub key "wins". If other paramaters `adapter` or `database` are specified in YAML,
- they are discarded as the `url` sub key "wins".
- ```
-
- Current implementation uses `ActiveRecord::Base.configurations` to resolve and merge
- all connection information before returning. This is achieved through a utility
- class: `ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig`.
-
- To understand the exact behavior of this class, it is best to review the
- behavior in `activerecord/test/cases/connection_adapters/connection_handler_test.rb`.
-
- *Richard Schneeman*
-
-* Make `change_column_null` revertable. Fixes #13576.
-
- *Yves Senn*, *Nishant Modak*, *Prathamesh Sonpatki*
-
-* Don't create/drop the test database if RAILS_ENV is specified explicitly.
-
- Previously, when the environment was development, we would always
- create or drop both the test and development databases.
-
- Now, if RAILS_ENV is explicitly defined as development, we don't create
- the test database.
-
- *Damien Mathieu*
-
-* Initialize version on Migration objects so that it can be used in a migration,
- and it will be included in the announce message.
-
- *Dylan Thacker-Smith*
-
-* `change_table` now uses the current adapter's `update_table_definition`
- method to retrieve a specific table definition.
- This ensures that `change_table` and `create_table` will use
- similar objects.
-
- Fixes #13577, #13503.
-
- *Nishant Modak*, *Prathamesh Sonpatki*, *Rafael Mendonça França*
-
-* Fixed ActiveRecord::Store nil conversion TypeError when using YAML coder.
- In case the YAML passed as paramter is nil, uses an empty string.
-
- Fixes #13570.
-
- *Thales Oliveira*
-
-* Deprecate unused `ActiveRecord::Base.symbolized_base_class`
- and `ActiveRecord::Base.symbolized_sti_name` without replacement.
+* The PostgreSQL adapter supports custom domains. Fixes #14305.
*Yves Senn*
-* Since the `test_help.rb` file in Railties now automatically maintains
- your test schema, the `rake db:test:*` tasks are deprecated. This
- doesn't stop you manually running other tasks on your test database
- if needed:
-
- rake db:schema:load RAILS_ENV=test
-
- *Jon Leighton*
-
-* Fix presence validator for association when the associated record responds to `to_a`.
-
- *gmarik*
-
-* Fixed regression on preload/includes with multiple arguments failing in certain conditions,
- raising a NoMethodError internally by calling `reflect_on_association` for `NilClass:Class`.
-
- Fixes #13437.
-
- *Vipul A M*, *khustochka*
-
-* Add the ability to nullify the `enum` column.
-
- Example:
-
- class Conversation < ActiveRecord::Base
- enum gender: [:female, :male]
- end
-
- Conversation::GENDER # => { female: 0, male: 1 }
-
- # conversation.update! gender: 0
- conversation.female!
- conversation.female? # => true
- conversation.gender # => "female"
+* PostgreSQL `Column#type` is now determined through the corresponding OID.
+ The column types stay the same except for enum columns. They no longer have
+ `nil` as type but `enum`.
- # conversation.update! gender: nil
- conversation.gender = nil
- conversation.gender.nil? # => true
- conversation.gender # => nil
-
- *Amr Tamimi*
-
-* Connection specification now accepts a "url" key. The value of this
- key is expected to contain a database URL. The database URL will be
- expanded into a hash and merged.
-
- *Richard Schneeman*
-
-* An `ArgumentError` is now raised on a call to `Relation#where.not(nil)`.
-
- Example:
-
- User.where.not(nil)
-
- # Before
- # => 'SELECT `users`.* FROM `users` WHERE (NOT (NULL))'
-
- # After
- # => ArgumentError, 'Invalid argument for .where.not(), got nil.'
-
- *Kuldeep Aggarwal*
-
-* Deprecated use of string argument as a configuration lookup in
- `ActiveRecord::Base.establish_connection`. Instead, a symbol must be given.
-
- *José Valim*
-
-* Fixed `update_column`, `update_columns`, and `update_all` to correctly serialize
- values for `array`, `hstore` and `json` column types in PostgreSQL.
-
- Fixes #12261.
-
- *Tadas Tamosauskas*, *Carlos Antonio da Silva*
-
-* Do not consider PostgreSQL array columns as number or text columns.
-
- The code uses these checks in several places to know what to do with a
- particular column, for instance AR attribute query methods has a branch
- like this:
-
- if column.number?
- !value.zero?
- end
-
- This should never be true for array columns, since it would be the same
- as running [].zero?, which results in a NoMethodError exception.
-
- Fixing this by ensuring that array columns in PostgreSQL never return
- true for number?/text? checks.
-
- *Carlos Antonio da Silva*
-
-* When connecting to a non-existant database, the error:
- `ActiveRecord::NoDatabaseError` will now be raised. When being used with Rails
- the error message will include information on how to create a database:
- `rake db:create`. Supported adapters: postgresql, mysql, mysql2, sqlite3
-
- *Richard Schneeman*
-
-* Do not raise `'cannot touch on a new record object'` exception on destroying
- already destroyed `belongs_to` association with `touch: true` option.
-
- Fixes #13445.
-
- Example:
-
- # Given Comment has belongs_to :post, touch: true
- comment.post.destroy
- comment.destroy # no longer raises an error
-
- *Paul Nikitochkin*
-
-* Fix a bug when assigning an array containing string numbers to a
- PostgreSQL integer array column.
-
- Fixes #13444.
-
- Example:
-
- # Given Book#ratings is of type :integer, array: true
- Book.new(ratings: [1, 2]) # worked before
- Book.new(ratings: ['1', '2']) # now works as well
-
- *Damien Mathieu*
-
-* Fix `PostgreSQL` insert to properly extract table name from multiline string SQL.
-
- Previously, executing an insert SQL in `PostgreSQL` with a command like this:
-
- insert into articles(
- number)
- values(
- 5152
- )
-
- would not work because the adapter was unable to extract the correct `articles`
- table name.
-
- *Kuldeep Aggarwal*
-
-* Correctly escape PostgreSQL arrays.
-
- Fixes: CVE-2014-0080
-
-* `Relation` no longer has mutator methods like `#map!` and `#delete_if`. Convert
- to an `Array` by calling `#to_a` before using these methods.
-
- It intends to prevent odd bugs and confusion in code that call mutator
- methods directly on the `Relation`.
-
- Example:
-
- # Instead of this
- Author.where(name: 'Hank Moody').compact!
-
- # Now you have to do this
- authors = Author.where(name: 'Hank Moody').to_a
- authors.compact!
-
- *Lauro Caetano*
-
-* Better support for `where()` conditions that use a `belongs_to`
- association name.
-
- Using the name of an association in `where` previously worked only
- if the value was a single `ActiveRecord::Base` object. e.g.
-
- Post.where(author: Author.first)
-
- Any other values, including `nil`, would cause invalid SQL to be
- generated. This change supports arguments in the `where` query
- conditions where the key is a `belongs_to` association name and the
- value is `nil`, an `Array` of `ActiveRecord::Base` objects, or an
- `ActiveRecord::Relation` object.
-
- class Post < ActiveRecord::Base
- belongs_to :author
- end
-
- `nil` value finds records where the association is not set:
-
- Post.where(author: nil)
- # SELECT "posts".* FROM "posts" WHERE "posts"."author_id" IS NULL
-
- `Array` values find records where the association foreign key
- matches the ids of the passed ActiveRecord models, resulting
- in the same query as `Post.where(author_id: [1,2])`:
-
- authors_array = [Author.find(1), Author.find(2)]
- Post.where(author: authors_array)
- # SELECT "posts".* FROM "posts" WHERE "posts"."author_id" IN (1, 2)
-
- `ActiveRecord::Relation` values find records using the same
- query as `Post.where(author_id: Author.where(last_name: "Emde"))`
-
- Post.where(author: Author.where(last_name: "Emde"))
- # SELECT "posts".* FROM "posts"
- # WHERE "posts"."author_id" IN (
- # SELECT "authors"."id" FROM "authors"
- # WHERE "authors"."last_name" = 'Emde')
-
- Polymorphic `belongs_to` associations will continue to be handled
- appropriately, with the polymorphic `association_type` field added
- to the query to match the base class of the value. This feature
- previously only worked when the value was a single `ActveRecord::Base`.
-
- class Post < ActiveRecord::Base
- belongs_to :author, polymorphic: true
- end
-
- Post.where(author: Author.where(last_name: "Emde"))
- # Generates a query similar to:
- Post.where(author_id: Author.where(last_name: "Emde"), author_type: "Author")
-
- *Martin Emde*
-
-* Respect temporary option when dropping tables with MySQL.
-
- Normal DROP TABLE also works, but commits the transaction.
-
- drop_table :temporary_table, temporary: true
-
- *Cody Cutrer*
-
-* Add option to create tables from a query.
-
- create_table(:long_query, temporary: true,
- as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
-
- Generates:
-
- CREATE TEMPORARY TABLE long_query AS
- SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
-
- *Cody Cutrer*
-
-* `db:test:clone` and `db:test:prepare` must load Rails environment.
-
- `db:test:clone` and `db:test:prepare` use `ActiveRecord::Base`. configurations,
- so we need to load the Rails environment, otherwise the config wont be in place.
-
- *arthurnn*
-
-* Use the right column to type cast grouped calculations with custom expressions.
-
- Fixes #13230.
-
- Example:
-
- # Before
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => '0.5' }
-
- # After
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => 0.5 }
-
- *Paul Nikitochkin*
-
-* Polymorphic `belongs_to` associations with the `touch: true` option set update the timestamps of
- the old and new owner correctly when moved between owners of different types.
-
- Example:
-
- class Rating < ActiveRecord::Base
- belongs_to :rateable, polymorphic: true, touch: true
- end
-
- rating = Rating.create rateable: Song.find(1)
- rating.update_attributes rateable: Book.find(2) # => timestamps of Song(1) and Book(2) are updated
-
- *Severin Schoepke*
-
-* Improve formatting of migration exception messages: make them easier to read
- with line breaks before/after, and improve the error for pending migrations.
-
- *John Bachir*
-
-* Fix `last` with `offset` to return the proper record instead of always the last one.
-
- Example:
-
- Model.offset(4).last
- # => returns the 4th record from the end.
-
- Fixes #7441.
-
- *kostya*, *Lauro Caetano*
-
-* `type_to_sql` returns a `String` for unmapped columns. This fixes an error
- when using unmapped PostgreSQL array types.
-
- Example:
-
- change_colum :table, :column, :bigint, array: true
-
- Fixes #13146.
-
- *Jens Fahnenbruck*, *Yves Senn*
-
-* Fix `QueryCache` to work with nested blocks, so that it will only clear the existing cache
- after leaving the outer block instead of clearing it right after the inner block is finished.
-
- *Vipul A M*
-
-* The ERB in fixture files is no longer evaluated in the context of the main
- object. Helper methods used by multiple fixtures should be defined on the
- class object returned by `ActiveRecord::FixtureSet.context_class`.
-
- *Victor Costan*
-
-* Previously, the `has_one` macro incorrectly accepted the `counter_cache`
- option, but never actually supported it. Now it will raise an `ArgumentError`
- when using `has_one` with `counter_cache`.
-
- *Godfrey Chan*
-
-* Implement `rename_index` natively for MySQL >= 5.7.
-
- *Cody Cutrer*
-
-* Fix bug when validating the uniqueness of an aliased attribute.
-
- Fixes #12402.
-
- *Lauro Caetano*
-
-* Update counter cache on a `has_many` relationship regardless of default scope.
-
- Fixes #12952.
-
- *Uku Taht*
-
-* `rename_index` adds the new index before removing the old one. This allows to
- rename indexes on columns with a foreign key and prevents the following error:
-
- Cannot drop index 'index_engines_on_car_id': needed in a foreign key constraint
-
- *Cody Cutrer*, *Yves Senn*
-
-* Raise `ActiveRecord::RecordNotDestroyed` when a replaced child
- marked with `dependent: destroy` fails to be destroyed.
-
- Fixes #12812.
-
- *Brian Thomas Storti*
-
-* Fix validation on uniqueness of empty association.
-
- *Evgeny Li*
-
-* Make `ActiveRecord::Relation#unscope` affect relations it is merged in to.
-
- *Jon Leighton*
-
-* Use strings to represent non-string `order_values`.
+ See #7814.
*Yves Senn*
-* Checks to see if the record contains the foreign key to set the inverse automatically.
-
- *Edo Balvers*
-
-* Added `ActiveRecord::Base.to_param` for convenient "pretty" URLs derived from a model's attribute or method.
-
- Example:
-
- class User < ActiveRecord::Base
- to_param :name
- end
-
- user = User.find_by(name: 'Fancy Pants')
- user.id # => 123
- user.to_param # => "123-fancy-pants"
-
- *Javan Makhmali*
-
-* Added `ActiveRecord::Base.no_touching`, which allows ignoring touch on models.
-
- Example:
-
- Post.no_touching do
- Post.first.touch
- end
-
- *Sam Stephenson*, *Damien Mathieu*
-
-* Prevent the counter cache from being decremented twice when destroying
- a record on a `has_many :through` association.
-
- Fixes #11079.
-
- *Dmitry Dedov*
-
-* Unify boolean type casting for `MysqlAdapter` and `Mysql2Adapter`.
- `type_cast` will return `1` for `true` and `0` for `false`.
-
- Fixes #11119.
-
- *Adam Williams*, *Yves Senn*
-
-* Fix bug where `has_one` association record update result in crash, when replaced with itself.
-
- Fixes #12834.
-
- *Denis Redozubov*, *Sergio Cambra*
-
-* Log bind variables after they are type casted. This makes it more
- transparent what values are actually sent to the database.
-
- irb(main):002:0> Event.find("im-no-integer")
- # Before: ... WHERE "events"."id" = $1 LIMIT 1 [["id", "im-no-integer"]]
- # After: ... WHERE "events"."id" = $1 LIMIT 1 [["id", 0]]
-
- *Yves Senn*
-
-* Fix uninitialized constant `TransactionState` error when `Marshall.load` is used on an Active Record result.
-
- Fixes #12790.
-
- *Jason Ayre*
-
-* `.unscope` now removes conditions specified in `default_scope`.
-
- *Jon Leighton*
-
-* Added `ActiveRecord::QueryMethods#rewhere` which will overwrite an existing, named where condition.
-
- Examples:
-
- Post.where(trashed: true).where(trashed: false) #=> WHERE `trashed` = 1 AND `trashed` = 0
- Post.where(trashed: true).rewhere(trashed: false) #=> WHERE `trashed` = 0
- Post.where(active: true).where(trashed: true).rewhere(trashed: false) #=> WHERE `active` = 1 AND `trashed` = 0
+* Fixed error when specifying a non-empty default value on a PostgreSQL array column.
- *DHH*
+ Fixes #10613.
-* Extend `ActiveRecord::Base#cache_key` to take an optional list of timestamp attributes of which the highest will be used.
+ *Luke Steensen*
- Example:
-
- # last_reviewed_at will be used, if that's more recent than updated_at, or vice versa
- Person.find(5).cache_key(:updated_at, :last_reviewed_at)
-
- *DHH*
+* Make possible to change `record_timestamps` inside Callbacks.
-* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values map to integers in the database, but can be queried by name.
+ *Tieg Zaharia*
- Example:
-
- class Conversation < ActiveRecord::Base
- enum status: [:active, :archived]
- end
+* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
+ custom primary key that didn't save due to validation error.
- Conversation::STATUS # => { active: 0, archived: 1 }
+ Fixes #14393.
- # conversation.update! status: 0
- conversation.active!
- conversation.active? # => true
- conversation.status # => "active"
+ *Chris Finne*
- # conversation.update! status: 1
- conversation.archived!
- conversation.archived? # => true
- conversation.status # => "archived"
+* Introduce `validate` as an alias for `valid?`.
- # conversation.update! status: 1
- conversation.status = :archived
+ This is more intuitive when you want to run validations but don't care about the return value.
- *DHH*
+ *Henrik Nyh*
-* `ActiveRecord::Base#attribute_for_inspect` now truncates long arrays (more than 10 elements).
+* Create indexes inline in CREATE TABLE for MySQL.
- *Jan Bernacki*
+ This is important, because adding an index on a temporary table after it has been created
+ would commit the transaction.
-* Allow for the name of the `schema_migrations` table to be configured.
-
- *Jerad Phelps*
-
-* Do not add to scope includes values from through associations.
- Fixed bug when providing `includes` in through association scope, and fetching targets.
+ It also allows creating and dropping indexed tables with fewer queries and fewer permissions
+ required.
Example:
- class Vendor < ActiveRecord::Base
- has_many :relationships, -> { includes(:user) }
- has_many :users, through: :relationships
+ create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t|
+ t.index :zip
end
+ # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query
- vendor = Vendor.first
-
- # Before
-
- vendor.users.to_a # => Raises exception: not found `:user` for `User`
-
- # After
-
- vendor.users.to_a # => No exception is raised
-
- Fixes #12242, #9517, #10240.
-
- *Paul Nikitochkin*
-
-* Type cast json values on write, so that the value is consistent
- with reading from the database.
-
- Example:
-
- x = JsonDataType.new tags: {"string" => "foo", :symbol => :bar}
-
- # Before:
- x.tags # => {"string" => "foo", :symbol => :bar}
-
- # After:
- x.tags # => {"string" => "foo", "symbol" => "bar"}
-
- *Severin Schoepke*
-
-* `ActiveRecord::Store` works together with PostgreSQL `hstore` columns.
-
- Fixes #12452.
-
- *Yves Senn*
-
-* Fix bug where `ActiveRecord::Store` used a global `Hash` to keep track of
- all registered `stored_attributes`. Now every subclass of
- `ActiveRecord::Base` has it's own `Hash`.
-
- *Yves Senn*
-
-* Save `has_one` association when primary key is manually set.
-
- Fixes #12302.
-
- *Lauro Caetano*
-
-* Allow any version of BCrypt when using `has_secure_password`.
-
- *Mike Perham*
-
-* Sub-query generated for `Relation` passed as array condition did not take in account
- bind values and have invalid syntax.
-
- Generate sub-query with inline bind values.
-
- Fixes #12586.
-
- *Paul Nikitochkin*
-
-* Fix a bug where rake db:structure:load crashed when the path contained
- spaces.
-
- *Kevin Mook*
-
-* `ActiveRecord::QueryMethods#unscope` unscopes negative equality
-
- Allows you to call `#unscope` on a relation with negative equality
- operators, i.e. `Arel::Nodes::NotIn` and `Arel::Nodes::NotEqual` that have
- been generated through the use of `where.not`.
-
- *Eric Hankins*
-
-* Raise an exception when model without primary key calls `.find_with_ids`.
-
- *Shimpei Makimoto*
-
-* Make `Relation#empty?` use `exists?` instead of `count`.
-
- *Szymon Nowak*
-
-* `rake db:structure:dump` no longer crashes when the port was specified as `Fixnum`.
-
- *Kenta Okamoto*
-
-* `NullRelation#pluck` takes a list of columns
-
- The method signature in `NullRelation` was updated to mimic that in
- `Calculations`.
-
- *Derek Prior*
-
-* `scope_chain` should not be mutated for other reflections.
-
- Currently `scope_chain` uses same array for building different
- `scope_chain` for different associations. During processing
- these arrays are sometimes mutated and because of in-place
- mutation the changed `scope_chain` impacts other reflections.
-
- Fix is to dup the value before adding to the `scope_chain`.
-
- Fixes #3882.
-
- *Neeraj Singh*
-
-* Prevent the inversed association from being reloaded on save.
-
- Fixes #9499.
-
- *Dmitry Polushkin*
-
-* Generate subquery for `Relation` if it passed as array condition for `where`
- method.
-
- Example:
-
- # Before
- Blog.where('id in (?)', Blog.where(id: 1))
- # => SELECT "blogs".* FROM "blogs" WHERE "blogs"."id" = 1
- # => SELECT "blogs".* FROM "blogs" WHERE (id IN (1))
-
- # After
- Blog.where('id in (?)', Blog.where(id: 1).select(:id))
- # => SELECT "blogs".* FROM "blogs"
- # WHERE "blogs"."id" IN (SELECT "blogs"."id" FROM "blogs" WHERE "blogs"."id" = 1)
-
- Fixes #12415.
-
- *Paul Nikitochkin*
-
-* For missed association exception message
- which is raised in `ActiveRecord::Associations::Preloader` class
- added owner record class name in order to simplify to find problem code.
-
- *Paul Nikitochkin*
-
-* `has_and_belongs_to_many` is now transparently implemented in terms of
- `has_many :through`. Behavior should remain the same, if not, it is a bug.
-
-* `create_savepoint`, `rollback_to_savepoint` and `release_savepoint` accept
- a savepoint name.
-
- *Yves Senn*
-
-* Make `next_migration_number` accessible for third party generators.
-
- *Yves Senn*
-
-* Objects instantiated using a null relationship will now retain the
- attributes of the where clause.
-
- Fixes #11676, #11675, #11376.
-
- *Paul Nikitochkin*, *Peter Brown*, *Nthalk*
-
-* Fixed `ActiveRecord::Associations::CollectionAssociation#find`
- when using `has_many` association with `:inverse_of` and finding an array of one element,
- it should return an array of one element too.
-
- *arthurnn*
-
-* Callbacks on has_many should access the in memory parent if a inverse_of is set.
-
- *arthurnn*
-
-* `ActiveRecord::ConnectionAdapters.string_to_time` respects
- string with timezone (e.g. Wed, 04 Sep 2013 20:30:00 JST).
-
- Fixes #12278.
+ *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
- *kennyj*
+* Save `has_one` association even if the record doesn't changed.
-* Calling `update_attributes` will now throw an `ArgumentError` whenever it
- gets a `nil` argument. More specifically, it will throw an error if the
- argument that it gets passed does not respond to to `stringify_keys`.
-
- Example:
-
- @my_comment.update_attributes(nil) # => raises ArgumentError
-
- *John Wang*
-
-* Deprecate `quoted_locking_column` method, which isn't used anywhere.
-
- *kennyj*
-
-* Migration dump UUID default functions to schema.rb.
-
- Fixes #10751.
-
- *kennyj*
-
-* Fixed a bug in `ActiveRecord::Associations::CollectionAssociation#find_by_scan`
- when using `has_many` association with `:inverse_of` option and UUID primary key.
-
- Fixes #10450.
-
- *kennyj*
-
-* Fix: joins association, with defined in the scope block constraints by using several
- where constraints and at least of them is not `Arel::Nodes::Equality`,
- generates invalid SQL expression.
-
- Fixes #11963.
-
- *Paul Nikitochkin*
-
-* `CollectionAssociation#first`/`#last` (e.g. `has_many`) use a `LIMIT`ed
- query to fetch results rather than loading the entire collection.
-
- *Lann Martin*
-
-* Make possible to run SQLite rake tasks without the `Rails` constant defined.
-
- *Damien Mathieu*
-
-* Allow Relation#from to accept other relations with bind values.
-
- *Ryan Wallace*
-
-* Fix inserts with prepared statements disabled.
-
- Fixes #12023.
+ Fixes #14407.
*Rafael Mendonça França*
-* Setting a has_one association on a new record no longer causes an empty
- transaction.
-
- *Dylan Thacker-Smith*
-
-* Fix `AR::Relation#merge` sometimes failing to preserve `readonly(false)` flag.
-
- *thedarkone*
-
-* Re-use `order` argument pre-processing for `reorder`.
-
- *Paul Nikitochkin*
-
-* Fix PredicateBuilder so polymorphic association keys in `where` clause can
- accept objects other than direct descendants of `ActiveRecord::Base` (decorated
- models, for example).
-
- *Mikhail Dieterle*
-
-* PostgreSQL adapter recognizes negative money values formatted with
- parentheses (eg. `($1.25) # => -1.25`)).
- Fixes #11899.
-
- *Yves Senn*
-
-* Stop interpreting SQL 'string' columns as :string type because there is no
- common STRING datatype in SQL.
-
- *Ben Woosley*
-
-* `ActiveRecord::FinderMethods#exists?` returns `true`/`false` in all cases.
-
- *Xavier Noria*
-
-* Assign inet/cidr attribute with `nil` value for invalid address.
-
- Example:
-
- record = User.new
- record.logged_in_from_ip # is type of an inet or a cidr
-
- # Before:
- record.logged_in_from_ip = 'bad ip address' # raise exception
-
- # After:
- record.logged_in_from_ip = 'bad ip address' # do not raise exception
- record.logged_in_from_ip # => nil
- record.logged_in_from_ip_before_type_cast # => 'bad ip address'
-
- *Paul Nikitochkin*
+* Use singular table name in generated migrations when
+ `ActiveRecord::Base.pluralize_table_names` is `false`.
-* `add_to_target` now accepts a second optional `skip_callbacks` argument
+ Fixes #13426.
- If truthy, it will skip the :before_add and :after_add callbacks.
-
- *Ben Woosley*
-
-* Fix interactions between `:before_add` callbacks and nested attributes
- assignment of `has_many` associations, when the association was not
- yet loaded:
-
- - A `:before_add` callback was being called when a nested attributes
- assignment assigned to an existing record.
-
- - Nested Attributes assignment did not affect the record in the
- association target when a `:before_add` callback triggered the
- loading of the association
-
- *Jörg Schray*
-
-* Allow enable_extension migration method to be revertible.
-
- *Eric Tipton*
+ *Kuldeep Aggarwal*
-* Type cast hstore values on write, so that the value is consistent
- with reading from the database.
+* `touch` accepts many attributes to be touched at once.
Example:
- x = Hstore.new tags: {"bool" => true, "number" => 5}
-
- # Before:
- x.tags # => {"bool" => true, "number" => 5}
+ # touches :signed_at, :sealed_at, and :updated_at/on attributes.
+ Photo.last.touch(:signed_at, :sealed_at)
- # After:
- x.tags # => {"bool" => "true", "number" => "5"}
+ *James Pinto*
- *Yves Senn* , *Severin Schoepke*
+* `rake db:structure:dump` only dumps schema information if the schema
+ migration table exists.
-* Fix multidimensional PostgreSQL arrays containing non-string items.
+ Fixes #14217.
*Yves Senn*
-* Fixes bug when using includes combined with select, the select statement was overwritten.
-
- Fixes #11773.
-
- *Edo Balvers*
+* Reap connections that were checked out by now-dead threads, instead
+ of waiting until they disconnect by themselves. Before this change,
+ a suitably constructed series of short-lived threads could starve
+ the connection pool, without ever having more than a couple alive at
+ the same time.
-* Load fixtures from linked folders.
+ *Matthew Draper*
- *Kassio Borges*
+* `pk_and_sequence_for` now ensures that only the pg_depend entries
+ pointing to pg_class, and thus only sequence objects, are considered.
-* Create a directory for sqlite3 file if not present on the system.
+ *Josh Williams*
- *Richard Schneeman*
-
-* Removed redundant override of `xml` column definition for PostgreSQL,
- in order to use `xml` column type instead of `text`.
-
- *Paul Nikitochkin*, *Michael Nikitochkin*
-
-* Revert `ActiveRecord::Relation#order` change that make new order
- prepend the old one.
-
- Before:
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY created_at desc, name asc
-
- After:
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY name asc, created_at desc
-
- This also affects order defined in `default_scope` or any kind of associations.
-
-* Add ability to define how a class is converted to Arel predicates.
- For example, adding a very vendor specific regex implementation:
-
- regex_handler = proc do |column, value|
- Arel::Nodes::InfixOperation.new('~', column, value.source)
- end
- ActiveRecord::PredicateBuilder.register_handler(Regexp, regex_handler)
+* `where.not` adds `references` for `includes` like normal `where` calls do.
- *Sean Griffin & @joannecheng*
-
-* Don't allow `quote_value` to be called without a column.
-
- Some adapters require column information to do their job properly.
- By enforcing the provision of the column for this internal method
- we ensure that those using adapters that require column information
- will always get the proper behavior.
-
- *Ben Woosley*
-
-* When using optimistic locking, `update` was not passing the column to `quote_value`
- to allow the connection adapter to properly determine how to quote the value. This was
- affecting certain databases that use specific column types.
-
- Fixes #6763.
-
- *Alfred Wong*
-
-* rescue from all exceptions in `ConnectionManagement#call`
-
- Fixes #11497.
-
- As `ActiveRecord::ConnectionAdapters::ConnectionManagement` middleware does
- not rescue from Exception (but only from StandardError), the Connection
- Pool quickly runs out of connections when multiple erroneous Requests come
- in right after each other.
-
- Rescuing from all exceptions and not just StandardError, fixes this
- behaviour.
-
- *Vipul A M*
-
-* `change_column` for PostgreSQL adapter respects the `:array` option.
+ Fixes #14406.
*Yves Senn*
-* Remove deprecation warning from `attribute_missing` for attributes that are columns.
-
- *Arun Agrawal*
-
-* Remove extra decrement of transaction deep level.
-
- Fixes #4566.
-
- *Paul Nikitochkin*
-
-* Reset @column_defaults when assigning `locking_column`.
- We had a potential problem. For example:
-
- class Post < ActiveRecord::Base
- self.column_defaults # if we call this unintentionally before setting locking_column ...
- self.locking_column = 'my_locking_column'
- end
-
- Post.column_defaults["my_locking_column"]
- => nil # expected value is 0 !
-
- *kennyj*
-
-* Remove extra select and update queries on save/touch/destroy ActiveRecord model
- with belongs to reflection with option `touch: true`.
-
- Fixes #11288.
-
- *Paul Nikitochkin*
-
-* Remove deprecated nil-passing to the following `SchemaCache` methods:
- `primary_keys`, `tables`, `columns` and `columns_hash`.
-
- *Yves Senn*
-
-* Remove deprecated block filter from `ActiveRecord::Migrator#migrate`.
-
- *Yves Senn*
-
-* Remove deprecated String constructor from `ActiveRecord::Migrator`.
-
- *Yves Senn*
-
-* Remove deprecated `scope` use without passing a callable object.
-
- *Arun Agrawal*
-
-* Remove deprecated `transaction_joinable=` in favor of `begin_transaction`
- with `:joinable` option.
-
- *Arun Agrawal*
-
-* Remove deprecated `decrement_open_transactions`.
-
- *Arun Agrawal*
-
-* Remove deprecated `increment_open_transactions`.
-
- *Arun Agrawal*
-
-* Remove deprecated `PostgreSQLAdapter#outside_transaction?`
- method. You can use `#transaction_open?` instead.
-
- *Yves Senn*
-
-* Remove deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
- `ActiveRecord::Fixtures.default_fixture_model_name`.
-
- *Vipul A M*
-
-* Removed deprecated `columns_for_remove` from `SchemaStatements`.
-
- *Neeraj Singh*
-
-* Remove deprecated `SchemaStatements#distinct`.
-
- *Francesco Rodriguez*
-
-* Move deprecated `ActiveRecord::TestCase` into the rails test
- suite. The class is no longer public and is only used for internal
- Rails tests.
-
- *Yves Senn*
-
-* Removed support for deprecated option `:restrict` for `:dependent`
- in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `delete_sql` in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `insert_sql` in associations.
-
- *Neeraj Singh*
-
-* Removed support for deprecated `finder_sql` in associations.
-
- *Neeraj Singh*
-
-* Support array as root element in JSON fields.
-
- *Alexey Noskov & Francesco Rodriguez*
-
-* Removed support for deprecated `counter_sql` in associations.
-
- *Neeraj Singh*
-
-* Do not invoke callbacks when `delete_all` is called on collection.
-
- Method `delete_all` should not be invoking callbacks and this
- feature was deprecated in Rails 4.0. This is being removed.
- `delete_all` will continue to honor the `:dependent` option. However
- if `:dependent` value is `:destroy` then the `:delete_all` deletion
- strategy for that collection will be applied.
-
- User can also force a deletion strategy by passing parameter to
- `delete_all`. For example you can do `@post.comments.delete_all(:nullify)`.
-
- *Neeraj Singh*
-
-* Calling default_scope without a proc will now raise `ArgumentError`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `type_cast_code` from Column.
-
- *Neeraj Singh*
-
-* Removed deprecated options `delete_sql` and `insert_sql` from HABTM
- association.
-
- Removed deprecated options `finder_sql` and `counter_sql` from
- collection association.
-
- *Neeraj Singh*
-
-* Remove deprecated `ActiveRecord::Base#connection` method.
- Make sure to access it via the class.
-
- *Yves Senn*
-
-* Remove deprecation warning for `auto_explain_threshold_in_seconds`.
-
- *Yves Senn*
-
-* Remove deprecated `:distinct` option from `Relation#count`.
-
- *Yves Senn*
-
-* Removed deprecated methods `partial_updates`, `partial_updates?` and
- `partial_updates=`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `scoped`.
-
- *Neeraj Singh*
-
-* Removed deprecated method `default_scopes?`.
-
- *Neeraj Singh*
-
-* Remove implicit join references that were deprecated in 4.0.
+* Extend fixture `$LABEL` replacement to allow string interpolation.
Example:
- # before with implicit joins
- Comment.where('posts.author_id' => 7)
-
- # after
- Comment.references(:posts).where('posts.author_id' => 7)
+ martin:
+ email: $LABEL@email.com
- *Yves Senn*
-
-* Apply default scope when joining associations. For example:
-
- class Post < ActiveRecord::Base
- default_scope -> { where published: true }
- end
-
- class Comment
- belongs_to :post
- end
+ users(:martin).email # => martin@email.com
- When calling `Comment.joins(:post)`, we expect to receive only
- comments on published posts, since that is the default scope for
- posts.
+ *Eric Steele*
- Before this change, the default scope from `Post` was not applied,
- so we'd get comments on unpublished posts.
+* Add support for `Relation` be passed as parameter on `QueryCache#select_all`.
- *Jon Leighton*
+ Fixes #14361.
-* Remove `activerecord-deprecated_finders` as a dependency.
-
- *Łukasz Strzałkowski*
-
-* Remove Oracle / Sqlserver / Firebird database tasks that were deprecated in 4.0.
-
- *kennyj*
-
-* `find_each` now returns an `Enumerator` when called without a block, so that it
- can be chained with other `Enumerable` methods.
-
- *Ben Woosley*
-
-* `ActiveRecord::Result.each` now returns an `Enumerator` when called without
- a block, so that it can be chained with other `Enumerable` methods.
-
- *Ben Woosley*
-
-* Flatten merged join_values before building the joins.
-
- While joining_values special treatment is given to string values.
- By flattening the array it ensures that string values are detected
- as strings and not arrays.
+ *arthurnn*
- Fixes #10669.
+* Passing an Active Record object to `find` is now deprecated. Call `.id`
+ on the object first.
- *Neeraj Singh and iwiznia*
+* Passing an Active Record object to `find` or `exists?` is now deprecated.
+ Call `.id` on the object first.
-* Do not load all child records for inverse case.
+* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
- currently `post.comments.find(Comment.first.id)` would load all
- comments for the given post to set the inverse association.
+ *Ryuta Kamizono*
- This has a huge performance penalty. Because if post has 100k
- records and all these 100k records would be loaded in memory
- even though the comment id was supplied.
+* Support for MySQL 5.6 fractional seconds.
- Fix is to use in-memory records only if loaded? is true. Otherwise
- load the records using full sql.
+ *arthurnn*, *Tatsuhiko Miyagawa*
- Fixes #10509.
+* Support for Postgres `citext` data type enabling case-insensitive where
+ values without needing to wrap in UPPER/LOWER sql functions.
- *Neeraj Singh*
+ *Troy Kruthoff*, *Lachlan Sylvester*
-* `inspect` on Active Record model classes does not initiate a
- new connection. This means that calling `inspect`, when the
- database is missing, will no longer raise an exception.
- Fixes #10936.
+* Allow strings to specify the `#order` value.
Example:
- Author.inspect # => "Author(no database connection)"
+ Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql
- *Yves Senn*
-
-* Handle single quotes in PostgreSQL default column values.
- Fixes #10881.
-
- *Dylan Markow*
+ *Marcelo Casiraghi*, *Robin Dupret*
-* Log the sql that is actually sent to the database.
+* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID"
+ warnings on enum columns.
- If I have a query that produces sql
- `WHERE "users"."name" = 'a b'` then in the log all the
- whitespace is being squeezed. So the sql that is printed in the
- log is `WHERE "users"."name" = 'a b'`.
+ *Dieter Komendera*
- Do not squeeze whitespace out of sql queries. Fixes #10982.
+* `includes` is able to detect the right preloading strategy when string
+ joins are involved.
- *Neeraj Singh*
+ Fixes #14109.
-* Fixture setup no longer depends on `ActiveRecord::Base.configurations`.
- This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`.
+ *Aaron Patterson*, *Yves Senn*
- *Yves Senn*
-
-* Fix mysql2 adapter raises the correct exception when executing a query on a
- closed connection.
+* Fixed error with validation with enum fields for records where the
+ value for any enum attribute is always evaluated as 0 during
+ uniqueness validation.
- *Yves Senn*
-
-* Ambiguous reflections are on :through relationships are no longer supported.
- For example, you need to change this:
-
- class Author < ActiveRecord::Base
- has_many :posts
- has_many :taggings, through: :posts
- end
-
- class Post < ActiveRecord::Base
- has_one :tagging
- has_many :taggings
- end
+ Fixes #14172.
- class Tagging < ActiveRecord::Base
- end
+ *Vilius Luneckas* *Ahmed AbouElhamayed*
- To this:
+* `before_add` callbacks are fired before the record is saved on
+ `has_and_belongs_to_many` assocations *and* on `has_many :through`
+ associations. Before this change, `before_add` callbacks would be fired
+ before the record was saved on `has_and_belongs_to_many` associations, but
+ *not* on `has_many :through` associations.
- class Author < ActiveRecord::Base
- has_many :posts
- has_many :taggings, through: :posts, source: :tagging
- end
+ Fixes #14144.
- class Post < ActiveRecord::Base
- has_one :tagging
- has_many :taggings
- end
+* Fixed STI classes not defining an attribute method if there is a
+ conflicting private method defined on its ancestors.
- class Tagging < ActiveRecord::Base
- end
+ Fixes #11569.
- *Aaron Patterson*
+ *Godfrey Chan*
-* Remove column restrictions for `count`, let the database raise if the SQL is
- invalid. The previous behavior was untested and surprising for the user.
- Fixes #5554.
+* Coerce strings when reading attributes. Fixes #10485.
Example:
- User.select("name, username").count
- # Before => SELECT count(*) FROM users
- # After => ActiveRecord::StatementInvalid
-
- # you can still use `count(:all)` to perform a query unrelated to the
- # selected columns
- User.select("name, username").count(:all) # => SELECT count(*) FROM users
+ book = Book.new(title: 12345)
+ book.save!
+ book.title # => "12345"
*Yves Senn*
-* Rails now automatically detects inverse associations. If you do not set the
- `:inverse_of` option on the association, then Active Record will guess the
- inverse association based on heuristics.
-
- Note that automatic inverse detection only works on `has_many`, `has_one`,
- and `belongs_to` associations. Extra options on the associations will
- also prevent the association's inverse from being found automatically.
-
- The automatic guessing of the inverse association uses a heuristic based
- on the name of the class, so it may not work for all associations,
- especially the ones with non-standard names.
-
- You can turn off the automatic detection of inverse associations by setting
- the `:inverse_of` option to `false` like so:
-
- class Taggable < ActiveRecord::Base
- belongs_to :tag, inverse_of: false
- end
+* Deprecate half-baked support for PostgreSQL range values with excluding beginnings.
+ We currently map PostgreSQL ranges to Ruby ranges. This conversion is not fully
+ possible because the Ruby range does not support excluded beginnings.
- *John Wang*
-
-* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432.
-
- *Adam Anderson*
-
-* Usage of `implicit_readonly` is being removed`. Please use `readonly` method
- explicitly to mark records as `readonly.
- Fixes #10615.
-
- Example:
-
- user = User.joins(:todos).select("users.*, todos.title as todos_title").readonly(true).first
- user.todos_title = 'clean pet'
- user.save! # will raise error
+ The current solution of incrementing the beginning is not correct and is now
+ deprecated. For subtypes where we don't know how to increment (e.g. `#succ`
+ is not defined) it will raise an ArgumentException for ranges with excluding
+ beginnings.
*Yves Senn*
-* Fix the `:primary_key` option for `has_many` associations.
-
- Fixes #10693.
+* Support for user created range types in PostgreSQL.
*Yves Senn*
-* Fix bug where tiny types are incorrectly coerced as boolean when the length is more than 1.
-
- Fixes #10620.
-
- *Aaron Patterson*
-
-* Also support extensions in PostgreSQL 9.1. This feature has been supported since 9.1.
-
- *kennyj*
-
-* Deprecate `ConnectionAdapters::SchemaStatements#distinct`,
- as it is no longer used by internals.
-
- *Ben Woosley*
-
-* Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix`
- is not blank.
-
- Call `assume_migrated_upto_version` on connection to prevent it from first
- being picked up in `method_missing`.
-
- In the base class, `Migration`, `method_missing` expects the argument to be a
- table name, and calls `proper_table_name` on the arguments before sending to
- `connection`. If `table_name_prefix` or `table_name_suffix` is used, the schema
- version changes to `prefix_version_suffix`, breaking `rake test:prepare`.
-
- Fixes #10411.
-
- *Kyle Stevens*
-
-* Method `read_attribute_before_type_cast` should accept input as symbol.
-
- *Neeraj Singh*
-
-* Confirm a record has not already been destroyed before decrementing counter cache.
-
- *Ben Tucker*
-
-* Fixed a bug in `ActiveRecord#sanitize_sql_hash_for_conditions` in which
- `self.class` is an argument to `PredicateBuilder#build_from_hash`
- causing `PredicateBuilder` to call non-existent method
- `Class#reflect_on_association`.
-
- *Zach Ohlgren*
-
-* While removing index if column option is missing then raise IrreversibleMigration exception.
-
- Following code should raise `IrreversibleMigration`. But the code was
- failing since options is an array and not a hash.
-
- def change
- change_table :users do |t|
- t.remove_index [:name, :email]
- end
- end
-
- Fix was to check if the options is a Hash before operating on it.
-
- Fixes #10419.
-
- *Neeraj Singh*
-
-* Do not overwrite manually built records during one-to-one nested attribute assignment
-
- For one-to-one nested associations, if you build the new (in-memory)
- child object yourself before assignment, then the NestedAttributes
- module will not overwrite it, e.g.:
-
- class Member < ActiveRecord::Base
- has_one :avatar
- accepts_nested_attributes_for :avatar
-
- def avatar
- super || build_avatar(width: 200)
- end
- end
-
- member = Member.new
- member.avatar_attributes = {icon: 'sad'}
- member.avatar.width # => 200
-
- *Olek Janiszewski*
-
-* fixes bug introduced by #3329. Now, when autosaving associations,
- deletions happen before inserts and saves. This prevents a 'duplicate
- unique value' database error that would occur if a record being created had
- the same value on a unique indexed field as that of a record being destroyed.
-
- *Johnny Holton*
-
-* Handle aliased attributes in ActiveRecord::Relation.
-
- When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database:
-
- With the model
-
- class Topic
- alias_attribute :heading, :title
- end
-
- The call
-
- Topic.where(heading: 'The First Topic')
-
- should yield the same result as
-
- Topic.where(title: 'The First Topic')
-
- This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`.
-
- This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`.
-
- *Godfrey Chan*
-
-* Mute `psql` output when running rake db:schema:load.
-
- *Godfrey Chan*
-
-* Trigger a save on `has_one association=(associate)` when the associate contents have changed.
-
- Fixes #8856.
-
- *Chris Thompson*
-
-* Abort a rake task when missing db/structure.sql like `db:schema:load` task.
-
- *kennyj*
-
-* rake:db:test:prepare falls back to original environment after execution.
-
- *Slava Markevich*
-
-Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 142d21ce92..4abe2ad0a0 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -4,6 +4,12 @@ require 'active_support/core_ext/module/remove_method'
require 'active_record/errors'
module ActiveRecord
+ class AssociationNotFoundError < ConfigurationError #:nodoc:
+ def initialize(record, association_name)
+ super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
+ end
+ end
+
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(reflection, associated_class = nil)
super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
@@ -145,7 +151,7 @@ module ActiveRecord
association = association_instance_get(name)
if association.nil?
- reflection = self.class.reflect_on_association(name)
+ raise AssociationNotFoundError.new(self, name) unless reflection = self.class.reflect_on_association(name)
association = reflection.association_class.new(self, reflection)
association_instance_set(name, association)
end
@@ -530,8 +536,8 @@ module ActiveRecord
# end
#
# @firm = Firm.first
- # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
- # @firm.invoices # selects all invoices by going through the Client join model
+ # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
+ # @firm.invoices # selects all invoices by going through the Client join model
#
# Similarly you can go through a +has_one+ association on the join model:
#
@@ -1036,6 +1042,9 @@ module ActiveRecord
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
#
+ # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
+ #
# [collection(force_reload = false)]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
@@ -1094,9 +1103,6 @@ module ActiveRecord
# Does the same as <tt>collection.create</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (*Note*: +collection+ is replaced with the symbol passed as the first argument, so
- # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.)
- #
# === Example
#
# A <tt>Firm</tt> class declares <tt>has_many :clients</tt>, which will add:
@@ -1211,6 +1217,9 @@ module ActiveRecord
#
# The following methods for retrieval and query of a single associated object will be added:
#
+ # +association+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
+ #
# [association(force_reload = false)]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
@@ -1229,9 +1238,6 @@ module ActiveRecord
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (+association+ is replaced with the symbol passed as the first argument, so
- # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
- #
# === Example
#
# An Account class declares <tt>has_one :beneficiary</tt>, which will add:
@@ -1317,6 +1323,9 @@ module ActiveRecord
# Methods will be added for retrieval and query for a single associated object, for which
# this object holds an id:
#
+ # +association+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
+ #
# [association(force_reload = false)]
# Returns the associated object. +nil+ is returned if none is found.
# [association=(associate)]
@@ -1332,9 +1341,6 @@ module ActiveRecord
# Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
# if the record is invalid.
#
- # (+association+ is replaced with the symbol passed as the first argument, so
- # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
- #
# === Example
#
# A Post class declares <tt>belongs_to :author</tt>, which will add:
@@ -1454,6 +1460,9 @@ module ActiveRecord
#
# Adds the following methods for retrieval and query:
#
+ # +collection+ is a placeholder for the symbol passed as the first argument, so
+ # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
+ #
# [collection(force_reload = false)]
# Returns an array of all the associated objects.
# An empty array is returned if none are found.
@@ -1495,9 +1504,6 @@ module ActiveRecord
# with +attributes+, linked to this object through the join table, and that has already been
# saved (if it passed the validation).
#
- # (+collection+ is replaced with the symbol passed as the first argument, so
- # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
- #
# === Example
#
# A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
@@ -1584,7 +1590,7 @@ module ActiveRecord
hm_options[:through] = middle_reflection.name
hm_options[:source] = join_model.right_reflection.name
- [:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k|
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate].each do |k|
hm_options[k] = options[k] if options.key? k
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 4e46256862..9ad2d2fb12 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -232,7 +232,7 @@ module ActiveRecord
# Returns true if record contains the foreign_key
def foreign_key_for?(record)
- record.attributes.has_key? reflection.foreign_key
+ record.has_attribute?(reflection.foreign_key)
end
# This should be implemented to return the values of the relevant key(s) on the owner,
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index b90c90c7c4..dee7e972c1 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -24,10 +24,6 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
- def initialize(owner, reflection)
- super
- @proxy = CollectionProxy.create(klass, self)
- end
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
@@ -37,7 +33,7 @@ module ActiveRecord
reload
end
- @proxy
+ @proxy ||= CollectionProxy.create(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -149,9 +145,8 @@ module ActiveRecord
# be chained. Since << flattens its argument list and inserts each record,
# +push+ and +concat+ behave identically.
def concat(*records)
- load_target if owner.new_record?
-
if owner.new_record?
+ load_target
concat_records(records)
else
transaction { concat_records(records) }
@@ -253,7 +248,7 @@ module ActiveRecord
dependent = _options[:dependent] || options[:dependent]
if records.first == :all
- if loaded? || dependent == :destroy
+ if (loaded? || dependent == :destroy) && dependent != :delete_all
delete_or_destroy(load_target, dependent)
else
delete_records(:all, dependent)
@@ -363,7 +358,9 @@ module ActiveRecord
if owner.new_record?
replace_records(other_array, original_target)
else
- transaction { replace_records(other_array, original_target) }
+ if other_array != original_target
+ transaction { replace_records(other_array, original_target) }
+ end
end
end
@@ -372,7 +369,7 @@ module ActiveRecord
if record.new_record?
include_in_memory?(record)
else
- loaded? ? target.include?(record) : scope.exists?(record)
+ loaded? ? target.include?(record) : scope.exists?(record.id)
end
else
false
@@ -517,13 +514,13 @@ module ActiveRecord
target
end
- def concat_records(records)
+ def concat_records(records, should_raise = false)
result = true
records.flatten.each do |record|
raise_on_type_mismatch!(record)
add_to_target(record) do |rec|
- result &&= insert_record(rec) unless owner.new_record?
+ result &&= insert_record(rec, true, should_raise) unless owner.new_record?
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index eba688866c..5b71ed163e 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -860,6 +860,10 @@ module ActiveRecord
!!@association.include?(record)
end
+ def arel
+ scope.arel
+ end
+
def proxy_association
@association
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 6457182195..3e4b7902c0 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -71,15 +71,15 @@ module ActiveRecord
[association_scope.limit_value, count].compact.min
end
- def has_cached_counter?(reflection = reflection)
+ def has_cached_counter?(reflection = reflection())
owner.attribute_present?(cached_counter_attribute_name(reflection))
end
- def cached_counter_attribute_name(reflection = reflection)
+ def cached_counter_attribute_name(reflection = reflection())
options[:counter_cache] || "#{reflection.name}_count"
end
- def update_counter(difference, reflection = reflection)
+ def update_counter(difference, reflection = reflection())
if has_cached_counter?(reflection)
counter = cached_counter_attribute_name(reflection)
owner.class.update_counters(owner.id, counter => difference)
@@ -98,7 +98,7 @@ module ActiveRecord
# it will be decremented twice.
#
# Hence this method.
- def inverse_updates_counter_cache?(reflection = reflection)
+ def inverse_updates_counter_cache?(reflection = reflection())
counter_name = cached_counter_attribute_name(reflection)
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
inverse_reflection.counter_cache_column == counter_name
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 31b8d27892..64bc98c642 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -30,7 +30,6 @@ module ActiveRecord
unless owner.new_record?
records.flatten.each do |record|
raise_on_type_mismatch!(record)
- record.save! if record.new_record?
end
end
@@ -40,7 +39,7 @@ module ActiveRecord
def concat_records(records)
ensure_not_nested
- records = super
+ records = super(records, true)
if owner.new_record? && records
records.flatten.each do |record|
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 cee3c9999f..1d923ecc09 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -54,7 +54,7 @@ module ActiveRecord
end
scope_chain_index += 1
- scope_chain_items.concat [klass.send(:build_default_scope)].compact
+ scope_chain_items.concat [klass.send(:build_default_scope, ActiveRecord::Relation.create(klass, table))].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 83637a0409..31ddf4e0fc 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -140,36 +140,13 @@ module ActiveRecord
end
def grouped_records(association, records)
- reflection_records = records_by_reflection(association, records)
-
- reflection_records.each_with_object({}) do |(reflection, r_records),h|
- h[reflection] = r_records.group_by { |record|
- association_klass(reflection, record)
- }
- end
- end
-
- def records_by_reflection(association, records)
- records.group_by do |record|
- reflection = record.class.reflect_on_association(association)
-
- reflection || raise_config_error(record, association)
- end
- end
-
- def raise_config_error(record, association)
- raise ActiveRecord::ConfigurationError,
- "Association named '#{association}' was not found on #{record.class.name}; " \
- "perhaps you misspelled it?"
- end
-
- def association_klass(reflection, record)
- if reflection.macro == :belongs_to && reflection.options[:polymorphic]
- klass = record.read_attribute(reflection.foreign_type.to_s)
- klass && klass.constantize
- else
- reflection.klass
+ h = {}
+ records.each do |record|
+ assoc = record.association(association)
+ klasses = h[assoc.reflection] ||= {}
+ (klasses[assoc.klass] ||= []) << record
end
+ h
end
class AlreadyLoaded
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 2a8530af62..70e97432e4 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -23,7 +23,7 @@ module ActiveRecord
reset_association owners, through_reflection.name
- middle_records = through_records.map { |(_,rec)| rec }.flatten
+ middle_records = through_records.flat_map { |(_,rec)| rec }
preloaders = preloader.preload(middle_records,
source_reflection.name,
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 9326c9c117..4b1733619a 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -29,6 +29,8 @@ module ActiveRecord
end
}
+ BLACKLISTED_CLASS_METHODS = %w(private public protected)
+
class AttributeMethodCache
def initialize
@module = Module.new
@@ -106,7 +108,8 @@ module ActiveRecord
else
# If B < A and A defines its own attribute method, then we don't want to overwrite that.
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
- defined && !ActiveRecord::Base.method_defined?(method_name) || super
+ base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name)
+ defined && !base_defined || super
end
end
@@ -131,7 +134,7 @@ module ActiveRecord
# A class method is 'dangerous' if it is already (re)defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'new' is.)
def dangerous_class_method?(method_name)
- class_method_defined_within?(method_name, Base)
+ BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base)
end
def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 4f58d06f35..e9622ca0c1 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -301,7 +301,7 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?(self.validation_context)
+ unless valid = record.valid?
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 9ec1feea97..1d47cba234 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -310,8 +310,8 @@ module ActiveRecord #:nodoc:
include Locking::Optimistic
include Locking::Pessimistic
include AttributeMethods
- include Callbacks
include Timestamp
+ include Callbacks
include Associations
include ActiveModel::SecurePassword
include AutosaveAssociation
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 759e162e19..db80c0faee 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -58,13 +58,11 @@ module ActiveRecord
# * +checkout_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
# * +reaping_frequency+: frequency in seconds to periodically run the
- # Reaper, which attempts to find and close dead connections, which can
- # occur if a programmer forgets to close a connection at the end of a
- # thread or a thread dies unexpectedly. (Default nil, which means don't
- # run the Reaper).
- # * +dead_connection_timeout+: number of seconds from last checkout
- # after which the Reaper will consider a connection reapable. (default
- # 5 seconds).
+ # Reaper, which attempts to find and recover connections from dead
+ # threads, which can occur if a programmer forgets to close a
+ # connection at the end of a thread or a thread dies unexpectedly.
+ # Regardless of this setting, the Reaper will be invoked before every
+ # blocking wait. (Default nil, which means don't schedule the Reaper).
class ConnectionPool
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
# with which it shares a Monitor. But could be a generic Queue.
@@ -222,7 +220,7 @@ module ActiveRecord
include MonitorMixin
- attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
+ attr_accessor :automatic_reconnect, :checkout_timeout
attr_reader :spec, :connections, :size, :reaper
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
@@ -237,7 +235,6 @@ module ActiveRecord
@spec = spec
@checkout_timeout = spec.config[:checkout_timeout] || 5
- @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5
@reaper = Reaper.new self, spec.config[:reaping_frequency]
@reaper.run
@@ -361,11 +358,13 @@ module ActiveRecord
# calling +checkout+ on this pool.
def checkin(conn)
synchronize do
+ owner = conn.owner
+
conn.run_callbacks :checkin do
conn.expire
end
- release conn
+ release conn, owner
@available.add conn
end
@@ -378,22 +377,28 @@ module ActiveRecord
@connections.delete conn
@available.delete conn
- # FIXME: we might want to store the key on the connection so that removing
- # from the reserved hash will be a little easier.
- release conn
+ release conn, conn.owner
@available.add checkout_new_connection if @available.any_waiting?
end
end
- # Removes dead connections from the pool. A dead connection can occur
- # if a programmer forgets to close a connection at the end of a thread
+ # Recover lost connections for the pool. A lost connection can occur if
+ # a programmer forgets to checkin a connection at the end of a thread
# or a thread dies unexpectedly.
def reap
- synchronize do
- stale = Time.now - @dead_connection_timeout
- connections.dup.each do |conn|
- if conn.in_use? && stale > conn.last_use && !conn.active_threadsafe?
+ stale_connections = synchronize do
+ @connections.select do |conn|
+ conn.in_use? && !conn.owner.alive?
+ end
+ end
+
+ stale_connections.each do |conn|
+ synchronize do
+ if conn.active?
+ conn.reset!
+ checkin conn
+ else
remove conn
end
end
@@ -415,20 +420,15 @@ module ActiveRecord
elsif @connections.size < @size
checkout_new_connection
else
+ reap
@available.poll(@checkout_timeout)
end
end
- def release(conn)
- thread_id = if @reserved_connections[current_connection_id] == conn
- current_connection_id
- else
- @reserved_connections.keys.find { |k|
- @reserved_connections[k] == conn
- }
- end
+ def release(conn, owner)
+ thread_id = owner.object_id
- @reserved_connections.delete thread_id if thread_id
+ @reserved_connections.delete thread_id
end
def new_connection
@@ -538,7 +538,10 @@ module ActiveRecord
# for (not necessarily the current class).
def retrieve_connection(klass) #:nodoc:
pool = retrieve_connection_pool(klass)
- (pool && pool.connection) or raise ConnectionNotEstablished
+ raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
+ conn = pool.connection
+ raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
+ conn
end
# Returns true if a connection that's accessible to this class has
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 6eb59cc398..da25e640c1 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -20,14 +20,7 @@ module ActiveRecord
# Returns an ActiveRecord::Result instance.
def select_all(arel, name = nil, binds = [])
- if arel.is_a?(Relation)
- relation = arel
- arel = relation.arel
- if !binds || binds.empty?
- binds = relation.bind_values
- end
- end
-
+ arel, binds = binds_from_relation arel, binds
select(to_sql(arel, binds), name, binds)
end
@@ -47,10 +40,7 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- binds = []
- if arel.is_a?(Relation)
- arel, binds = arel.arel, arel.bind_values
- end
+ arel, binds = binds_from_relation arel, []
select_rows(to_sql(arel, binds), name, binds).map(&:first)
end
@@ -328,7 +318,7 @@ module ActiveRecord
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
- elsif limit.to_s =~ /,/
+ elsif limit.to_s.include?(',')
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
@@ -389,6 +379,13 @@ module ActiveRecord
row = result.rows.first
row && row.first
end
+
+ def binds_from_relation(relation, binds)
+ if relation.is_a?(Relation) && binds.blank?
+ relation, binds = relation.arel, relation.bind_values
+ end
+ [relation, binds]
+ end
end
end
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 adc23a6674..4a4506c7f5 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -63,6 +63,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
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) }
else
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 a51691bfa8..47fe501752 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def visit_AddColumn(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
add_column_options!(sql, column_options(o))
end
@@ -26,7 +26,7 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale)
column_sql = "#{quote_column_name(o.name)} #{sql_type}"
add_column_options!(column_sql, column_options(o)) unless o.primary_key?
column_sql
@@ -64,7 +64,7 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- sql << " DEFAULT #{@conn.quote(options[:default], options[:column])}" if options_include_default?(options)
+ sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options)
# must explicitly check for :null to allow change_column to work on migrations
if options[:null] == false
sql << " NOT NULL"
@@ -75,6 +75,12 @@ module ActiveRecord
sql
end
+ def quote_value(value, column)
+ column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale)
+
+ @conn.quote(value, column)
+ end
+
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
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 c39bf15e83..71c3a4378b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# 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, :primary_key) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key, :sql_type) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
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 ad069f5e53..aa99822389 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -40,7 +40,7 @@ module ActiveRecord
# index_exists?(:suppliers, :company_id, unique: true)
#
# # Check an index with a custom name exists
- # index_exists?(:suppliers, :company_id, name: "idx_company_id"
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
#
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name)
@@ -186,24 +186,23 @@ module ActiveRecord
def create_table(table_name, options = {})
td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
- if !options[:as]
- unless options[:id] == false
- pk = options.fetch(:primary_key) {
- Base.get_primary_key table_name.to_s.singularize
- }
-
- td.primary_key pk, options.fetch(:id, :primary_key), options
+ if options[:id] != false && !options[:as]
+ pk = options.fetch(:primary_key) do
+ Base.get_primary_key table_name.to_s.singularize
end
- yield td if block_given?
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
+ yield td if block_given?
+
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
end
- execute schema_creation.accept td
- td.indexes.each_pair { |c,o| add_index table_name, c, o }
+ result = execute schema_creation.accept td
+ td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
+ result
end
# Creates a new join table with the name created using the lexical order of the first two
@@ -740,6 +739,40 @@ module ActiveRecord
Table.new(table_name, base)
end
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
+ column_names = Array(column_name)
+ index_name = index_name(table_name, column: column_names)
+
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_type = options[:type].to_s if options.key?(:type)
+ index_name = options[:name].to_s if options.key?(:name)
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+
+ if options.key?(:algorithm)
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
+ }
+ end
+
+ using = "USING #{options[:using]}" if options[:using].present?
+
+ if supports_partial_index?
+ 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
+ if table_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]
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -770,40 +803,6 @@ module ActiveRecord
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
- def add_index_options(table_name, column_name, options = {})
- column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
-
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
-
- index_type = options[:unique] ? "UNIQUE" : ""
- index_type = options[:type].to_s if options.key?(:type)
- index_name = options[:name].to_s if options.key?(:name)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
-
- if options.key?(:algorithm)
- algorithm = index_algorithms.fetch(options[:algorithm]) {
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
- }
- end
-
- using = "USING #{options[:using]}" if options[:using].present?
-
- if supports_partial_index?
- 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
- if 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]
- end
-
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 11b28a4858..ffd5055dec 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -71,8 +71,8 @@ module ActiveRecord
define_callbacks :checkout, :checkin
attr_accessor :visitor, :pool
- attr_reader :schema_cache, :last_use, :in_use, :logger
- alias :in_use? :in_use
+ attr_reader :schema_cache, :owner, :logger
+ alias :in_use? :owner
def self.type_cast_config_to_integer(config)
if config =~ SIMPLE_INT
@@ -94,9 +94,8 @@ module ActiveRecord
super()
@connection = connection
- @in_use = false
+ @owner = nil
@instrumenter = ActiveSupport::Notifications.instrumenter
- @last_use = false
@logger = logger
@pool = pool
@schema_cache = SchemaCache.new self
@@ -114,9 +113,8 @@ module ActiveRecord
def lease
synchronize do
- unless in_use
- @in_use = true
- @last_use = Time.now
+ unless in_use?
+ @owner = Thread.current
end
end
end
@@ -127,7 +125,7 @@ module ActiveRecord
end
def expire
- @in_use = false
+ @owner = nil
end
def unprepared_visitor
@@ -148,28 +146,19 @@ module ActiveRecord
'Abstract'
end
- # Does this adapter support migrations? Backend specific, as the
- # abstract adapter always returns +false+.
+ # Does this adapter support migrations?
def supports_migrations?
false
end
# Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables? Backend specific, as
- # the abstract adapter always returns +false+.
+ # to an Active Record class, such as join tables?
def supports_primary_key?
false
end
- # Does this adapter support using DISTINCT within COUNT? This is +true+
- # for all adapters except sqlite.
- def supports_count_distinct?
- true
- end
-
# Does this adapter support DDL rollbacks in transactions? That is, would
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
- # SQL Server, and others support this. MySQL and others do not.
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
def supports_ddl_transactions?
false
end
@@ -178,8 +167,7 @@ module ActiveRecord
false
end
- # Does this adapter support savepoints? PostgreSQL and MySQL do,
- # SQLite < 3.6.8 does not.
+ # Does this adapter support savepoints?
def supports_savepoints?
false
end
@@ -187,7 +175,6 @@ module ActiveRecord
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
- # This is false for all adapters but Firebird.
def prefetch_primary_key?(table_name = nil)
false
end
@@ -202,8 +189,7 @@ module ActiveRecord
false
end
- # Does this adapter support explain? As of this writing sqlite3,
- # mysql2, and postgresql are the only ones that do.
+ # Does this adapter support explain?
def supports_explain?
false
end
@@ -213,12 +199,17 @@ module ActiveRecord
false
end
- # Does this adapter support database extensions? As of this writing only
- # postgresql does.
+ # Does this adapter support database extensions?
def supports_extensions?
false
end
+ # Does this adapter support creating indexes in the same statement as
+ # creating the table?
+ def supports_indexes_in_create?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -227,14 +218,12 @@ module ActiveRecord
def enable_extension(name)
end
- # A list of extensions, to be filled in by adapters that support them. At
- # the moment only postgresql does.
+ # A list of extensions, to be filled in by adapters that support them.
def extensions
[]
end
# A list of index algorithms, to be filled by adapters that support them.
- # MySQL and PostgreSQL have support for them right now.
def index_algorithms
{}
end
@@ -262,12 +251,6 @@ module ActiveRecord
def active?
end
- # Adapter should redefine this if it needs a threadsafe way to approximate
- # if the connection is active
- def active_threadsafe?
- active?
- end
-
# Disconnects from the database if already connected, and establishes a
# new connection with the database. Implementors should call super if they
# override the default implementation.
@@ -301,7 +284,6 @@ module ActiveRecord
end
# Returns true if its required to reload the connection between requests for development mode.
- # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
false
end
@@ -340,6 +322,11 @@ module ActiveRecord
node
end
+ def case_sensitive_comparison(table, attribute, column, value)
+ value = case_sensitive_modifier(value) unless value.nil?
+ table[attribute].eq(value)
+ end
+
def case_insensitive_comparison(table, attribute, column, value)
table[attribute].lower.eq(table.lower(value))
end
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 23edc8b955..20eea208ec 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,18 +6,31 @@ module ActiveRecord
include Savepoints
class SchemaCreation < AbstractAdapter::SchemaCreation
-
def visit_AddColumn(o)
add_column_position!(super, column_options(o))
end
private
+
+ def visit_TableDefinition(o)
+ name = o.name
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
+
+ statements = o.columns.map { |c| accept c }
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
+
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
+ create_sql << "#{o.options}"
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
+ create_sql
+ end
+
def visit_ChangeColumnDefinition(o)
column = o.column
options = o.options
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
- add_column_options!(change_column_sql, options)
+ add_column_options!(change_column_sql, options.merge(column: column))
add_column_position!(change_column_sql, options)
end
@@ -29,6 +42,11 @@ module ActiveRecord
end
sql
end
+
+ def index_in_create(table_name, column_name, options)
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
end
def schema_creation
@@ -225,6 +243,10 @@ module ActiveRecord
version[0] >= 5
end
+ def supports_indexes_in_create?
+ true
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -459,7 +481,7 @@ module ActiveRecord
end
def bulk_change_table(table_name, operations) #:nodoc:
- sqls = operations.map do |command, args|
+ sqls = operations.flat_map do |command, args|
table, arguments = args.shift, args
method = :"#{command}_sql"
@@ -468,7 +490,7 @@ module ActiveRecord
else
raise "Unknown method called : #{method}(#{arguments.inspect})"
end
- end.flatten.join(", ")
+ end.join(", ")
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
end
@@ -592,6 +614,14 @@ module ActiveRecord
Arel::Nodes::Bin.new(node)
end
+ def case_sensitive_comparison(table, attribute, column, value)
+ if column.case_sensitive?
+ table[attribute].eq(value)
+ else
+ super
+ end
+ end
+
def case_insensitive_comparison(table, attribute, column, value)
if column.case_sensitive?
super
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index f2fbd5a8f2..187eefb9e4 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -87,7 +87,7 @@ module ActiveRecord
end
end
- # Casts value (which is a String) to an appropriate instance.
+ # Casts value to an appropriate instance.
def type_cast(value)
return nil if value.nil?
return coder.load(value) if encoded?
@@ -95,7 +95,13 @@ module ActiveRecord
klass = self.class
case type
- when :string, :text then value
+ when :string, :text
+ case value
+ when TrueClass; "1"
+ when FalseClass; "0"
+ else
+ value.to_s
+ end
when :integer then klass.value_to_integer(value)
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 3f8b14bf67..e0715f7ce9 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -35,7 +35,12 @@ module ActiveRecord
@uri = URI.parse(url)
@adapter = @uri.scheme
@adapter = "postgresql" if @adapter == "postgres"
- @query = @uri.query || ''
+
+ if @uri.opaque
+ @uri.opaque, @query = @uri.opaque.split('?', 2)
+ else
+ @query = @uri.query
+ end
end
# Converts the given URL to a full connection hash.
@@ -65,30 +70,38 @@ module ActiveRecord
# "localhost"
# # => {}
def query_hash
- Hash[@query.split("&").map { |pair| pair.split("=") }]
+ Hash[(@query || '').split("&").map { |pair| pair.split("=") }]
end
def raw_config
- query_hash.merge({
- "adapter" => @adapter,
- "username" => uri.user,
- "password" => uri.password,
- "port" => uri.port,
- "database" => database,
- "host" => uri.host })
+ if uri.opaque
+ query_hash.merge({
+ "adapter" => @adapter,
+ "database" => uri.opaque })
+ else
+ query_hash.merge({
+ "adapter" => @adapter,
+ "username" => uri.user,
+ "password" => uri.password,
+ "port" => uri.port,
+ "database" => database_from_path,
+ "host" => uri.host })
+ end
end
# Returns name of the database.
- # Sqlite3 expects this to be a full path or `:memory:`.
- def database
+ def database_from_path
if @adapter == 'sqlite3'
- if '/:memory:' == uri.path
- ':memory:'
- else
- uri.path
- end
+ # 'sqlite3:/foo' is absolute, because that makes sense. The
+ # corresponding relative version, 'sqlite3:foo', is handled
+ # elsewhere, as an "opaque".
+
+ uri.path
else
- uri.path.sub(%r{^/},"")
+ # Only SQLite uses a filename as the "database" name; for
+ # anything else, a leading slash would be silly.
+
+ uri.path.sub(%r{^/}, "")
end
end
end
@@ -124,7 +137,7 @@ module ActiveRecord
if config
resolve_connection config
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
- resolve_env_connection env.to_sym
+ resolve_symbol_connection env.to_sym
else
raise AdapterNotSpecified
end
@@ -193,42 +206,41 @@ module ActiveRecord
#
def resolve_connection(spec)
case spec
- when Symbol, String
- resolve_env_connection spec
+ when Symbol
+ resolve_symbol_connection spec
+ when String
+ resolve_string_connection spec
when Hash
resolve_hash_connection spec
end
end
+ def resolve_string_connection(spec)
+ # Rails has historically accepted a string to mean either
+ # an environment key or a URL spec, so we have deprecated
+ # this ambiguous behaviour and in the future this function
+ # can be removed in favor of resolve_url_connection.
+ if configurations.key?(spec)
+ ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
+ "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
+ resolve_connection(configurations[spec])
+ else
+ resolve_url_connection(spec)
+ end
+ end
+
# Takes the environment such as `:production` or `:development`.
# This requires that the @configurations was initialized with a key that
# matches.
#
- #
- # Resolver.new("production" => {}).resolve_env_connection(:production)
+ # Resolver.new("production" => {}).resolve_symbol_connection(:production)
# # => {}
#
- # Takes a connection URL.
- #
- # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo")
- # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
- #
- def resolve_env_connection(spec)
- # Rails has historically accepted a string to mean either
- # an environment key or a URL spec, so we have deprecated
- # this ambiguous behaviour and in the future this function
- # can be removed in favor of resolve_string_connection and
- # resolve_symbol_connection.
+ def resolve_symbol_connection(spec)
if config = configurations[spec.to_s]
- if spec.is_a?(String)
- ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
- "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
- end
resolve_connection(config)
- elsif spec.is_a?(String)
- resolve_string_connection(spec)
else
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
+ raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
end
end
@@ -237,14 +249,19 @@ module ActiveRecord
# hash and merges with the rest of the hash.
# Connection details inside of the "url" key win any merge conflicts
def resolve_hash_connection(spec)
- if url = spec.delete("url")
- connection_hash = resolve_string_connection(url)
+ if spec["url"] && spec["url"] !~ /^jdbc:/
+ connection_hash = resolve_string_connection(spec.delete("url"))
spec.merge!(connection_hash)
end
spec
end
- def resolve_string_connection(url)
+ # Takes a connection URL.
+ #
+ # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo")
+ # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
+ #
+ def resolve_url_connection(url)
ConnectionUrlResolver.new(url).to_hash
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index b07b0cb826..5e82fdcbe0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -20,9 +20,9 @@ module ActiveRecord
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
rescue Mysql2::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
@@ -83,6 +83,14 @@ module ActiveRecord
@connection.escape(string)
end
+ def quoted_date(value)
+ if value.acts_like?(:time) && value.respond_to?(:usec)
+ "#{super}.#{sprintf("%06d", value.usec)}"
+ else
+ super
+ end
+ end
+
# CONNECTION MANAGEMENT ====================================
def active?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 49f0bfbcde..e6aa2ba921 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -36,9 +36,9 @@ module ActiveRecord
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
rescue Mysql::Error => error
if error.message.include?("Unknown database")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
new file mode 100644
index 0000000000..2cbcd5fd50
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -0,0 +1,158 @@
+module ActiveRecord
+ module ConnectionAdapters
+ # PostgreSQL-specific extensions to column definitions in a table.
+ class PostgreSQLColumn < Column #:nodoc:
+ attr_accessor :array
+
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
+ @oid_type = oid_type
+ default_value = self.class.extract_value_from_default(default)
+
+ if sql_type =~ /\[\]$/
+ @array = true
+ super(name, default_value, sql_type[0..sql_type.length - 3], null)
+ else
+ @array = false
+ super(name, default_value, sql_type, null)
+ end
+
+ @default_function = default if has_default_function?(default_value, default)
+ end
+
+ def number?
+ !array && super
+ end
+
+ def text?
+ !array && super
+ end
+
+ # :stopdoc:
+ class << self
+ include ConnectionAdapters::PostgreSQLColumn::Cast
+ include ConnectionAdapters::PostgreSQLColumn::ArrayParser
+ attr_accessor :money_precision
+ end
+ # :startdoc:
+
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
+ case default
+ when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
+ $1
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
+ $1
+ # Character types
+ when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
+ $1.gsub(/''/, "'")
+ # Binary data types
+ when /\A'(.*)'::bytea\z/m
+ $1
+ # Date/time types
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
+ # Geometric types
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
+ # Network address types
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
+ # XML type
+ when /\A'(.*)'::xml\z/m
+ $1
+ # Arrays
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Hstore
+ when /\A'(.*)'::hstore\z/
+ $1
+ # JSON
+ when /\A'(.*)'::json\z/
+ $1
+ # Object identifier types
+ when /\A-?\d+\z/
+ $1
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
+ end
+ end
+
+ def type_cast_for_write(value)
+ if @oid_type.respond_to?(:type_cast_for_write)
+ @oid_type.type_cast_for_write(value)
+ else
+ super
+ end
+ end
+
+ def type_cast(value)
+ return if value.nil?
+ return super if encoded?
+
+ @oid_type.type_cast value
+ end
+
+ def accessor
+ @oid_type.accessor
+ end
+
+ private
+
+ def has_default_function?(default_value, default)
+ !default_value && (%r{\w+\(.*\)} === default)
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ when /^timestamp/i; nil
+ else super
+ end
+ end
+
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ if sql_type == 'money'
+ self.class.money_precision
+ elsif sql_type =~ /timestamp/i
+ $1.to_i if sql_type =~ /\((\d+)\)/
+ else
+ super
+ end
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ @oid_type.simplified_type(field_type) || super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index fae260a921..9e898015a6 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -6,6 +6,11 @@ module ActiveRecord
module OID
class Type
def type; end
+ def simplified_type(sql_type); type end
+
+ def infinity(options = {})
+ ::Float::INFINITY * (options[:negative] ? -1 : 1)
+ end
end
class Identity < Type
@@ -14,9 +19,33 @@ module ActiveRecord
end
end
+ class String < Type
+ def type; :string end
+
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_s
+ end
+ end
+
+ class SpecializedString < OID::String
+ def type; @type end
+
+ def initialize(type)
+ @type = type
+ end
+ end
+
+ class Text < OID::String
+ def type; :text end
+ end
+
class Bit < Type
+ def type; :string end
+
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_bit value
else
value
@@ -25,6 +54,8 @@ module ActiveRecord
end
class Bytea < Type
+ def type; :binary end
+
def type_cast(value)
return if value.nil?
PGconn.unescape_bytea value
@@ -32,9 +63,11 @@ module ActiveRecord
end
class Money < Type
+ def type; :decimal end
+
def type_cast(value)
return if value.nil?
- return value unless String === value
+ return value unless ::String === value
# Because money output is formatted according to the locale, there are two
# cases to consider (note the decimal separators):
@@ -76,8 +109,10 @@ module ActiveRecord
end
class Point < Type
+ def type; :string end
+
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_point value
else
value
@@ -86,13 +121,15 @@ module ActiveRecord
end
class Array < Type
+ def type; @subtype.type end
+
attr_reader :subtype
def initialize(subtype)
@subtype = subtype
end
def type_cast(value)
- if String === value
+ if ::String === value
ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype
else
value
@@ -102,6 +139,8 @@ module ActiveRecord
class Range < Type
attr_reader :subtype
+ def simplified_type(sql_type); sql_type.to_sym end
+
def initialize(subtype)
@subtype = subtype
end
@@ -109,23 +148,19 @@ module ActiveRecord
def extract_bounds(value)
from, to = value[1..-2].split(',')
{
- from: (value[1] == ',' || from == '-infinity') ? infinity(:negative => true) : from,
- to: (value[-2] == ',' || to == 'infinity') ? infinity : to,
+ from: (value[1] == ',' || from == '-infinity') ? @subtype.infinity(negative: true) : from,
+ to: (value[-2] == ',' || to == 'infinity') ? @subtype.infinity : to,
exclude_start: (value[0] == '('),
exclude_end: (value[-1] == ')')
}
end
- def infinity(options = {})
- ::Float::INFINITY * (options[:negative] ? -1 : 1)
- end
-
def infinity?(value)
value.respond_to?(:infinite?) && value.infinite?
end
- def to_integer(value)
- infinity?(value) ? value : value.to_i
+ def type_cast_single(value)
+ infinity?(value) ? value : @subtype.type_cast(value)
end
def type_cast(value)
@@ -133,32 +168,27 @@ module ActiveRecord
return value if value.is_a?(::Range)
extracted = extract_bounds(value)
-
- case @subtype
- when :date
- from = ConnectionAdapters::Column.value_to_date(extracted[:from])
- from -= 1.day if extracted[:exclude_start]
- to = ConnectionAdapters::Column.value_to_date(extracted[:to])
- when :decimal
- from = BigDecimal.new(extracted[:from].to_s)
- # FIXME: add exclude start for ::Range, same for timestamp ranges
- to = BigDecimal.new(extracted[:to].to_s)
- when :time
- from = ConnectionAdapters::Column.string_to_time(extracted[:from])
- to = ConnectionAdapters::Column.string_to_time(extracted[:to])
- when :integer
- from = to_integer(extracted[:from]) rescue value ? 1 : 0
- from -= 1 if extracted[:exclude_start]
- to = to_integer(extracted[:to]) rescue value ? 1 : 0
- else
- return value
+ from = type_cast_single extracted[:from]
+ to = type_cast_single extracted[:to]
+
+ if !infinity?(from) && extracted[:exclude_start]
+ if from.respond_to?(:succ)
+ from = from.succ
+ ActiveSupport::Deprecation.warn <<-MESSAGE
+Excluding the beginning of a Range is only partialy supported through `#succ`.
+This is not reliable and will be removed in the future.
+ MESSAGE
+ else
+ raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
+ end
end
-
::Range.new(from, to, extracted[:exclude_end])
end
end
class Integer < Type
+ def type; :integer end
+
def type_cast(value)
return if value.nil?
@@ -167,6 +197,8 @@ module ActiveRecord
end
class Boolean < Type
+ def type; :boolean end
+
def type_cast(value)
return if value.nil?
@@ -176,6 +208,14 @@ module ActiveRecord
class Timestamp < Type
def type; :timestamp; end
+ def simplified_type(sql_type)
+ case sql_type
+ when /^timestamp with(?:out)? time zone$/
+ :datetime
+ else
+ :timestamp
+ end
+ end
def type_cast(value)
return if value.nil?
@@ -187,7 +227,7 @@ module ActiveRecord
end
class Date < Type
- def type; :datetime; end
+ def type; :date; end
def type_cast(value)
return if value.nil?
@@ -199,6 +239,8 @@ module ActiveRecord
end
class Time < Type
+ def type; :time end
+
def type_cast(value)
return if value.nil?
@@ -209,6 +251,8 @@ module ActiveRecord
end
class Float < Type
+ def type; :float end
+
def type_cast(value)
return if value.nil?
@@ -217,14 +261,30 @@ module ActiveRecord
end
class Decimal < Type
+ def type; :decimal end
+
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_decimal value
end
+
+ def infinity(options = {})
+ BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ end
+ end
+
+ class Enum < Type
+ def type; :enum end
+
+ def type_cast(value)
+ value.to_s
+ end
end
class Hstore < Type
+ def type; :hstore end
+
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.hstore_to_string value
end
@@ -241,14 +301,20 @@ module ActiveRecord
end
class Cidr < Type
+ def type; :cidr end
def type_cast(value)
return if value.nil?
ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
end
end
+ class Inet < Cidr
+ def type; :inet end
+ end
class Json < Type
+ def type; :json end
+
def type_cast_for_write(value)
ConnectionAdapters::PostgreSQLColumn.json_to_string value
end
@@ -264,6 +330,13 @@ module ActiveRecord
end
end
+ class Uuid < Type
+ def type; :uuid end
+ def type_cast(value)
+ value.presence
+ end
+ end
+
class TypeMap
def initialize
@mapping = {}
@@ -310,7 +383,7 @@ module ActiveRecord
}
# Register an OID type named +name+ with a typecasting object in
- # +type+. +name+ should correspond to the `typname` column in
+ # +type+. +name+ should correspond to the `typname` column in
# the `pg_type` table.
def self.register_type(name, type)
NAMES[name] = type
@@ -327,54 +400,46 @@ module ActiveRecord
end
register_type 'int2', OID::Integer.new
- alias_type 'int4', 'int2'
- alias_type 'int8', 'int2'
- alias_type 'oid', 'int2'
-
- register_type 'daterange', OID::Range.new(:date)
- register_type 'numrange', OID::Range.new(:decimal)
- register_type 'tsrange', OID::Range.new(:time)
- register_type 'int4range', OID::Range.new(:integer)
- alias_type 'tstzrange', 'tsrange'
- alias_type 'int8range', 'int4range'
-
+ alias_type 'int4', 'int2'
+ alias_type 'int8', 'int2'
+ alias_type 'oid', 'int2'
register_type 'numeric', OID::Decimal.new
- register_type 'text', OID::Identity.new
- alias_type 'varchar', 'text'
- alias_type 'char', 'text'
- alias_type 'bpchar', 'text'
- alias_type 'xml', 'text'
-
- # FIXME: why are we keeping these types as strings?
- alias_type 'tsvector', 'text'
- alias_type 'interval', 'text'
- alias_type 'macaddr', 'text'
- alias_type 'uuid', 'text'
-
- register_type 'money', OID::Money.new
- register_type 'bytea', OID::Bytea.new
- register_type 'bool', OID::Boolean.new
- register_type 'bit', OID::Bit.new
- register_type 'varbit', OID::Bit.new
-
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
-
+ register_type 'text', OID::Text.new
+ register_type 'varchar', OID::String.new
+ alias_type 'char', 'varchar'
+ alias_type 'bpchar', 'varchar'
+ register_type 'bool', OID::Boolean.new
+ register_type 'bit', OID::Bit.new
+ alias_type 'varbit', 'bit'
register_type 'timestamp', OID::Timestamp.new
- register_type 'timestamptz', OID::Timestamp.new
+ alias_type 'timestamptz', 'timestamp'
register_type 'date', OID::Date.new
register_type 'time', OID::Time.new
- register_type 'path', OID::Identity.new
+ register_type 'money', OID::Money.new
+ register_type 'bytea', OID::Bytea.new
register_type 'point', OID::Point.new
- register_type 'polygon', OID::Identity.new
- register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
register_type 'json', OID::Json.new
- register_type 'ltree', OID::Identity.new
-
register_type 'cidr', OID::Cidr.new
- alias_type 'inet', 'cidr'
+ register_type 'inet', OID::Inet.new
+ register_type 'uuid', OID::Uuid.new
+ register_type 'xml', SpecializedString.new(:xml)
+ register_type 'tsvector', SpecializedString.new(:tsvector)
+ register_type 'macaddr', SpecializedString.new(:macaddr)
+ register_type 'citext', SpecializedString.new(:citext)
+ register_type 'ltree', SpecializedString.new(:ltree)
+
+ # FIXME: why are we keeping these types as strings?
+ alias_type 'interval', 'varchar'
+ alias_type 'path', 'varchar'
+ alias_type 'line', 'varchar'
+ alias_type 'polygon', 'varchar'
+ alias_type 'circle', 'varchar'
+ alias_type 'lseg', 'varchar'
+ alias_type 'box', 'varchar'
end
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 ae8ede4b42..50a73aa666 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -104,14 +104,11 @@ module ActiveRecord
schema, table = Utils.extract_schema_and_table(name.to_s)
return false unless table
- binds = [[nil, table]]
- binds << [nil, schema] if schema
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
SELECT COUNT(*)
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind in ('v','r')
+ WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
AND c.relname = '#{table.gsub(/(^"|"$)/,'')}'
AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
SQL
@@ -327,6 +324,7 @@ module ActiveRecord
AND attr.attrelid = cons.conrelid
AND attr.attnum = cons.conkey[1]
AND cons.contype = 'p'
+ AND dep.classid = 'pg_class'::regclass
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 36c7462419..9fe8e0497e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -7,6 +7,7 @@ require 'active_record/connection_adapters/postgresql/quoting'
require 'active_record/connection_adapters/postgresql/schema_statements'
require 'active_record/connection_adapters/postgresql/database_statements'
require 'active_record/connection_adapters/postgresql/referential_integrity'
+require 'active_record/connection_adapters/postgresql/column'
require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
@@ -43,222 +44,6 @@ module ActiveRecord
end
module ConnectionAdapters
- # PostgreSQL-specific extensions to column definitions in a table.
- class PostgreSQLColumn < Column #:nodoc:
- attr_accessor :array
-
- def initialize(name, default, oid_type, sql_type = nil, null = true)
- @oid_type = oid_type
- default_value = self.class.extract_value_from_default(default)
-
- if sql_type =~ /\[\]$/
- @array = true
- super(name, default_value, sql_type[0..sql_type.length - 3], null)
- else
- @array = false
- super(name, default_value, sql_type, null)
- end
-
- @default_function = default if has_default_function?(default_value, default)
- end
-
- def number?
- !array && super
- end
-
- def text?
- !array && super
- end
-
- # :stopdoc:
- class << self
- include ConnectionAdapters::PostgreSQLColumn::Cast
- include ConnectionAdapters::PostgreSQLColumn::ArrayParser
- attr_accessor :money_precision
- end
- # :startdoc:
-
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- return default unless default
-
- case default
- when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
- $1
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
- $1
- # Character types
- when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
- $1.gsub(/''/, "'")
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Hstore
- when /\A'(.*)'::hstore\z/
- $1
- # JSON
- when /\A'(.*)'::json\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- end
- end
-
- def type_cast_for_write(value)
- if @oid_type.respond_to?(:type_cast_for_write)
- @oid_type.type_cast_for_write(value)
- else
- super
- end
- end
-
- def type_cast(value)
- return if value.nil?
- return super if encoded?
-
- @oid_type.type_cast value
- end
-
- def accessor
- @oid_type.accessor
- end
-
- private
-
- def has_default_function?(default_value, default)
- !default_value && (%r{\w+\(.*\)} === default)
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- when /^timestamp/i; nil
- else super
- end
- end
-
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
- end
-
- # Extracts the precision from PostgreSQL-specific data types.
- def extract_precision(sql_type)
- if sql_type == 'money'
- self.class.money_precision
- elsif sql_type =~ /timestamp/i
- $1.to_i if sql_type =~ /\((\d+)\)/
- else
- super
- end
- end
-
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- case field_type
- # Numeric and monetary types
- when /^(?:real|double precision)$/
- :float
- # Monetary types
- when 'money'
- :decimal
- when 'hstore'
- :hstore
- when 'ltree'
- :ltree
- # Network address types
- when 'inet'
- :inet
- when 'cidr'
- :cidr
- when 'macaddr'
- :macaddr
- # Character types
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
- :string
- # Binary data types
- when 'bytea'
- :binary
- # Date/time types
- when /^timestamp with(?:out)? time zone$/
- :datetime
- when /^interval(?:|\(\d+\))$/
- :string
- # Geometric types
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
- :string
- # Bit strings
- when /^bit(?: varying)?(?:\(\d+\))?$/
- :string
- # XML type
- when 'xml'
- :xml
- # tsvector type
- when 'tsvector'
- :tsvector
- # Arrays
- when /^\D+\[\]$/
- :string
- # Object identifier types
- when 'oid'
- :integer
- # UUID type
- when 'uuid'
- :uuid
- # JSON type
- when 'json'
- :json
- # Small and big integer types
- when /^(?:small|big)int$/
- :integer
- when /(num|date|tstz|ts|int4|int8)range$/
- field_type.to_sym
- # Pass through all types that are not specific to PostgreSQL.
- else
- super
- end
- end
- end
-
# The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
#
# Options:
@@ -353,6 +138,10 @@ module ActiveRecord
def json(name, options = {})
column(name, 'json', options)
end
+
+ def citext(name, options = {})
+ column(name, 'citext', options)
+ end
end
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
@@ -393,6 +182,10 @@ module ActiveRecord
column name, type, options
end
+ def citext(name, options = {})
+ column(name, 'citext', options)
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -441,7 +234,8 @@ module ActiveRecord
macaddr: { name: "macaddr" },
uuid: { name: "uuid" },
json: { name: "json" },
- ltree: { name: "ltree" }
+ ltree: { name: "ltree" },
+ citext: { name: "citext" }
}
include Quoting
@@ -592,10 +386,6 @@ module ActiveRecord
false
end
- def active_threadsafe?
- @connection.connect_poll != PG::PGRES_POLLING_FAILED
- end
-
# Close then reopen the connection.
def reconnect!
super
@@ -605,7 +395,12 @@ module ActiveRecord
def reset!
clear_cache!
- super
+ reset_transaction
+ unless @connection.transaction_status == ::PG::PQTRANS_IDLE
+ @connection.query 'ROLLBACK'
+ end
+ @connection.query 'DISCARD ALL'
+ configure_connection
end
# Disconnects from the database if already connected. Otherwise, this
@@ -659,6 +454,10 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_materialized_views?
+ postgresql_version >= 90300
+ end
+
def enable_extension(name)
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
reload_type_map
@@ -785,18 +584,36 @@ module ActiveRecord
end
def initialize_type_map(type_map)
- result = execute('SELECT oid, typname, typelem, typdelim, typinput FROM pg_type', 'SCHEMA')
- leaves, nodes = result.partition { |row| row['typelem'] == '0' }
+ if supports_ranges?
+ result = execute(<<-SQL, 'SCHEMA')
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
+ FROM pg_type as t
+ LEFT JOIN pg_range as r ON oid = rngtypid
+ SQL
+ else
+ result = execute(<<-SQL, 'SCHEMA')
+ SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype
+ FROM pg_type as t
+ SQL
+ end
+ ranges, nodes = result.partition { |row| row['typtype'] == 'r' }
+ enums, nodes = nodes.partition { |row| row['typtype'] == 'e' }
+ domains, nodes = nodes.partition { |row| row['typtype'] == 'd' }
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
+ leaves, nodes = nodes.partition { |row| row['typelem'] == '0' }
+
+ # populate the enum types
+ enums.each do |row|
+ type_map[row['oid'].to_i] = OID::Enum.new
+ end
- # populate the leaf nodes
+ # populate the base types
leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
type_map[row['oid'].to_i] = OID::NAMES[row['typname']]
end
records_by_oid = result.group_by { |row| row['oid'] }
- arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
-
# populate composite types
nodes.each do |row|
add_oid row, records_by_oid, type_map
@@ -807,6 +624,23 @@ module ActiveRecord
array = OID::Array.new type_map[row['typelem'].to_i]
type_map[row['oid'].to_i] = array
end
+
+ # populate range types
+ ranges.find_all { |row| type_map.key? row['rngsubtype'].to_i }.each do |row|
+ subtype = type_map[row['rngsubtype'].to_i]
+ range = OID::Range.new subtype
+ type_map[row['oid'].to_i] = range
+ end
+
+ # populate domain types
+ domains.each do |row|
+ base_type_oid = row["typbasetype"].to_i
+ if base_type = type_map[base_type_oid]
+ type_map[row['oid'].to_i] = base_type
+ else
+ warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}."
+ end
+ end
end
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
@@ -888,9 +722,9 @@ module ActiveRecord
configure_connection
rescue ::PG::Error => error
if error.message.include?("does not exist")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 3c5f7a981e..6e6a51dab8 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -33,9 +33,9 @@ module ActiveRecord
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
- raise ActiveRecord::NoDatabaseError.new(error.message)
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
else
- raise error
+ raise
end
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 11f6a47158..bbb866cedf 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -18,14 +18,14 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
+ # adapter: "sqlite3",
# database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
+ # "adapter" => "sqlite3",
# "database" => "path/to/dbfile"
# )
#
@@ -93,16 +93,12 @@ module ActiveRecord
# the connection URL. This hash responds to any string key with
# resolved connection information.
def default_url_hash
- if @raw_config.blank?
- Hash.new do |hash, key|
- hash[key] = if key.is_a? String
- ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
- else
- nil
- end
+ Hash.new do |hash, key|
+ hash[key] = if key.is_a? String
+ ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash
+ else
+ nil
end
- else
- {}
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index d9aaf8597f..4e53f66005 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -299,7 +299,7 @@ module ActiveRecord
def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
- id &&
+ !id.nil? &&
comparison_object.id == id
end
alias :eql? :==
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 7f6228131f..71efbb8f93 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -95,15 +95,7 @@ module ActiveRecord
end
# Raised when a given database does not exist
- class NoDatabaseError < ActiveRecordError
- def initialize(message)
- super extend_message(message)
- end
-
- # can be over written to add additional error information.
- def extend_message(message)
- message
- end
+ class NoDatabaseError < StatementInvalid
end
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 297792aeec..6f134bbef8 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -361,6 +361,7 @@ module ActiveRecord
# geeksomnia:
# name: Geeksomnia's Account
# subdomain: $LABEL
+ # email: $LABEL@email.com
#
# Also, sometimes (like when porting older join table fixtures) you'll need
# to be able to get a hold of the identifier for a given label. ERB
@@ -549,7 +550,7 @@ module ActiveRecord
end
# Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
+ # Identifiers are positive integers less than 2^30.
def self.identify(label)
Zlib.crc32(label.to_s) % MAX_ID
end
@@ -627,7 +628,7 @@ module ActiveRecord
# interpolate the fixture label
row.each do |key, value|
- row[key] = label if "$LABEL" == value
+ row[key] = value.gsub("$LABEL", label) if value.is_a?(String)
end
# generate a primary key if necessary
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
new file mode 100644
index 0000000000..4a7aace460
--- /dev/null
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 4
+ MINOR = 2
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 063b1d1bd0..13d7432773 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -64,7 +64,7 @@ module ActiveRecord
end
# Returns true if this object hasn't been saved yet -- that is, a record
- # for the object doesn't exist in the data store yet; otherwise, returns false.
+ # for the object doesn't exist in the database yet; otherwise, returns false.
def new_record?
sync_with_transaction_state
@new_record
@@ -214,6 +214,8 @@ module ActiveRecord
#
# This method raises an +ActiveRecord::ActiveRecordError+ if the
# attribute is marked as readonly.
+ #
+ # See also +update_column+.
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
@@ -403,15 +405,18 @@ module ActiveRecord
end
# Saves the record with the updated_at/on attributes set to the current time.
- # Please note that no validation is performed and only the +after_touch+
- # callback is executed.
- # If an attribute name is passed, that attribute is updated along with
- # updated_at/on attributes.
+ # Please note that no validation is performed and only the +after_touch+,
+ # +after_commit+ and +after_rollback+ callbacks are executed.
+ #
+ # If attribute names are passed, they are updated along with updated_at/on
+ # attributes.
#
- # product.touch # updates updated_at/on
- # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
#
- # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object.
+ # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on
+ # associated object.
#
# class Brake < ActiveRecord::Base
# belongs_to :car, touch: true
@@ -430,11 +435,11 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(name = nil)
+ def touch(*names)
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
attributes = timestamp_attributes_for_update_in_model
- attributes << name if name
+ attributes.concat(names)
unless attributes.empty?
current_time = current_time_from_proper_timezone
@@ -450,6 +455,8 @@ module ActiveRecord
changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
+ else
+ true
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 11b564f8f9..a4ceacbf44 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -116,17 +116,22 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
+ self.configurations = Rails.application.config.database_configuration
- class ActiveRecord::NoDatabaseError
- remove_possible_method :extend_message
- def extend_message(message)
- message << "Run `$ bin/rake db:create db:migrate` to create your database"
- message
- end
- end
+ begin
+ establish_connection
+ rescue ActiveRecord::NoDatabaseError
+ warn <<-end_warning
+Oops - You have a database configured, but it doesn't exist yet!
- self.configurations = Rails.application.config.database_configuration
- establish_connection
+Here's how to get started:
+
+ 1. Configure your database in config/database.yml.
+ 2. Run `bin/rake db:create` to create the database.
+ 3. Run `bin/rake db:setup` to load your database schema.
+end_warning
+ raise
+ end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1d5c80bc01..6b0459ea37 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -186,7 +186,7 @@ db_namespace = namespace :db do
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(',') : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -268,7 +268,8 @@ db_namespace = namespace :db do
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- if ActiveRecord::Base.connection.supports_migrations?
+ if ActiveRecord::Base.connection.supports_migrations? &&
+ ActiveRecord::SchemaMigration.table_exists?
File.open(filename, "a") do |f|
f.puts ActiveRecord::Base.connection.dump_schema_information
f.print "\n"
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index bce7766501..03b5bdc46c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -151,7 +151,7 @@ module ActiveRecord
super ||
other_aggregation.kind_of?(self.class) &&
name == other_aggregation.name &&
- other_aggregation.options &&
+ !other_aggregation.options.nil? &&
active_record == other_aggregation.active_record
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 447042254d..4d37ac6e2b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -617,7 +617,9 @@ module ActiveRecord
def references_eager_loaded_tables?
joined_tables = arel.join_sources.map do |join|
- unless join.is_a?(Arel::Nodes::StringJoin)
+ if join.is_a?(Arel::Nodes::StringJoin)
+ tables_in_string(join.left)
+ else
[join.left.table_name, join.left.table_alias]
end
end
@@ -629,5 +631,12 @@ module ActiveRecord
(references_values - joined_tables).any?
end
+
+ def tables_in_string(string)
+ return [] if string.blank?
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
+ string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7099bdd285..c2b9dc08fe 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation'
+
module ActiveRecord
module FinderMethods
ONE_AS_ONE = '1 AS one'
@@ -280,7 +282,12 @@ module ActiveRecord
# Person.exists?(false)
# Person.exists?
def exists?(conditions = :none)
- conditions = conditions.id if Base === conditions
+ if Base === conditions
+ conditions = conditions.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `exists?`." \
+ "Please pass the id of the object by calling `.id`"
+ end
+
return false if !conditions
relation = apply_join_dependency(self, construct_join_dependency)
@@ -292,7 +299,12 @@ module ActiveRecord
when Array, Hash
relation = relation.where(conditions)
else
- relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
+ if conditions != :none
+ column = columns_hash[primary_key]
+ substitute = connection.substitute_at(column, bind_values.length)
+ relation = where(table[primary_key].eq(substitute))
+ relation.bind_values += [[column, conditions]]
+ end
end
connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
@@ -409,7 +421,11 @@ module ActiveRecord
end
def find_one(id)
- id = id.id if ActiveRecord::Base === id
+ if ActiveRecord::Base === id
+ id = id.id
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
+ "Please pass the id of the object by calling `.id`"
+ end
column = columns_hash[primary_key]
substitute = connection.substitute_at(column, bind_values.length)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 5d38f0dce8..0213bca981 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -49,6 +49,8 @@ module ActiveRecord
Arel::Nodes::Not.new(rel)
end
end
+
+ @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
@scope.where_values += where_value
@scope
end
@@ -168,7 +170,7 @@ module ActiveRecord
# Use to indicate that the given +table_names+ are referenced by an SQL string,
# and should therefore be JOINed in any query rather than loaded separately.
- # This method only works in conjuction with +includes+.
+ # This method only works in conjunction with +includes+.
# See #includes for more details.
#
# User.includes(:posts).where("posts.name = 'foo'")
@@ -202,7 +204,7 @@ module ActiveRecord
# fields are retrieved:
#
# Model.select(:field)
- # # => [#<Model field:value>]
+ # # => [#<Model id: nil, field: "value">]
#
# Although in the above example it looks as though this method returns an
# array, it actually returns a relation object and can have other query
@@ -211,12 +213,12 @@ module ActiveRecord
# The argument to the method can also be an array of fields.
#
# Model.select(:field, :other_field, :and_one_more)
- # # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
+ # # => [#<Model id: nil, field: "value", other_field: "value", and_one_more: "value">]
#
# You can also use one or more strings, which will be used unchanged as SELECT fields.
#
# Model.select('field AS field_one', 'other_field AS field_two')
- # # => [#<Model field: "value", other_field: "value">]
+ # # => [#<Model id: nil, field: "value", other_field: "value">]
#
# If an alias was specified, it will be accessible from the resulting objects:
#
@@ -224,7 +226,7 @@ module ActiveRecord
# # => "value"
#
# Accessing attributes of an object that do not have fields retrieved by a select
- # will throw <tt>ActiveModel::MissingAttributeError</tt>:
+ # except +id+ will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
# Model.select(:field).first.other_field
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
@@ -261,6 +263,10 @@ module ActiveRecord
#
# User.group('name AS grouped_name, age')
# => [#<User id: 3, name: "Foo", age: 21, ...>, #<User id: 2, name: "Oscar", age: 21, ...>, #<User id: 5, name: "Foo", age: 23, ...>]
+ #
+ # Passing in an array of attributes to group by is also supported.
+ # User.select([:id, :first_name]).group(:id, :first_name).first(3)
+ # => [#<User id: 1, first_name: "Bill">, #<User id: 2, first_name: "Earl">, #<User id: 3, first_name: "Beto">]
def group(*args)
check_if_method_has_arguments!(:group, args)
spawn.group!(*args)
@@ -275,15 +281,6 @@ module ActiveRecord
# Allows to specify an order attribute:
#
- # User.order('name')
- # => SELECT "users".* FROM "users" ORDER BY name
- #
- # User.order('name DESC')
- # => SELECT "users".* FROM "users" ORDER BY name DESC
- #
- # User.order('name DESC, email')
- # => SELECT "users".* FROM "users" ORDER BY name DESC, email
- #
# User.order(:name)
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC
#
@@ -292,6 +289,15 @@ module ActiveRecord
#
# User.order(:name, email: :desc)
# => SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
+ #
+ # User.order('name')
+ # => SELECT "users".* FROM "users" ORDER BY name
+ #
+ # User.order('name DESC')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC
+ #
+ # User.order('name DESC, email')
+ # => SELECT "users".* FROM "users" ORDER BY name DESC, email
def order(*args)
check_if_method_has_arguments!(:order, args)
spawn.order!(*args)
@@ -1030,10 +1036,15 @@ module ActiveRecord
arel.order(*orders) unless orders.empty?
end
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC,
+ 'asc', 'desc', 'ASC', 'DESC'] # :nodoc:
+
def validate_order_args(args)
- args.grep(Hash) do |h|
- unless (h.values - [:asc, :desc]).empty?
- raise ArgumentError, 'Direction should be :asc or :desc'
+ args.each do |arg|
+ next unless arg.is_a?(Hash)
+ arg.each do |_key, value|
+ raise ArgumentError, "Direction \"#{value}\" is invalid. Valid " \
+ "directions are: #{VALID_DIRECTIONS.inspect}" unless VALID_DIRECTIONS.include?(value)
end
end
end
@@ -1055,7 +1066,7 @@ module ActiveRecord
when Hash
arg.map { |field, dir|
field = klass.attribute_alias(field) if klass.attribute_alias?(field)
- table[field].send(dir)
+ table[field].send(dir.downcase)
}
else
arg
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 01fec31544..18190cb535 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end
module ClassMethods
- # Returns a scope for the model without the +default_scope+.
+ # Returns a scope for the model without the previously set scopes.
#
# class Post < ActiveRecord::Base
# def self.default_scope
@@ -19,11 +19,12 @@ module ActiveRecord
# end
# end
#
- # Post.all # Fires "SELECT * FROM posts WHERE published = true"
- # Post.unscoped.all # Fires "SELECT * FROM posts"
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
#
# This method also accepts a block. All queries inside the block will
- # not use the +default_scope+:
+ # not use the previously set scopes.
#
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
@@ -93,14 +94,14 @@ module ActiveRecord
self.default_scopes += [scope]
end
- def build_default_scope # :nodoc:
+ def build_default_scope(base_rel = relation) # :nodoc:
if !Base.is_a?(method(:default_scope).owner)
# The user has defined their own default scope method, so call that
evaluate_default_scope { default_scope }
elsif default_scopes.any?
evaluate_default_scope do
- default_scopes.inject(relation) do |default_scope, scope|
- default_scope.merge(unscoped { scope.call })
+ default_scopes.inject(base_rel) do |default_scope, scope|
+ default_scope.merge(base_rel.scoping { scope.call })
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index ec3e8f281b..17f76b63b3 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -369,7 +369,7 @@ module ActiveRecord
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state.has_key?(:id)
- self.id = restore_state[:id]
+ write_attribute(self.class.primary_key, restore_state[:id])
else
@attributes.delete(self.class.primary_key)
@attributes_cache.delete(self.class.primary_key)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 26dca415ff..9999624fcf 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -60,6 +60,8 @@ module ActiveRecord
# Runs all the validations within the specified context. Returns +true+ if
# no errors are found, +false+ otherwise.
#
+ # Aliased as validate.
+ #
# If the argument is +false+ (default is +nil+), the context is set to <tt>:create</tt> if
# <tt>new_record?</tt> is +true+, and to <tt>:update</tt> if it is not.
#
@@ -71,6 +73,8 @@ module ActiveRecord
errors.empty? && output
end
+ alias_method :validate, :valid?
+
protected
def perform_validations(options={}) # :nodoc:
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 7ebe9dfec0..71c71cb4b1 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -13,6 +13,7 @@ module ActiveRecord
def validate_each(record, attribute, value)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
+ value = map_enum_attribute(finder_class, attribute, value)
value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
@@ -67,8 +68,7 @@ module ActiveRecord
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- value = klass.connection.case_sensitive_modifier(value) unless value.nil?
- table[attribute].eq(value)
+ klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
end
@@ -91,6 +91,12 @@ module ActiveRecord
value = coder.dump value if value && coder
value
end
+
+ def map_enum_attribute(klass, attribute, value)
+ mapping = klass.enum_mapping_for(attribute.to_s)
+ value = mapping[value] if value && mapping
+ value
+ end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 7795561e51..cf76a13b44 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveRecord
- # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ # Returns the version of the currently loaded ActiveRecord as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta2"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
- STRING = ActiveRecord.version.to_s
+ gem_version
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 3968acba64..d3c853cfea 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -23,16 +23,16 @@ module ActiveRecord
case file_name
when /^(add|remove)_.*_(?:to|from)_(.*)/
@migration_action = $1
- @table_name = $2.pluralize
+ @table_name = normalize_table_name($2)
when /join_table/
if attributes.length == 2
@migration_action = 'join'
- @join_tables = attributes.map(&:plural_name)
+ @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name)
set_index_names
end
when /^create_(.+)/
- @table_name = $1.pluralize
+ @table_name = normalize_table_name($1)
@migration_template = "create_table_migration.rb"
end
end
@@ -61,6 +61,10 @@ module ActiveRecord
raise IllegalMigrationNameError.new(file_name)
end
end
+
+ def normalize_table_name(_table_name)
+ pluralize_table_names? ? _table_name.pluralize : _table_name.singularize
+ end
end
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0eb1231c79..90953ce6cd 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -144,9 +144,9 @@ module ActiveRecord
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
end
end
-
- def test_foreign_key_violations_are_translated_to_specific_exception
- unless @connection.adapter_name == 'SQLite'
+
+ unless current_adapter?(:SQLite3Adapter)
+ def test_foreign_key_violations_are_translated_to_specific_exception
assert_raises(ActiveRecord::InvalidForeignKey) do
# Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
if @connection.prefetch_primary_key?
@@ -157,6 +157,18 @@ module ActiveRecord
end
end
end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
+ klass_has_fk = Class.new(ActiveRecord::Base) do
+ self.table_name = 'fk_test_has_fk'
+ end
+
+ assert_raises(ActiveRecord::InvalidForeignKey) do
+ has_fk = klass_has_fk.new
+ has_fk.fk_id = 1231231231
+ has_fk.save(validate: false)
+ end
+ end
end
def test_disable_referential_integrity
@@ -218,7 +230,7 @@ module ActiveRecord
@connection = Klass.connection
end
- def teardown
+ teardown do
Klass.remove_connection
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 0878925a6c..7c0f11b033 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -1,23 +1,23 @@
require "cases/helper"
+require 'support/connection_helper'
class ActiveSchemaTest < ActiveRecord::TestCase
- def setup
- @connection = ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ include ConnectionHelper
+ def setup
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
def execute(sql, name = nil) return sql end
end
end
- def teardown
- ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ teardown do
+ reset_connection
end
def test_add_index
- # add_index calls index_name_exists? which can't work since execute is stubbed
+ # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
+ def test_indexes_in_create
+ ActiveRecord::Base.connection.stubs(:table_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"
+ 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
+
+ assert_equal expected, actual
+ end
+
private
def with_real_execute
ActiveRecord::Base.connection.singleton_class.class_eval do
diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
index 97adb6b297..340fc95503 100644
--- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb
@@ -3,10 +3,10 @@ require 'models/person'
class MysqlCaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
- validates_uniqueness_of :string_cs_column, :case_sensitive => false
- validates_uniqueness_of :string_ci_column, :case_sensitive => false
end
+ repair_validations(CollationTest)
+
def test_columns_include_collation_different_from_table
assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
@@ -18,6 +18,7 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
CollationTest.create!(:string_ci_column => 'A')
invalid = CollationTest.new(:string_ci_column => 'a')
queries = assert_sql { invalid.save }
@@ -26,10 +27,29 @@ class MysqlCaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
CollationTest.create!(:string_cs_column => 'A')
invalid = CollationTest.new(:string_cs_column => 'a')
queries = assert_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
assert_match(/lower/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'A')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_match(/binary/i, ci_uniqueness_query)
+ end
+
+ def test_case_sensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'A')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
+ assert_no_match(/binary/i, cs_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 5cd5d8ac5f..412efa22ff 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -1,6 +1,11 @@
require "cases/helper"
+require 'support/connection_helper'
+require 'support/ddl_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+ include DdlHelper
+
class Klass < ActiveRecord::Base
end
@@ -69,59 +74,50 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_exec_no_binds
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
+ with_example_table do
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- result = @connection.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
+ # if there are no bind parameters, it will return a string (due to
+ # the libmysql api)
+ result = @connection.exec_query('SELECT id, data FROM ex')
+ 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
def test_exec_with_binds
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ with_example_table do
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
- 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
def test_exec_typecasts_bind_vals
- @connection.exec_query('drop table if exists ex')
- @connection.exec_query(<<-eosql)
- CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY,
- `data` varchar(255))
- eosql
- @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @connection.columns('ex').find { |col| col.name == 'id' }
+ with_example_table do
+ @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
- result = @connection.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ result = @connection.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
- 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
# Test that MySQL allows multiple results for stored procedures
@@ -166,12 +162,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase
private
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
- end
+ def with_example_table(&block)
+ definition ||= <<-SQL
+ `id` int(11) auto_increment PRIMARY KEY,
+ `data` varchar(255)
+ SQL
+ super(@connection, 'ex', definition, &block)
end
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 578f6301bd..1699380eb3 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -1,19 +1,15 @@
# encoding: utf-8
require "cases/helper"
+require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
class MysqlAdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+
def setup
@conn = ActiveRecord::Base.connection
- @conn.exec_query('drop table if exists ex')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex` (
- `id` int(11) auto_increment PRIMARY KEY,
- `number` integer,
- `data` varchar(255))
- eosql
end
def test_bad_connection_mysql
@@ -25,8 +21,10 @@ module ActiveRecord
end
def test_valid_column
- column = @conn.columns('ex').find { |col| col.name == 'id' }
- assert @conn.valid_type?(column.type)
+ 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
@@ -38,31 +36,35 @@ module ActiveRecord
end
def test_exec_insert_number
- insert(@conn, 'number' => 10)
+ with_example_table do
+ insert(@conn, 'number' => 10)
- result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
+ result = @conn.exec_query('SELECT number FROM ex WHERE number = 10')
- assert_equal 1, result.rows.length
- # if there are no bind parameters, it will return a string (due to
- # the libmysql api)
- assert_equal '10', result.rows.last.last
+ assert_equal 1, result.rows.length
+ # if there are no bind parameters, it will return a string (due to
+ # the libmysql api)
+ assert_equal '10', result.rows.last.last
+ end
end
def test_exec_insert_string
- str = 'いただきます!'
- insert(@conn, 'number' => 10, 'data' => str)
+ with_example_table do
+ str = 'いただきます!'
+ insert(@conn, 'number' => 10, 'data' => str)
- result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
+ result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10')
- value = result.rows.last.last
+ value = result.rows.last.last
- # FIXME: this should probably be inside the mysql AR adapter?
- value.force_encoding(@conn.client_encoding)
+ # FIXME: this should probably be inside the mysql AR adapter?
+ value.force_encoding(@conn.client_encoding)
- # The strings in this file are utf-8, so transcode to utf-8
- value.encode!(Encoding::UTF_8)
+ # The strings in this file are utf-8, so transcode to utf-8
+ value.encode!(Encoding::UTF_8)
- assert_equal str, value
+ assert_equal str, value
+ end
end
def test_tables_quoting
@@ -74,46 +76,37 @@ module ActiveRecord
end
def test_pk_and_sequence_for
- pk, seq = @conn.pk_and_sequence_for('ex')
- assert_equal 'id', pk
- assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ with_example_table do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
end
def test_pk_and_sequence_for_with_non_standard_primary_key
- @conn.exec_query('drop table if exists ex_with_non_standard_pk')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_non_standard_pk` (
- `code` INT(11) auto_increment,
- PRIMARY KEY (`code`))
- eosql
- pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk')
- assert_equal 'code', pk
- assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq
+ with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'code', pk
+ assert_equal @conn.default_sequence_name('ex', 'code'), seq
+ end
end
def test_pk_and_sequence_for_with_custom_index_type_pk
- @conn.exec_query('drop table if exists ex_with_custom_index_type_pk')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_custom_index_type_pk` (
- `id` INT(11) auto_increment,
- PRIMARY KEY USING BTREE (`id`))
- eosql
- pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk')
- assert_equal 'id', pk
- assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
+ with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
end
def test_tinyint_integer_typecasting
- @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column')
- @conn.exec_query(<<-eosql)
- CREATE TABLE `ex_with_non_boolean_tinyint_column` (
- `status` TINYINT(4))
- eosql
- insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column')
+ with_example_table '`status` TINYINT(4)' do
+ insert(@conn, { 'status' => 2 }, 'ex')
- result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column')
+ result = @conn.exec_query('SELECT status FROM ex')
- assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ assert_equal 2, result.column_types['status'].type_cast(result.last['status'])
+ end
end
def test_supports_extensions
@@ -140,6 +133,15 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+
+ def with_example_table(definition = nil, &block)
+ definition ||= <<-SQL
+ `id` int(11) auto_increment PRIMARY KEY,
+ `number` integer,
+ `data` varchar(255)
+ SQL
+ super(@conn, 'ex', definition, &block)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 8eb9565963..61ae0abfd1 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'distinct_select'=>'distinct_id int, select_id int'
end
- def teardown
+ teardown do
drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 4ccf568406..cefc3e3c7e 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -1,23 +1,23 @@
require "cases/helper"
+require 'support/connection_helper'
class ActiveSchemaTest < ActiveRecord::TestCase
- def setup
- @connection = ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ include ConnectionHelper
+ def setup
ActiveRecord::Base.connection.singleton_class.class_eval do
alias_method :execute_without_stub, :execute
def execute(sql, name = nil) return sql end
end
end
- def teardown
- ActiveRecord::Base.remove_connection
- ActiveRecord::Base.establish_connection(@connection)
+ teardown do
+ reset_connection
end
def test_add_index
- # add_index calls index_name_exists? which can't work since execute is stubbed
+ # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed
+ def (ActiveRecord::Base.connection).table_exists?(*); true; end
def (ActiveRecord::Base.connection).index_name_exists?(*); false; end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
@@ -116,6 +116,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
+ def test_indexes_in_create
+ ActiveRecord::Base.connection.stubs(:table_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"
+ 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
+
+ assert_equal expected, actual
+ end
+
private
def with_real_execute
ActiveRecord::Base.connection.singleton_class.class_eval do
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index 6bcc113482..09bebf3071 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -3,10 +3,10 @@ require 'models/person'
class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
class CollationTest < ActiveRecord::Base
- validates_uniqueness_of :string_cs_column, :case_sensitive => false
- validates_uniqueness_of :string_ci_column, :case_sensitive => false
end
+ repair_validations(CollationTest)
+
def test_columns_include_collation_different_from_table
assert_equal 'utf8_bin', CollationTest.columns_hash['string_cs_column'].collation
assert_equal 'utf8_general_ci', CollationTest.columns_hash['string_ci_column'].collation
@@ -18,6 +18,7 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => false)
CollationTest.create!(:string_ci_column => 'A')
invalid = CollationTest.new(:string_ci_column => 'a')
queries = assert_sql { invalid.save }
@@ -26,10 +27,29 @@ class Mysql2CaseSensitivityTest < ActiveRecord::TestCase
end
def test_case_insensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => false)
CollationTest.create!(:string_cs_column => 'A')
invalid = CollationTest.new(:string_cs_column => 'a')
queries = assert_sql { invalid.save }
cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/)}
assert_match(/lower/i, cs_uniqueness_query)
end
+
+ def test_case_sensitive_comparison_for_ci_column
+ CollationTest.validates_uniqueness_of(:string_ci_column, :case_sensitive => true)
+ CollationTest.create!(:string_ci_column => 'A')
+ invalid = CollationTest.new(:string_ci_column => 'A')
+ queries = assert_sql { invalid.save }
+ ci_uniqueness_query = queries.detect { |q| q.match(/string_ci_column/) }
+ assert_match(/binary/i, ci_uniqueness_query)
+ end
+
+ def test_case_sensitive_comparison_for_cs_column
+ CollationTest.validates_uniqueness_of(:string_cs_column, :case_sensitive => true)
+ CollationTest.create!(:string_cs_column => 'A')
+ invalid = CollationTest.new(:string_cs_column => 'A')
+ queries = assert_sql { invalid.save }
+ cs_uniqueness_query = queries.detect { |q| q.match(/string_cs_column/) }
+ assert_no_match(/binary/i, cs_uniqueness_query)
+ end
end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 9b7202c915..182d9409c7 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -1,15 +1,18 @@
require "cases/helper"
+require 'support/connection_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
def setup
super
@subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
end
def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
+ ActiveSupport::Notifications.unsubscribe(@subscription)
super
end
@@ -97,14 +100,10 @@ class MysqlConnectionTest < ActiveRecord::TestCase
@connection.execute "DROP TABLE `bar_baz`"
end
- private
-
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
+ if mysql_56?
+ def test_quote_time_usec
+ assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0))
+ assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1a82308176..799d927ee4 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
'distinct_select'=>'distinct_id int, select_id int'
end
- def teardown
+ teardown do
drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order']
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 22dd48e113..3808db5141 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -7,7 +7,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
remove_method :execute
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 3090f4478f..36ded66998 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -19,14 +19,17 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
@column = PgArray.columns.find { |c| c.name == 'tags' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists pg_arrays'
end
def test_column
assert_equal :string, @column.type
+ assert_equal "character varying(255)", @column.sql_type
assert @column.array
assert_not @column.text?
+ assert_not @column.number?
+ assert_not @column.binary?
ratings_column = PgArray.columns_hash['ratings']
assert_equal :integer, ratings_column.type
@@ -34,6 +37,28 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_not ratings_column.number?
end
+ def test_default
+ @connection.add_column 'pg_arrays', 'score', :integer, array: true, default: [4, 4, 2]
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["score"]
+
+ assert_equal([4, 4, 2], column.default)
+ assert_equal([4, 4, 2], PgArray.new.score)
+ ensure
+ PgArray.reset_column_information
+ end
+
+ def test_default_strings
+ @connection.add_column 'pg_arrays', 'names', :string, array: true, default: ["foo", "bar"]
+ PgArray.reset_column_information
+ column = PgArray.columns_hash["names"]
+
+ assert_equal(["foo", "bar"], column.default)
+ assert_equal(["foo", "bar"], PgArray.new.names)
+ ensure
+ PgArray.reset_column_information
+ end
+
def test_change_column_with_array
@connection.add_column :pg_arrays, :snippets, :string, array: true, default: []
@connection.change_column :pg_arrays, :snippets, :text, array: true, default: "{}"
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index b8dd35c4c5..c3394d7712 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -23,7 +23,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase
assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists bytea_data_type'
end
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
new file mode 100644
index 0000000000..948bf49a54
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+
+require 'cases/helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+if ActiveRecord::Base.connection.supports_extensions?
+ class PostgresqlCitextTest < ActiveRecord::TestCase
+ class Citext < ActiveRecord::Base
+ self.table_name = 'citexts'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ unless @connection.extension_enabled?('citext')
+ @connection.enable_extension 'citext'
+ @connection.commit_db_transaction
+ end
+
+ @connection.reconnect!
+
+ @connection.create_table('citexts') do |t|
+ t.citext 'cival'
+ end
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS citexts;'
+ @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;'
+ end
+
+ def test_citext_enabled
+ assert @connection.extension_enabled?('citext')
+ end
+
+ def test_column
+ column = Citext.columns_hash['cival']
+ assert_equal :citext, column.type
+ assert_equal 'citext', column.sql_type
+ assert_not column.text?
+ assert_not column.number?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_change_table_supports_json
+ @connection.transaction do
+ @connection.change_table('citexts') do |t|
+ t.citext 'username'
+ end
+ Citext.reset_column_information
+ column = Citext.columns.find { |c| c.name == 'username' }
+ assert_equal :citext, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
+ end
+ ensure
+ Citext.reset_column_information
+ end
+
+ def test_write
+ x = Citext.new(cival: 'Some CI Text')
+ x.save!
+ citext = Citext.first
+ assert_equal "Some CI Text", citext.cival
+
+ citext.cival = "Some NEW CI Text"
+ citext.save!
+
+ assert_equal "Some NEW CI Text", citext.reload.cival
+ end
+
+ def test_select_case_insensitive
+ @connection.execute "insert into citexts (cival) values('Cased Text')"
+ x = Citext.where(cival: 'cased text').first
+ assert_equal 'Cased Text', x.cival
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 7202cce390..224b1b770b 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -8,7 +8,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
self.table_name = "postgresql_composites"
end
- def teardown
+ teardown do
@connection.execute 'DROP TABLE IF EXISTS postgresql_composites'
@connection.execute 'DROP TYPE IF EXISTS full_address'
end
@@ -29,6 +29,17 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase
end
end
+ def test_column
+ column = PostgresqlComposite.columns_hash["address"]
+ # TODO: Composite columns should have a type
+ assert_nil column.type
+ assert_equal "full_address", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
def test_composite_mapping
@connection.execute "INSERT INTO postgresql_composites VALUES (1, ROW('Paris', 'Champs-Élysées'));"
composite = PostgresqlComposite.first
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 4715fa002d..5f84c893c0 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -1,19 +1,22 @@
require "cases/helper"
+require 'support/connection_helper'
module ActiveRecord
class PostgresqlConnectionTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
class NonExistentTable < ActiveRecord::Base
end
def setup
super
@subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
@connection = ActiveRecord::Base.connection
end
def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
+ ActiveSupport::Notifications.unsubscribe(@subscription)
super
end
@@ -45,6 +48,37 @@ module ActiveRecord
assert_equal 'off', expect
end
+ def test_reset
+ @connection.query('ROLLBACK')
+ @connection.query('SET geqo TO off')
+
+ # Verify the setting has been applied.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'off', expect
+
+ @connection.reset!
+
+ # Verify the setting has been cleared.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'on', expect
+ end
+
+ def test_reset_with_transaction
+ @connection.query('ROLLBACK')
+ @connection.query('SET geqo TO off')
+
+ # Verify the setting has been applied.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'off', expect
+
+ @connection.query('BEGIN')
+ @connection.reset!
+
+ # Verify the setting has been cleared.
+ expect = @connection.query('show geqo').first.first
+ assert_equal 'on', expect
+ end
+
def test_tables_logs_name
@connection.tables('hello')
assert_equal 'SCHEMA', @subscriber.logged[0][1]
@@ -167,17 +201,5 @@ module ActiveRecord
ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}}))
end
end
-
- private
-
- def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
- begin
- yield original_connection
- ensure
- ActiveRecord::Base.establish_connection(original_connection)
- end
- end
-
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 5c3a797c41..e7dda1a1af 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -27,9 +27,6 @@ end
class PostgresqlTimestampWithZone < ActiveRecord::Base
end
-class PostgresqlUUID < ActiveRecord::Base
-end
-
class PostgresqlLtree < ActiveRecord::Base
end
@@ -68,14 +65,11 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@first_oid = PostgresqlOid.find(1)
@connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')")
-
- @connection.execute("INSERT INTO postgresql_uuids (id, guid, compact_guid) VALUES(1, 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', 'f06c715096c1012f131764ce8f32c6d8')")
- @first_uuid = PostgresqlUUID.find(1)
end
- def teardown
+ teardown do
[PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress,
- PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone, PostgresqlUUID].each(&:delete_all)
+ PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all)
end
def test_array_escaping
@@ -124,10 +118,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
end
- def test_data_type_of_uuid_types
- assert_equal :uuid, @first_uuid.column_for_attribute(:guid).type
- end
-
def test_array_values
assert_equal [35000,21000,18000,17000], @first_array.commission_by_quarter
assert_equal ['foo','bar','baz'], @first_array.nicknames
@@ -180,11 +170,6 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
- def test_uuid_values
- assert_equal 'd96c3da0-96c1-012f-1316-64ce8f32c6d8', @first_uuid.guid
- assert_equal 'f06c7150-96c1-012f-1317-64ce8f32c6d8', @first_uuid.compact_guid
- end
-
def test_bit_string_values
assert_equal '00010101', @first_bit_string.bit_string
assert_equal '00010101', @first_bit_string.bit_string_varying
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
new file mode 100644
index 0000000000..214e89dd7f
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlDomainTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ class PostgresqlDomain < ActiveRecord::Base
+ self.table_name = "postgresql_domains"
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)"
+ @connection.create_table('postgresql_domains') do |t|
+ t.column :price, :custom_money
+ end
+ end
+
+ # reload type map after creating the enum type
+ @connection.send(:reload_type_map)
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_domains'
+ @connection.execute 'DROP DOMAIN IF EXISTS custom_money'
+ reset_connection
+ end
+
+ def test_column
+ column = PostgresqlDomain.columns_hash["price"]
+ assert_equal :decimal, column.type
+ assert_equal "custom_money", column.sql_type
+ assert column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_domain_acts_like_basetype
+ PostgresqlDomain.create price: ""
+ record = PostgresqlDomain.first
+ assert_nil record.price
+
+ record.price = "34.15"
+ record.save!
+
+ assert_equal BigDecimal.new("34.15"), record.reload.price
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
new file mode 100644
index 0000000000..73da5a74ab
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'support/connection_helper'
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlEnumTest < ActiveRecord::TestCase
+ include ConnectionHelper
+
+ class PostgresqlEnum < ActiveRecord::Base
+ self.table_name = "postgresql_enums"
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.transaction do
+ @connection.execute <<-SQL
+ CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+ SQL
+ @connection.create_table('postgresql_enums') do |t|
+ t.column :current_mood, :mood
+ end
+ end
+ # reload type map after creating the enum type
+ @connection.send(:reload_type_map)
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_enums'
+ @connection.execute 'DROP TYPE IF EXISTS mood'
+ reset_connection
+ end
+
+ def test_column
+ column = PostgresqlEnum.columns_hash["current_mood"]
+ assert_equal :enum, column.type
+ assert_equal "mood", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_enum_mapping
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ enum = PostgresqlEnum.first
+ assert_equal "sad", enum.current_mood
+
+ enum.current_mood = "happy"
+ enum.save!
+
+ assert_equal "happy", enum.reload.current_mood
+ end
+
+ def test_invalid_enum_update
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ enum = PostgresqlEnum.first
+ enum.current_mood = "angry"
+
+ assert_raise ActiveRecord::StatementInvalid do
+ enum.save
+ end
+ end
+
+ def test_no_oid_warning
+ @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
+ stderr_output = capture(:stderr) { PostgresqlEnum.first }
+
+ assert stderr_output.blank?
+ end
+
+ def test_enum_type_cast
+ enum = PostgresqlEnum.new
+ enum.current_mood = :happy
+
+ assert_equal "happy", enum.current_mood
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index f2502430de..c24c4b0d56 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -31,7 +31,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
@column = Hstore.columns.find { |c| c.name == 'tags' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists hstores'
end
@@ -54,6 +54,22 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
def test_column
assert_equal :hstore, @column.type
+ assert_equal "hstore", @column.sql_type
+ assert_not @column.number?
+ assert_not @column.text?
+ assert_not @column.binary?
+ assert_not @column.array
+ end
+
+ def test_default
+ @connection.add_column 'hstores', 'permissions', :hstore, default: '"users"=>"read", "articles"=>"write"'
+ Hstore.reset_column_information
+ column = Hstore.columns_hash["permissions"]
+
+ assert_equal({"users"=>"read", "articles"=>"write"}, column.default)
+ assert_equal({"users"=>"read", "articles"=>"write"}, Hstore.new.permissions)
+ ensure
+ Hstore.reset_column_information
end
def test_change_table_supports_hstore
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 3daef399d8..ee793ffff2 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -26,12 +26,29 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
@column = JsonDataType.columns.find { |c| c.name == 'payload' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists json_data_type'
end
def test_column
- assert_equal :json, @column.type
+ column = JsonDataType.columns_hash["payload"]
+ assert_equal :json, column.type
+ assert_equal "json", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_default
+ @connection.add_column 'json_data_type', 'permissions', :json, default: '{"users": "read", "posts": ["read", "write"]}'
+ JsonDataType.reset_column_information
+ column = JsonDataType.columns_hash["permissions"]
+
+ assert_equal({"users"=>"read", "posts"=>["read", "write"]}, column.default)
+ assert_equal({"users"=>"read", "posts"=>["read", "write"]}, JsonDataType.new.permissions)
+ ensure
+ JsonDataType.reset_column_information
end
def test_change_table_supports_json
@@ -57,16 +74,16 @@ class PostgresqlJSONTest < ActiveRecord::TestCase
end
def test_type_cast_json
- assert @column
+ column = JsonDataType.columns_hash["payload"]
data = "{\"a_key\":\"a_value\"}"
- hash = @column.class.string_to_json data
+ hash = column.class.string_to_json data
assert_equal({'a_key' => 'a_value'}, hash)
- assert_equal({'a_key' => 'a_value'}, @column.type_cast(data))
+ assert_equal({'a_key' => 'a_value'}, column.type_cast(data))
- assert_equal({}, @column.type_cast("{}"))
- assert_equal({'key'=>nil}, @column.type_cast('{"key": null}'))
- assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
+ assert_equal({}, column.type_cast("{}"))
+ assert_equal({'key'=>nil}, column.type_cast('{"key": null}'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, column.type_cast(%q({"c":"}", "\"a\"":"b \"a b"})))
end
def test_rewrite
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 5d12ca75ca..718f37a380 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -10,6 +10,11 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
def setup
@connection = ActiveRecord::Base.connection
+
+ unless @connection.extension_enabled?('ltree')
+ @connection.enable_extension 'ltree'
+ end
+
@connection.transaction do
@connection.create_table('ltrees') do |t|
t.ltree 'path'
@@ -19,13 +24,18 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase
skip "do not test on PG without ltree"
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists ltrees'
end
def test_column
column = Ltree.columns_hash['path']
assert_equal :ltree, column.type
+ assert_equal "ltree", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
end
def test_write
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 019406dd84..49d8ec238d 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -1,9 +1,12 @@
# encoding: utf-8
require "cases/helper"
+require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+
def setup
@connection = ActiveRecord::Base.connection
end
@@ -176,6 +179,51 @@ module ActiveRecord
assert_nil @connection.pk_and_sequence_for('unobtainium')
end
+ def test_pk_and_sequence_for_with_collision_pg_class_oid
+ @connection.exec_query('create table ex(id serial primary key)')
+ @connection.exec_query('create table ex2(id serial primary key)')
+
+ correct_depend_record = [
+ "'pg_class'::regclass",
+ "'ex_id_seq'::regclass",
+ '0',
+ "'pg_class'::regclass",
+ "'ex'::regclass",
+ '1',
+ "'a'"
+ ]
+
+ collision_depend_record = [
+ "'pg_attrdef'::regclass",
+ "'ex2_id_seq'::regclass",
+ '0',
+ "'pg_class'::regclass",
+ "'ex'::regclass",
+ '1',
+ "'a'"
+ ]
+
+ @connection.exec_query(
+ "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
+ )
+ @connection.exec_query(
+ "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})"
+ )
+ @connection.exec_query(
+ "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})"
+ )
+
+ seq = @connection.pk_and_sequence_for('ex').last
+ assert_equal 'ex_id_seq', seq
+
+ @connection.exec_query(
+ "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
+ )
+ ensure
+ @connection.exec_query('DROP TABLE IF EXISTS ex')
+ @connection.exec_query('DROP TABLE IF EXISTS ex2')
+ end
+
def test_exec_insert_number
with_example_table do
insert(@connection, 'number' => 10)
@@ -324,12 +372,8 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
- def with_example_table(definition = nil)
- definition ||= 'id serial primary key, number integer, data character varying(255)'
- @connection.exec_query("create table ex(#{definition})")
- yield
- ensure
- @connection.exec_query('drop table if exists ex')
+ def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block)
+ super(@connection, 'ex', definition, &block)
end
def connection_without_insert_returning
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 1122f8b9a1..51846e22d9 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -10,46 +10,46 @@ module ActiveRecord
end
def test_type_cast_true
- c = Column.new(nil, 1, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean')
assert_equal 't', @conn.type_cast(true, nil)
assert_equal 't', @conn.type_cast(true, c)
end
def test_type_cast_false
- c = Column.new(nil, 1, 'boolean')
+ c = PostgreSQLColumn.new(nil, 1, OID::Boolean.new, 'boolean')
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
def test_type_cast_cidr
ip = IPAddr.new('255.0.0.0/8')
- c = Column.new(nil, ip, 'cidr')
+ c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'cidr')
assert_equal ip, @conn.type_cast(ip, c)
end
def test_type_cast_inet
ip = IPAddr.new('255.1.0.0/8')
- c = Column.new(nil, ip, 'inet')
+ c = PostgreSQLColumn.new(nil, ip, OID::Cidr.new, 'inet')
assert_equal ip, @conn.type_cast(ip, c)
end
def test_quote_float_nan
nan = 0.0/0
- c = Column.new(nil, 1, 'float')
+ c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
assert_equal "'NaN'", @conn.quote(nan, c)
end
def test_quote_float_infinity
infinity = 1.0/0
- c = Column.new(nil, 1, 'float')
+ c = PostgreSQLColumn.new(nil, 1, OID::Float.new, 'float')
assert_equal "'Infinity'", @conn.quote(infinity, c)
end
def test_quote_cast_numeric
fixnum = 666
- c = Column.new(nil, nil, 'varchar')
+ c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar')
assert_equal "'666'", @conn.quote(fixnum, c)
- c = Column.new(nil, nil, 'text')
+ c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text')
assert_equal "'666'", @conn.quote(fixnum, c)
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 5c2d8e1c1d..57c7da2657 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/connection_helper'
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
@@ -8,14 +9,20 @@ if ActiveRecord::Base.connection.supports_ranges?
end
class PostgresqlRangeTest < ActiveRecord::TestCase
- def teardown
- @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
- end
+ self.use_transactional_fixtures = false
+ include ConnectionHelper
def setup
- @connection = ActiveRecord::Base.connection
+ @connection = PostgresqlRange.connection
begin
@connection.transaction do
+ @connection.execute <<_SQL
+ CREATE TYPE floatrange AS RANGE (
+ subtype = float8,
+ subtype_diff = float8mi
+ );
+_SQL
+
@connection.create_table('postgresql_ranges') do |t|
t.daterange :date_range
t.numrange :num_range
@@ -24,7 +31,11 @@ if ActiveRecord::Base.connection.supports_ranges?
t.int4range :int4_range
t.int8range :int8_range
end
+
+ @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange'
end
+ @connection.send :reload_type_map
+ PostgresqlRange.reset_column_information
rescue ActiveRecord::StatementInvalid
skip "do not test on PG without range"
end
@@ -35,23 +46,26 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'']",
tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']",
int4_range: "[1, 10]",
- int8_range: "[10, 100]")
+ int8_range: "[10, 100]",
+ float_range: "[0.5, 0.7]")
insert_range(id: 102,
- date_range: "(''2012-01-02'', ''2012-01-04'')",
+ date_range: "[''2012-01-02'', ''2012-01-04'')",
num_range: "[0.1, 0.2)",
ts_range: "[''2010-01-01 14:30'', ''2011-01-01 14:30'')",
tstz_range: "[''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'')",
- int4_range: "(1, 10)",
- int8_range: "(10, 100)")
+ int4_range: "[1, 10)",
+ int8_range: "[10, 100)",
+ float_range: "[0.5, 0.7)")
insert_range(id: 103,
- date_range: "(''2012-01-02'',]",
+ date_range: "[''2012-01-02'',]",
num_range: "[0.1,]",
ts_range: "[''2010-01-01 14:30'',]",
tstz_range: "[''2010-01-01 14:30:00+05'',]",
- int4_range: "(1,]",
- int8_range: "(10,]")
+ int4_range: "[1,]",
+ int8_range: "[10,]",
+ float_range: "[0.5,]")
insert_range(id: 104,
date_range: "[,]",
@@ -59,15 +73,17 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range: "[,]",
tstz_range: "[,]",
int4_range: "[,]",
- int8_range: "[,]")
+ int8_range: "[,]",
+ float_range: "[,]")
insert_range(id: 105,
- date_range: "(''2012-01-02'', ''2012-01-02'')",
- num_range: "(0.1, 0.1)",
- ts_range: "(''2010-01-01 14:30'', ''2010-01-01 14:30'')",
- tstz_range: "(''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
- int4_range: "(1, 1)",
- int8_range: "(10, 10)")
+ date_range: "[''2012-01-02'', ''2012-01-02'')",
+ num_range: "[0.1, 0.1)",
+ ts_range: "[''2010-01-01 14:30'', ''2010-01-01 14:30'')",
+ tstz_range: "[''2010-01-01 14:30:00+05'', ''2010-01-01 06:30:00-03'')",
+ int4_range: "[1, 1)",
+ int8_range: "[10, 10)",
+ float_range: "[0.5, 0.5)")
@new_range = PostgresqlRange.new
@first_range = PostgresqlRange.find(101)
@@ -77,6 +93,12 @@ if ActiveRecord::Base.connection.supports_ranges?
@empty_range = PostgresqlRange.find(105)
end
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.execute 'DROP TYPE IF EXISTS floatrange'
+ reset_connection
+ end
+
def test_data_type_of_range_types
assert_equal :daterange, @first_range.column_for_attribute(:date_range).type
assert_equal :numrange, @first_range.column_for_attribute(:num_range).type
@@ -88,24 +110,24 @@ if ActiveRecord::Base.connection.supports_ranges?
def test_int4range_values
assert_equal 1...11, @first_range.int4_range
- assert_equal 2...10, @second_range.int4_range
- assert_equal 2...Float::INFINITY, @third_range.int4_range
+ assert_equal 1...10, @second_range.int4_range
+ assert_equal 1...Float::INFINITY, @third_range.int4_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int4_range)
assert_nil @empty_range.int4_range
end
def test_int8range_values
assert_equal 10...101, @first_range.int8_range
- assert_equal 11...100, @second_range.int8_range
- assert_equal 11...Float::INFINITY, @third_range.int8_range
+ assert_equal 10...100, @second_range.int8_range
+ assert_equal 10...Float::INFINITY, @third_range.int8_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.int8_range)
assert_nil @empty_range.int8_range
end
def test_daterange_values
assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 5), @first_range.date_range
- assert_equal Date.new(2012, 1, 3)...Date.new(2012, 1, 4), @second_range.date_range
- assert_equal Date.new(2012, 1, 3)...Float::INFINITY, @third_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Date.new(2012, 1, 4), @second_range.date_range
+ assert_equal Date.new(2012, 1, 2)...Float::INFINITY, @third_range.date_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.date_range)
assert_nil @empty_range.date_range
end
@@ -133,6 +155,14 @@ if ActiveRecord::Base.connection.supports_ranges?
assert_nil @empty_range.tstz_range
end
+ def test_custom_range_values
+ assert_equal 0.5..0.7, @first_range.float_range
+ assert_equal 0.5...0.7, @second_range.float_range
+ assert_equal 0.5...Float::INFINITY, @third_range.float_range
+ assert_equal (-Float::INFINITY...Float::INFINITY), @fourth_range.float_range
+ assert_nil @empty_range.float_range
+ end
+
def test_create_tstzrange
tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT')
round_trip(@new_range, :tstz_range, tstzrange)
@@ -203,6 +233,38 @@ if ActiveRecord::Base.connection.supports_ranges?
assert_nil_round_trip(@first_range, :int8_range, 39999...39999)
end
+ def test_exclude_beginning_for_subtypes_with_succ_method_is_deprecated
+ tz = ::ActiveRecord::Base.default_timezone
+
+ silence_warnings {
+ assert_deprecated {
+ range = PostgresqlRange.create!(date_range: "(''2012-01-02'', ''2012-01-04'']")
+ assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range
+ }
+ assert_deprecated {
+ range = PostgresqlRange.create!(ts_range: "(''2010-01-01 14:30'', ''2011-01-01 14:30'']")
+ assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 1)..Time.send(tz, 2011, 1, 1, 14, 30, 0), range.ts_range
+ }
+ assert_deprecated {
+ range = PostgresqlRange.create!(tstz_range: "(''2010-01-01 14:30:00+05'', ''2011-01-01 14:30:00-03'']")
+ assert_equal Time.parse('2010-01-01 09:30:01 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), range.tstz_range
+ }
+ assert_deprecated {
+ range = PostgresqlRange.create!(int4_range: "(1, 10]")
+ assert_equal 2..10, range.int4_range
+ }
+ assert_deprecated {
+ range = PostgresqlRange.create!(int8_range: "(10, 100]")
+ assert_equal 11..100, range.int8_range
+ }
+ }
+ end
+
+ def test_exclude_beginning_for_subtypes_without_succ_method_is_not_supported
+ assert_raises(ArgumentError) { PostgresqlRange.create!(num_range: "(0.1, 0.2]") }
+ assert_raises(ArgumentError) { PostgresqlRange.create!(float_range: "(0.5, 0.7]") }
+ end
+
private
def assert_equal_round_trip(range, attribute, value)
round_trip(range, attribute, value)
@@ -229,7 +291,8 @@ if ActiveRecord::Base.connection.supports_ranges?
ts_range,
tstz_range,
int4_range,
- int8_range
+ int8_range,
+ float_range
) VALUES (
#{values[:id]},
'#{values[:date_range]}',
@@ -237,7 +300,8 @@ if ActiveRecord::Base.connection.supports_ranges?
'#{values[:ts_range]}',
'#{values[:tstz_range]}',
'#{values[:int4_range]}',
- '#{values[:int8_range]}'
+ '#{values[:int8_range]}',
+ '#{values[:float_range]}'
)
SQL
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index d5e1838543..99c26c4bf7 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -27,7 +27,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
set_session_auth
@connection.execute "RESET search_path"
USERS.each do |u|
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 3f7009c1d1..11ec7599a3 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -71,7 +71,7 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
- def teardown
+ teardown do
@connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE"
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
@@ -271,13 +271,13 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_with_uppercase_index_name
- ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"}
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
+ assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"}
+ @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)"
- ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME
- assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"}
- ActiveRecord::Base.connection.schema_search_path = "public"
+ with_schema_search_path SCHEMA_NAME do
+ assert_nothing_raised { @connection.remove_index! "things", "things_Index"}
+ end
end
def test_primary_key_with_schema_specified
@@ -328,18 +328,17 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_prepared_statements_with_multiple_schemas
+ [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
+ with_schema_search_path schema_name do
+ Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
+ end
+ end
- @connection.schema_search_path = SCHEMA_NAME
- Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now)
-
- @connection.schema_search_path = SCHEMA2_NAME
- Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now)
-
- @connection.schema_search_path = SCHEMA_NAME
- assert_equal 1, Thing5.count
-
- @connection.schema_search_path = SCHEMA2_NAME
- assert_equal 1, Thing5.count
+ [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name|
+ with_schema_search_path schema_name do
+ assert_equal 1, Thing5.count
+ end
+ end
end
def test_schema_exists?
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 3f5d981444..9e03ea6bee 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -4,31 +4,88 @@ require "cases/helper"
require 'active_record/base'
require 'active_record/connection_adapters/postgresql_adapter'
+module PostgresqlUUIDHelper
+ def connection
+ @connection ||= ActiveRecord::Base.connection
+ end
+
+ def enable_uuid_ossp
+ unless connection.extension_enabled?('uuid-ossp')
+ connection.enable_extension 'uuid-ossp'
+ connection.commit_db_transaction
+ end
+
+ connection.reconnect!
+ end
+
+ def drop_table(name)
+ connection.execute "drop table if exists #{name}"
+ end
+end
+
class PostgresqlUUIDTest < ActiveRecord::TestCase
- class UUID < ActiveRecord::Base
- self.table_name = 'pg_uuids'
+ include PostgresqlUUIDHelper
+
+ class UUIDType < ActiveRecord::Base
+ self.table_name = "uuid_data_type"
end
- def setup
- @connection = ActiveRecord::Base.connection
+ setup do
+ connection.create_table "uuid_data_type" do |t|
+ t.uuid 'guid'
+ end
+ end
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
+ teardown do
+ drop_table "uuid_data_type"
+ end
+
+ def test_data_type_of_uuid_types
+ column = UUIDType.columns_hash["guid"]
+ assert_equal :uuid, column.type
+ assert_equal "uuid", column.sql_type
+ assert_not column.number?
+ assert_not column.text?
+ assert_not column.binary?
+ assert_not column.array
+ end
+
+ def test_treat_blank_uuid_as_nil
+ UUIDType.create! guid: ''
+ assert_equal(nil, UUIDType.last.guid)
+ end
+
+ def test_uuid_formats
+ ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
+ "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
+ "a0eebc999c0b4ef8bb6d6bb9bd380a11",
+ "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11",
+ "{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}"].each do |valid_uuid|
+ UUIDType.create(guid: valid_uuid)
+ uuid = UUIDType.last
+ assert_equal "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", uuid.guid
end
+ end
+end
- @connection.reconnect!
+class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase
+ include PostgresqlUUIDHelper
- @connection.transaction do
- @connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
- t.string 'name'
- t.uuid 'other_uuid', default: 'uuid_generate_v4()'
- end
+ class UUID < ActiveRecord::Base
+ self.table_name = 'pg_uuids'
+ end
+
+ setup do
+ enable_uuid_ossp
+
+ connection.create_table('pg_uuids', id: :uuid, default: 'uuid_generate_v1()') do |t|
+ t.string 'name'
+ t.uuid 'other_uuid', default: 'uuid_generate_v4()'
end
end
- def teardown
- @connection.execute 'drop table if exists pg_uuids'
+ teardown do
+ drop_table "pg_uuids"
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -49,14 +106,14 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
end
def test_pk_and_sequence_for_uuid_primary_key
- pk, seq = @connection.pk_and_sequence_for('pg_uuids')
+ pk, seq = connection.pk_and_sequence_for('pg_uuids')
assert_equal 'id', pk
assert_equal nil, seq
end
def test_schema_dumper_for_uuid_primary_key
schema = StringIO.new
- ActiveRecord::SchemaDumper.dump(@connection, schema)
+ ActiveRecord::SchemaDumper.dump(connection, schema)
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string)
assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string)
end
@@ -64,34 +121,24 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
end
class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
- class UUID < ActiveRecord::Base
- self.table_name = 'pg_uuids'
- end
+ include PostgresqlUUIDHelper
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.reconnect!
+ setup do
+ enable_uuid_ossp
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
- end
-
- @connection.transaction do
- @connection.create_table('pg_uuids', id: false) do |t|
- t.primary_key :id, :uuid, default: nil
- t.string 'name'
- end
+ connection.create_table('pg_uuids', id: false) do |t|
+ t.primary_key :id, :uuid, default: nil
+ t.string 'name'
end
end
- def teardown
- @connection.execute 'drop table if exists pg_uuids'
+ teardown do
+ drop_table "pg_uuids"
end
if ActiveRecord::Base.connection.supports_extensions?
def test_id_allows_default_override_via_nil
- col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
+ col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
@@ -101,6 +148,8 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase
end
class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
+ include PostgresqlUUIDHelper
+
class UuidPost < ActiveRecord::Base
self.table_name = 'pg_uuid_posts'
has_many :uuid_comments, inverse_of: :uuid_post
@@ -111,30 +160,24 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase
belongs_to :uuid_post
end
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.reconnect!
-
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
- end
+ setup do
+ enable_uuid_ossp
- @connection.transaction do
- @connection.create_table('pg_uuid_posts', id: :uuid) do |t|
+ connection.transaction do
+ connection.create_table('pg_uuid_posts', id: :uuid) do |t|
t.string 'title'
end
- @connection.create_table('pg_uuid_comments', id: :uuid) do |t|
+ connection.create_table('pg_uuid_comments', id: :uuid) do |t|
t.uuid :uuid_post_id, default: 'uuid_generate_v4()'
t.string 'content'
end
end
end
- def teardown
- @connection.transaction do
- @connection.execute 'drop table if exists pg_uuid_comments'
- @connection.execute 'drop table if exists pg_uuid_posts'
+ teardown do
+ connection.transaction do
+ drop_table "pg_uuid_comments"
+ drop_table "pg_uuid_posts"
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
index 66e07b71a0..47b7d38eda 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -1,11 +1,15 @@
require "cases/helper"
-class ViewTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+module ViewTestConcern
+ extend ActiveSupport::Concern
+
+ included do
+ self.use_transactional_fixtures = false
+ mattr_accessor :view_type
+ end
SCHEMA_NAME = 'test_schema'
TABLE_NAME = 'things'
- VIEW_NAME = 'view_things'
COLUMNS = [
'id integer',
'name character varying(50)',
@@ -14,17 +18,19 @@ class ViewTest < ActiveRecord::TestCase
]
class ThingView < ActiveRecord::Base
- self.table_name = 'test_schema.view_things'
end
def setup
+ super
+ ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things"
+
@connection = ActiveRecord::Base.connection
@connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
- @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})"
- @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
+ @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
end
def teardown
+ super
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
@@ -35,7 +41,7 @@ class ViewTest < ActiveRecord::TestCase
def test_column_definitions
assert_nothing_raised do
- assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}")
+ assert_equal COLUMNS, columns(ThingView.table_name)
end
end
@@ -47,3 +53,15 @@ class ViewTest < ActiveRecord::TestCase
end
end
+
+class ViewTest < ActiveRecord::TestCase
+ include ViewTestConcern
+ self.view_type = 'view'
+end
+
+if ActiveRecord::Base.connection.supports_materialized_views?
+ class MaterializedViewTest < ActiveRecord::TestCase
+ include ViewTestConcern
+ self.view_type = 'materialized_view'
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb
index dd2a727afe..ae299697b1 100644
--- a/activerecord/test/cases/adapters/postgresql/xml_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb
@@ -23,7 +23,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase
@column = XmlDataType.columns.find { |c| c.name == 'payload' }
end
- def teardown
+ teardown do
@connection.execute 'drop table if exists xml_data_type'
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 73cb739b2b..14aad61ce2 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -2,28 +2,22 @@
require "cases/helper"
require 'models/owner'
require 'tempfile'
+require 'support/ddl_helper'
module ActiveRecord
module ConnectionAdapters
class SQLite3AdapterTest < ActiveRecord::TestCase
+ include DdlHelper
+
self.use_transactional_fixtures = false
class DualEncoding < ActiveRecord::Base
end
def setup
- @conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 100
- @conn.execute <<-eosql
- CREATE TABLE items (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
-
- @subscriber = SQLSubscriber.new
- ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
+ @conn = Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: 100
end
def test_bad_connection
@@ -37,7 +31,7 @@ module ActiveRecord
def test_connect_with_url
original_connection = ActiveRecord::Base.remove_connection
tf = Tempfile.open 'whatever'
- url = "sqlite3://#{tf.path}"
+ url = "sqlite3:#{tf.path}"
ActiveRecord::Base.establish_connection(url)
assert ActiveRecord::Base.connection
ensure
@@ -48,7 +42,7 @@ module ActiveRecord
def test_connect_memory_with_url
original_connection = ActiveRecord::Base.remove_connection
- url = "sqlite3:///:memory:"
+ url = "sqlite3::memory:"
ActiveRecord::Base.establish_connection(url)
assert ActiveRecord::Base.connection
ensure
@@ -57,8 +51,10 @@ module ActiveRecord
end
def test_valid_column
- column = @conn.columns('items').find { |col| col.name == 'id' }
- assert @conn.valid_type?(column.type)
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
end
# sqlite databases should be able to support any type and not
@@ -69,13 +65,8 @@ module ActiveRecord
assert @conn.valid_type?(:foobar)
end
- def teardown
- ActiveSupport::Notifications.unsubscribe(@subscriber)
- super
- end
-
def test_column_types
- owner = Owner.create!(:name => "hello".encode('ascii-8bit'))
+ owner = Owner.create!(name: "hello".encode('ascii-8bit'))
owner.reload
select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', '
result = Owner.connection.exec_query <<-esql
@@ -85,23 +76,28 @@ module ActiveRecord
esql
assert(!result.rows.first.include?("blob"), "should not store blobs")
+ ensure
+ owner.delete
end
def test_exec_insert
- column = @conn.columns('items').find { |col| col.name == 'number' }
- vals = [[column, 10]]
- @conn.exec_insert('insert into items (number) VALUES (?)', 'SQL', vals)
+ with_example_table do
+ column = @conn.columns('ex').find { |col| col.name == 'number' }
+ vals = [[column, 10]]
+ @conn.exec_insert('insert into ex (number) VALUES (?)', 'SQL', vals)
- result = @conn.exec_query(
- 'select number from items where number = ?', 'SQL', vals)
+ result = @conn.exec_query(
+ 'select number from ex where number = ?', 'SQL', vals)
- assert_equal 1, result.rows.length
- assert_equal 10, result.rows.first.first
+ assert_equal 1, result.rows.length
+ assert_equal 10, result.rows.first.first
+ end
end
def test_primary_key_returns_nil_for_no_pk
- @conn.exec_query('create table ex(id int, data string)')
- assert_nil @conn.primary_key('ex')
+ with_example_table 'id int, data string' do
+ assert_nil @conn.primary_key('ex')
+ end
end
def test_connection_no_db
@@ -112,17 +108,17 @@ module ActiveRecord
def test_bad_timeout
assert_raises(TypeError) do
- Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => 'usa'
+ Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: 'usa'
end
end
# connection is OK with a nil timeout
def test_nil_timeout
- conn = Base.sqlite3_connection :database => ':memory:',
- :adapter => 'sqlite3',
- :timeout => nil
+ conn = Base.sqlite3_connection database: ':memory:',
+ adapter: 'sqlite3',
+ timeout: nil
assert conn, 'made a connection'
end
@@ -141,44 +137,47 @@ module ActiveRecord
end
def test_exec_no_binds
- @conn.exec_query('create table ex(id int, data string)')
- result = @conn.exec_query('SELECT id, data FROM ex')
- assert_equal 0, result.rows.length
- assert_equal 2, result.columns.length
- assert_equal %w{ id data }, result.columns
-
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec_query('SELECT id, data FROM ex')
- assert_equal 1, result.rows.length
- assert_equal 2, result.columns.length
-
- assert_equal [[1, 'foo']], result.rows
+ with_example_table 'id int, data string' do
+ result = @conn.exec_query('SELECT id, data FROM ex')
+ assert_equal 0, result.rows.length
+ assert_equal 2, result.columns.length
+ assert_equal %w{ id data }, result.columns
+
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query('SELECT id, data FROM ex')
+ assert_equal 1, result.rows.length
+ assert_equal 2, result.columns.length
+
+ assert_equal [[1, 'foo']], result.rows
+ end
end
def test_exec_query_with_binds
- @conn.exec_query('create table ex(id int, data string)')
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
+ with_example_table 'id int, data string' do
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ result = @conn.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]])
- 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
def test_exec_query_typecasts_bind_vals
- @conn.exec_query('create table ex(id int, data string)')
- @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
- column = @conn.columns('ex').find { |col| col.name == 'id' }
+ with_example_table 'id int, data string' do
+ @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")')
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
- result = @conn.exec_query(
- 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
+ result = @conn.exec_query(
+ 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']])
- 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
def test_quote_binary_column_escapes_it
@@ -190,7 +189,7 @@ module ActiveRecord
)
eosql
str = "\x80".force_encoding("ASCII-8BIT")
- binary = DualEncoding.new :name => 'いただきます!', :data => str
+ binary = DualEncoding.new name: 'いただきます!', data: str
binary.save!
assert_equal str, binary.data
@@ -202,16 +201,20 @@ module ActiveRecord
name = 'hello'.force_encoding(Encoding::ASCII_8BIT)
Owner.create(name: name)
assert_equal Encoding::ASCII_8BIT, name.encoding
+ ensure
+ Owner.delete_all
end
def test_execute
- @conn.execute "INSERT INTO items (number) VALUES (10)"
- records = @conn.execute "SELECT * FROM items"
- assert_equal 1, records.length
-
- record = records.first
- assert_equal 10, record['number']
- assert_equal 1, record['id']
+ with_example_table do
+ @conn.execute "INSERT INTO ex (number) VALUES (10)"
+ records = @conn.execute "SELECT * FROM ex"
+ assert_equal 1, records.length
+
+ record = records.first
+ assert_equal 10, record['number']
+ assert_equal 1, record['id']
+ end
end
def test_quote_string
@@ -219,128 +222,141 @@ module ActiveRecord
end
def test_insert_sql
- 2.times do |i|
- rv = @conn.insert_sql "INSERT INTO items (number) VALUES (#{i})"
- assert_equal(i + 1, rv)
+ 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
-
- records = @conn.execute "SELECT * FROM items"
- assert_equal 2, records.length
end
def test_insert_sql_logged
- sql = "INSERT INTO items (number) VALUES (10)"
- name = "foo"
-
- assert_logged([[sql, name, []]]) do
- @conn.insert_sql sql, name
+ with_example_table do
+ sql = "INSERT INTO ex (number) VALUES (10)"
+ name = "foo"
+ assert_logged [[sql, name, []]] do
+ @conn.insert_sql sql, name
+ end
end
end
def test_insert_id_value_returned
- sql = "INSERT INTO items (number) VALUES (10)"
- idval = 'vuvuzela'
- id = @conn.insert_sql sql, nil, nil, idval
- assert_equal idval, id
+ with_example_table do
+ sql = "INSERT INTO ex (number) VALUES (10)"
+ idval = 'vuvuzela'
+ id = @conn.insert_sql sql, nil, nil, idval
+ assert_equal idval, id
+ end
end
def test_select_rows
- 2.times do |i|
- @conn.create "INSERT INTO items (number) VALUES (#{i})"
+ with_example_table do
+ 2.times do |i|
+ @conn.create "INSERT INTO ex (number) VALUES (#{i})"
+ end
+ rows = @conn.select_rows 'select number, id from ex'
+ assert_equal [[0, 1], [1, 2]], rows
end
- rows = @conn.select_rows 'select number, id from items'
- assert_equal [[0, 1], [1, 2]], rows
end
def test_select_rows_logged
- sql = "select * from items"
- name = "foo"
-
- assert_logged([[sql, name, []]]) do
- @conn.select_rows sql, name
+ with_example_table do
+ sql = "select * from ex"
+ name = "foo"
+ assert_logged [[sql, name, []]] do
+ @conn.select_rows sql, name
+ end
end
end
def test_transaction
- count_sql = 'select count(*) from items'
+ with_example_table do
+ count_sql = 'select count(*) from ex'
- @conn.begin_db_transaction
- @conn.create "INSERT INTO items (number) VALUES (10)"
+ @conn.begin_db_transaction
+ @conn.create "INSERT INTO ex (number) VALUES (10)"
- assert_equal 1, @conn.select_rows(count_sql).first.first
- @conn.rollback_db_transaction
- assert_equal 0, @conn.select_rows(count_sql).first.first
+ assert_equal 1, @conn.select_rows(count_sql).first.first
+ @conn.rollback_db_transaction
+ assert_equal 0, @conn.select_rows(count_sql).first.first
+ end
end
def test_tables
- assert_equal %w{ items }, @conn.tables
-
- @conn.execute <<-eosql
- CREATE TABLE people (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer
- )
- eosql
- assert_equal %w{ items people }.sort, @conn.tables.sort
+ with_example_table do
+ assert_equal %w{ ex }, @conn.tables
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer', 'people' do
+ assert_equal %w{ ex people }.sort, @conn.tables.sort
+ end
+ end
end
def test_tables_logs_name
- assert_logged [['SCHEMA', []]] do
+ sql = <<-SQL
+ SELECT name FROM sqlite_master
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ SQL
+ assert_logged [[sql.squish, 'SCHEMA', []]] do
@conn.tables('hello')
- assert_not_nil @subscriber.logged.first.shift
end
end
def test_indexes_logs_name
- assert_logged [["PRAGMA index_list(\"items\")", 'SCHEMA', []]] do
- @conn.indexes('items', 'hello')
+ with_example_table do
+ assert_logged [["PRAGMA index_list(\"ex\")", 'SCHEMA', []]] do
+ @conn.indexes('ex', 'hello')
+ end
end
end
def test_table_exists_logs_name
- assert @conn.table_exists?('items')
- assert_equal 'SCHEMA', @subscriber.logged[0][1]
+ with_example_table do
+ sql = <<-SQL
+ SELECT name FROM sqlite_master
+ WHERE type = 'table'
+ AND NOT name = 'sqlite_sequence' AND name = \"ex\"
+ SQL
+ assert_logged [[sql.squish, 'SCHEMA', []]] do
+ assert @conn.table_exists?('ex')
+ end
+ end
end
def test_columns
- columns = @conn.columns('items').sort_by { |x| x.name }
- assert_equal 2, columns.length
- assert_equal %w{ id number }.sort, columns.map { |x| x.name }
- assert_equal [nil, nil], columns.map { |x| x.default }
- assert_equal [true, true], columns.map { |x| x.null }
+ with_example_table do
+ columns = @conn.columns('ex').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
end
def test_columns_with_default
- @conn.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer default 10
- )
- eosql
- column = @conn.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert_equal 10, column.default
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer default 10' do
+ column = @conn.columns('ex').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
end
def test_columns_with_not_null
- @conn.execute <<-eosql
- CREATE TABLE columns_with_default (
- id integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- column = @conn.columns('columns_with_default').find { |x|
- x.name == 'number'
- }
- assert !column.null, "column should not be null"
+ with_example_table 'id integer PRIMARY KEY AUTOINCREMENT, number integer not null' do
+ column = @conn.columns('ex').find { |x| x.name == 'number' }
+ assert_not column.null, "column should not be null"
+ end
end
def test_indexes_logs
- assert_difference('@subscriber.logged.length') do
- @conn.indexes('items')
+ with_example_table do
+ assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
+ @conn.indexes('ex')
+ end
end
- assert_match(/items/, @subscriber.logged.last.first)
end
def test_no_indexes
@@ -348,41 +364,45 @@ module ActiveRecord
end
def test_index
- @conn.add_index 'items', 'id', :unique => true, :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
+ with_example_table do
+ @conn.add_index 'ex', 'id', unique: true, name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
- assert_equal 'items', index.table
- assert index.unique, 'index is unique'
- assert_equal ['id'], index.columns
+ assert_equal 'ex', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
end
def test_non_unique_index
- @conn.add_index 'items', 'id', :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
- assert !index.unique, 'index is not unique'
+ with_example_table do
+ @conn.add_index 'ex', 'id', name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
+ assert_not index.unique, 'index is not unique'
+ end
end
def test_compound_index
- @conn.add_index 'items', %w{ id number }, :name => 'fun'
- index = @conn.indexes('items').find { |idx| idx.name == 'fun' }
- assert_equal %w{ id number }.sort, index.columns.sort
+ with_example_table do
+ @conn.add_index 'ex', %w{ id number }, name: 'fun'
+ index = @conn.indexes('ex').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
end
def test_primary_key
- assert_equal 'id', @conn.primary_key('items')
-
- @conn.execute <<-eosql
- CREATE TABLE foos (
- internet integer PRIMARY KEY AUTOINCREMENT,
- number integer not null
- )
- eosql
- assert_equal 'internet', @conn.primary_key('foos')
+ with_example_table do
+ assert_equal 'id', @conn.primary_key('ex')
+ with_example_table 'internet integer PRIMARY KEY AUTOINCREMENT, number integer not null', 'foos' do
+ assert_equal 'internet', @conn.primary_key('foos')
+ end
+ end
end
def test_no_primary_key
- @conn.execute 'CREATE TABLE failboat (number integer not null)'
- assert_nil @conn.primary_key('failboat')
+ with_example_table 'number integer not null' do
+ assert_nil @conn.primary_key('ex')
+ end
end
def test_supports_extensions
@@ -400,10 +420,21 @@ module ActiveRecord
private
def assert_logged logs
+ subscriber = SQLSubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe('sql.active_record', subscriber)
yield
- assert_equal logs, @subscriber.logged
+ assert_equal logs, subscriber.logged
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
end
+ def with_example_table(definition = nil, table_name = 'ex', &block)
+ definition ||= <<-SQL
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer
+ SQL
+ super(@conn, table_name, definition, &block)
+ end
end
end
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 500df52cd8..811695938e 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -10,7 +10,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::SchemaMigration.drop_table
end
- def teardown
+ teardown do
@connection.drop_table :fruits rescue nil
@connection.drop_table :nep_fruits rescue nil
@connection.drop_table :nep_schema_migrations rescue nil
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index d172ee2e7a..091c94676e 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -340,6 +340,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { line_item.touch }
end
+ def test_belongs_to_with_touch_option_on_touch_without_updated_at_attributes
+ assert_not LineItem.column_names.include?("updated_at")
+
+ line_item = LineItem.create!
+ invoice = Invoice.create!(line_items: [line_item])
+ initial = invoice.updated_at
+ line_item.touch
+
+ assert_not_equal initial, invoice.reload.updated_at
+ end
+
def test_belongs_to_with_touch_option_on_touch_and_removed_parent
line_item = LineItem.create!
Invoice.create!(line_items: [line_item])
@@ -824,6 +835,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, comments(:greetings).reload.children_count
end
+ def test_belongs_to_with_id_assigning
+ post = posts(:welcome)
+ comment = Comment.create! body: "foo", post: post
+ parent = comments(:greetings)
+ assert_equal 0, parent.reload.children_count
+ comment.parent_id = parent.id
+
+ comment.save!
+ assert_equal 1, parent.reload.children_count
+ end
+
def test_polymorphic_with_custom_primary_key
toy = Toy.create!
sponsor = Sponsor.create!(:sponsorable => toy)
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 2d0d4541b4..cf71bc1597 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -101,6 +101,27 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
"after_adding#{david.id}"], ar.developers_log
end
+ def test_has_and_belongs_to_many_before_add_called_before_save
+ dev = nil
+ new_dev = nil
+ klass = Class.new(Project) do
+ def self.name; Project.name; end
+ has_and_belongs_to_many :developers_with_callbacks,
+ :class_name => "Developer",
+ :before_add => lambda { |o,r|
+ dev = r
+ new_dev = r.new_record?
+ }
+ end
+ rec = klass.create!
+ alice = Developer.new(:name => 'alice')
+ rec.developers_with_callbacks << alice
+ assert_equal alice, dev
+ assert_not_nil new_dev
+ assert new_dev, "record should not have been saved"
+ assert_not alice.new_record?
+ end
+
def test_has_and_belongs_to_many_after_add_called_after_save
ar = projects(:active_record)
assert ar.developers_log.empty?
@@ -138,7 +159,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
activerecord.reload
assert activerecord.developers_with_callbacks.size == 2
end
- log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
+ log_array = activerecord.developers_with_callbacks.flat_map {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.sort
assert activerecord.developers_with_callbacks.clear
assert_equal log_array, activerecord.developers_log.sort
end
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 5ff117eaa0..0ff87d53ea 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -68,7 +68,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
generate_test_object_graphs
end
- def teardown
+ teardown do
[Circle, Square, Triangle, PaintColor, PaintTexture,
ShapeExpression, NonPolyOne, NonPolyTwo].each do |c|
c.delete_all
@@ -111,7 +111,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
@first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post)
end
- def teardown
+ teardown do
@davey_mcdave.destroy
@first_post.destroy
@first_comment.destroy
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index b12bc355e8..a61a070331 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -90,7 +90,7 @@ class EagerSingularizationTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
connection.drop_table :viri
connection.drop_table :octopi
connection.drop_table :passes
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 498a4e8144..8c9797861c 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -235,6 +235,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_finding_with_includes_on_empty_polymorphic_type_column
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ sponsor.update!(sponsorable_type: '', sponsorable_id: nil) # sponsorable_type column might be declared NOT NULL
+ sponsor = assert_queries(1) do
+ assert_nothing_raised { Sponsor.all.merge!(:includes => :sponsorable).find(sponsor.id) }
+ end
+ assert_no_queries do
+ assert_equal nil, sponsor.sponsorable
+ end
+ end
+
def test_loading_from_an_association
posts = authors(:david).posts.merge(:includes => :comments, :order => "posts.id").to_a
assert_equal 2, posts.first.comments.size
@@ -407,19 +418,19 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_load_has_one_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael))
+ michael = Person.all.merge!(:includes => :favourite_reference).find(people(:michael).id)
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
end
def test_eager_load_has_many_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :references).find(people(:michael))
+ michael = Person.all.merge!(:includes => :references).find(people(:michael).id)
references(:michael_magician,:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
end
def test_eager_load_has_many_through_quotes_table_and_column_names
- michael = Person.all.merge!(:includes => :jobs).find(people(:michael))
+ michael = Person.all.merge!(:includes => :jobs).find(people(:michael).id)
jobs(:magician, :unicyclist)
assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
end
@@ -709,16 +720,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_invalid_association_reference
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=> :monkeys ).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=>[ :monkeys ]).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
Post.all.merge!(:includes=>[ 'monkeys' ]).find(6)
}
- assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
+ assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
Post.all.merge!(:includes=>[ :monkeys, :elephants ]).find(6)
}
end
@@ -1194,4 +1205,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
authors(:david).essays.includes(:writer).any?
end
end
+
+ test "preloading associations with string joins and order references" do
+ author = assert_queries(2) {
+ Author.includes(:posts).joins("LEFT JOIN posts ON posts.author_id = authors.id").order("posts.title DESC").first
+ }
+ assert_no_queries {
+ assert_equal 5, author.posts.size
+ }
+ end
+
+ test "including associations with where.not adds implicit references" do
+ author = assert_queries(2) {
+ Author.includes(:posts).where.not(posts: { title: 'Welcome to the weblog'} ).last
+ }
+
+ assert_no_queries {
+ assert_equal 2, author.posts.size
+ }
+ end
end
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 bac1cb8e2d..366472c6fd 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
@@ -11,6 +11,7 @@ require 'models/author'
require 'models/tag'
require 'models/tagging'
require 'models/parrot'
+require 'models/person'
require 'models/pirate'
require 'models/treasure'
require 'models/price_estimate'
@@ -795,4 +796,27 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_create
+ rich_person = RichPerson.new
+
+ treasure = Treasure.new
+ treasure.rich_people << rich_person
+ treasure.valid?
+
+ assert_equal 1, treasure.rich_people.size
+ assert_nil rich_person.first_name, 'should not run associated person validation on create when validate: false'
+ end
+
+ def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_update
+ rich_person = RichPerson.create!
+ person_first_name = rich_person.first_name
+ assert_not_nil person_first_name
+
+ treasure = Treasure.new
+ treasure.rich_people << rich_person
+ treasure.valid?
+
+ assert_equal 1, treasure.rich_people.size
+ assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false'
+ 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 321440cab7..c79c0c87c5 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -22,8 +22,10 @@ require 'models/engine'
require 'models/categorization'
require 'models/minivan'
require 'models/speedometer'
-require 'models/pirate'
-require 'models/ship'
+require 'models/reference'
+require 'models/job'
+require 'models/college'
+require 'models/student'
class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
fixtures :authors, :posts, :comments
@@ -41,7 +43,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
:people, :posts, :readers, :taggings, :cars, :essays,
- :categorizations
+ :categorizations, :jobs
def setup
Client.destroyed_client_ids.clear
@@ -65,6 +67,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
dev.developer_projects.map(&:project_id).sort
end
+ def test_has_many_build_with_options
+ college = College.create(name: 'UFMT')
+ Student.create(active: true, college_id: college.id, name: 'Sarah')
+
+ assert_equal college.students, Student.where(active: true, college_id: college.id)
+ end
+
def test_create_from_association_should_respect_default_scope
car = Car.create(:name => 'honda')
assert_equal 'honda', car.name
@@ -109,6 +118,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy"
end
+ def test_delete_all_on_association_is_the_same_as_not_loaded
+ author = authors :david
+ author.thinking_posts.create!(:body => "test")
+ author.reload
+ expected_sql = capture_sql { author.thinking_posts.delete_all }
+
+ author.thinking_posts.create!(:body => "test")
+ author.reload
+ author.thinking_posts.inspect
+ loaded_sql = capture_sql { author.thinking_posts.delete_all }
+ assert_equal(expected_sql, loaded_sql)
+ end
+
def test_building_the_associated_object_with_implicit_sti_base_class
firm = DependentFirm.new
company = firm.companies.build
@@ -1244,6 +1266,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal orig_accounts, firm.accounts
end
+ def test_replace_with_same_content
+ firm = Firm.first
+ firm.clients = []
+ firm.save
+
+ assert_queries(0, ignore_none: true) do
+ firm.clients = []
+ end
+ end
+
def test_transactions_when_replacing_on_persisted
good = Client.new(:name => "Good")
bad = Client.new(:name => "Bad", :raise_on_save => true)
@@ -1832,12 +1864,4 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
end
-
- test 'has_many_association passes context validation to validate children' do
- pirate = FamousPirate.new
- pirate.famous_ships << ship = FamousShip.new
- assert_equal true, pirate.valid?
- assert_equal false, pirate.valid?(:conference)
- assert_equal "can't be blank", ship.errors[:name].first
- end
end
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 47592f312e..026a7fe635 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -817,6 +817,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert author.named_categories(true).include?(category)
end
+ def test_collection_exists
+ author = authors(:mary)
+ category = Category.create!(author_ids: [author.id], name: "Primary")
+ assert category.authors.exists?(id: author.id)
+ assert category.reload.authors.exists?(id: author.id)
+ end
+
def test_collection_delete_with_nonstandard_primary_key_on_belongs_to
author = authors(:mary)
category = author.named_categories.create(:name => "Primary")
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index a9efa6d86a..b23517b2f9 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -117,4 +117,13 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
assert_equal [author], Author.where(id: author).joins(:special_categorizations)
end
+
+ test "the default scope of the target is correctly aliased when joining associations" do
+ author = Author.create! name: "Jon"
+ author.categories.create! name: 'Not Special'
+ author.special_categories.create! name: 'Special'
+
+ categories = author.categories.includes(:special_categorizations).references(:special_categorizations).to_a
+ assert_equal 2, categories.size
+ end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 6c581a432f..952baaca36 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -22,7 +22,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
@target.table_name = 'topics'
end
- def teardown
+ teardown do
ActiveRecord::Base.send(:attribute_method_matchers).clear
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
@@ -555,6 +555,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_converted_values_are_returned_after_assignment
+ developer = Developer.new(name: 1337, salary: "50000")
+
+ assert_equal "50000", developer.salary_before_type_cast
+ assert_equal 1337, developer.name_before_type_cast
+
+ assert_equal 50000, developer.salary
+ assert_equal "1337", developer.name
+
+ developer.save!
+
+ assert_equal "50000", developer.salary_before_type_cast
+ assert_equal 1337, developer.name_before_type_cast
+
+ assert_equal 50000, developer.salary
+ assert_equal "1337", developer.name
+ end
+
def test_write_nil_to_time_attributes
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
@@ -728,19 +746,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert "unknown attribute: hello", error.message
end
- def test_read_attribute_overwrites_private_method_not_considered_implemented
- # simulate a model with a db column that shares its name an inherited
- # private method (e.g. Object#system)
- #
- Object.class_eval do
- private
- def title; "private!"; end
+ def test_methods_override_in_multi_level_subclass
+ klass = Class.new(Developer) do
+ def name
+ "dev:#{read_attribute(:name)}"
+ end
+ end
+
+ 2.times { klass = Class.new klass }
+ dev = klass.new(name: 'arthurnn')
+ dev.save!
+ assert_equal 'dev:arthurnn', dev.reload.name
+ end
+
+ def test_global_methods_are_overwritten
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = 'computers'
+ end
+
+ assert !klass.instance_method_already_implemented?(:system)
+ computer = klass.new
+ assert_nil computer.system
+ end
+
+ def test_global_methods_are_overwritte_when_subclassing
+ klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }
+
+ subklass = Class.new(klass) do
+ self.table_name = 'computers'
end
- assert !@target.instance_method_already_implemented?(:title)
- topic = @target.new
- assert_nil topic.title
- Object.send(:undef_method, :title) # remove test method from object
+ assert !klass.instance_method_already_implemented?(:system)
+ assert !subklass.instance_method_already_implemented?(:system)
+ computer = subklass.new
+ assert_nil computer.system
end
def test_instance_method_should_be_defined_on_the_base_class
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index c55dd685a1..f7584c3a51 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -21,6 +21,31 @@ require 'models/electron'
require 'models/molecule'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
+ def test_autosave_validation
+ person = Class.new(ActiveRecord::Base) {
+ self.table_name = 'people'
+ validate :should_be_cool, :on => :create
+ def self.name; 'Person'; end
+
+ private
+
+ def should_be_cool
+ unless self.first_name == 'cool'
+ errors.add :first_name, "not cool"
+ end
+ end
+ }
+ reference = Class.new(ActiveRecord::Base) {
+ self.table_name = "references"
+ def self.name; 'Reference'; end
+ belongs_to :person, autosave: true, class: person
+ }
+
+ u = person.create!(first_name: 'cool')
+ u.update_attributes!(first_name: 'nah') # still valid because validation only applies on 'create'
+ assert reference.create!(person: u).persisted?
+ end
+
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
assert_no_difference_when_adding_callbacks_twice_for Pirate, :ship
end
@@ -39,10 +64,6 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
private
- def base
- ActiveRecord::Base
- end
-
def assert_no_difference_when_adding_callbacks_twice_for(model, association_name)
reflection = model.reflect_on_association(association_name)
assert_no_difference "callbacks_for_model(#{model.name}).length" do
@@ -51,9 +72,9 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
end
def callbacks_for_model(model)
- model.instance_variables.grep(/_callbacks$/).map do |ivar|
+ model.instance_variables.grep(/_callbacks$/).flat_map do |ivar|
model.instance_variable_get(ivar)
- end.flatten
+ end
end
end
@@ -597,15 +618,15 @@ end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- def setup
- super
+ setup do
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
- def teardown
+ teardown do
# We are running without transactional fixtures and need to cleanup.
Bird.delete_all
+ Parrot.delete_all
@ship.delete
@pirate.delete
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 8a0b0b9589..4969344763 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -212,7 +212,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) || mysql_56?
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -533,6 +533,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_equality_of_new_records
assert_not_equal Topic.new, Topic.new
+ assert_equal false, Topic.new == Topic.new
end
def test_equality_of_destroyed_records
@@ -544,6 +545,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal topic_2, topic_1
end
+ def test_equality_with_blank_ids
+ one = Subscriber.new(:id => '')
+ two = Subscriber.new(:id => '')
+ assert_equal one, two
+ end
+
def test_hashing
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
@@ -578,12 +585,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal nil, Topic.find_by_id(topic.id)
end
- def test_blank_ids
- one = Subscriber.new(:id => '')
- two = Subscriber.new(:id => '')
- assert_equal one, two
- end
-
def test_comparison_with_different_objects
topic = Topic.create
category = Category.create(:name => "comparison")
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 291751c435..40f73cd68c 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -20,13 +20,13 @@ module ActiveRecord
def setup
super
@connection = ActiveRecord::Base.connection
- @listener = LogListener.new
+ @subscriber = LogListener.new
@pk = Topic.columns.find { |c| c.primary }
- ActiveSupport::Notifications.subscribe('sql.active_record', @listener)
+ @subscription = ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber)
end
- def teardown
- ActiveSupport::Notifications.unsubscribe(@listener)
+ teardown do
+ ActiveSupport::Notifications.unsubscribe(@subscription)
end
if ActiveRecord::Base.connection.supports_statement_cache?
@@ -37,7 +37,7 @@ module ActiveRecord
@connection.exec_query(sql, 'SQL', binds)
- message = @listener.calls.find { |args| args[4][:sql] == sql }
+ message = @subscriber.calls.find { |args| args[4][:sql] == sql }
assert_equal binds, message[4][:binds]
end
@@ -48,14 +48,14 @@ module ActiveRecord
@connection.exec_query(sql, 'SQL', binds)
- message = @listener.calls.find { |args| args[4][:sql] == sql }
+ message = @subscriber.calls.find { |args| args[4][:sql] == sql }
assert_equal [[@pk, 3]], message[4][:binds]
end
def test_find_one_uses_binds
Topic.find(1)
binds = [[@pk, 1]]
- message = @listener.calls.find { |args| args[4][:binds] == binds }
+ message = @subscriber.calls.find { |args| args[4][:binds] == binds }
assert message, 'expected a message with binds'
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index c7b64f29c3..c1dd1f1c69 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -127,13 +127,13 @@ module ActiveRecord
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Identity.new
+ oid = PostgreSQLAdapter::OID::Integer.new
bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
assert_equal :integer, bigint_column.type
end
def test_smallint_column_should_map_to_integer
- oid = PostgreSQLAdapter::OID::Identity.new
+ oid = PostgreSQLAdapter::OID::Integer.new
smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index eb2fe5639b..deed226eab 100644
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -29,12 +29,6 @@ module ActiveRecord
assert_not adapter.lease, 'should not lease adapter'
end
- def test_last_use
- assert_not adapter.last_use
- adapter.lease
- assert adapter.last_use
- end
-
def test_expire_mutates_in_use
assert adapter.lease, 'lease adapter'
assert adapter.in_use?, 'adapter is in use'
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 318cc5a32c..e097449029 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -13,10 +13,72 @@ module ActiveRecord
@previous_database_url = ENV.delete("DATABASE_URL")
end
- def teardown
+ teardown do
ENV["DATABASE_URL"] = @previous_database_url
end
+ def resolve(spec, config)
+ ConnectionSpecification::Resolver.new(klass.new(config).resolve).resolve(spec)
+ end
+
+ def spec(spec, config)
+ ConnectionSpecification::Resolver.new(klass.new(config).resolve).spec(spec)
+ end
+
+ def test_resolver_with_database_uri_and_known_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve(:production, config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_known_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = assert_deprecated { resolve("production", config) }
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_unknown_symbol_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = resolve(:production, config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_resolver_with_database_uri_and_unknown_string_key
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ assert_raises AdapterNotSpecified do
+ spec("production", config)
+ end
+ end
+
+ def test_resolver_with_database_uri_and_supplied_url
+ ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo"
+ config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } }
+ actual = resolve("postgres://localhost/foo", config)
+ expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expected, actual
+ end
+
+ def test_jdbc_url
+ config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } }
+ actual = klass.new(config).resolve
+ assert_equal config, actual
+ end
+
+ def test_environment_does_not_exist_in_config_url_does_exist
+ ENV['DATABASE_URL'] = "postgres://localhost/foo"
+ config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } }
+ actual = klass.new(config).resolve
+ expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" }
+ assert_equal expect_prod, actual["production"]
+ end
+
def test_string_connection
config = { "production" => "postgres://localhost/foo" }
actual = klass.new(config).resolve
@@ -69,21 +131,6 @@ module ActiveRecord
assert_equal nil, actual[:test]
end
- def test_sting_with_database_url
- ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
-
- config = { "production" => "postgres://localhost/foo" }
- actual = klass.new(config).resolve
-
- expected = { "production" =>
- { "adapter" => "postgresql",
- "database" => "foo",
- "host" => "localhost"
- }
- }
- assert_equal expected, actual
- end
-
def test_url_sub_key_with_database_url
ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO"
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 1cf215017b..8d15a76735 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'active_support/concurrency/latch'
module ActiveRecord
module ConnectionAdapters
@@ -22,8 +23,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
@pool.disconnect!
end
@@ -89,10 +89,9 @@ module ActiveRecord
end
def test_full_pool_exception
+ @pool.size.times { @pool.checkout }
assert_raises(ConnectionTimeoutError) do
- (@pool.size + 1).times do
- @pool.checkout
- end
+ @pool.checkout
end
end
@@ -125,7 +124,6 @@ module ActiveRecord
@pool.checkout
@pool.checkout
@pool.checkout
- @pool.dead_connection_timeout = 0
connections = @pool.connections.dup
@@ -135,21 +133,25 @@ module ActiveRecord
end
def test_reap_inactive
+ ready = ActiveSupport::Concurrency::Latch.new
@pool.checkout
- @pool.checkout
- @pool.checkout
- @pool.dead_connection_timeout = 0
-
- connections = @pool.connections.dup
- connections.each do |conn|
- conn.extend(Module.new { def active_threadsafe?; false; end; })
+ child = Thread.new do
+ @pool.checkout
+ @pool.checkout
+ ready.release
+ Thread.stop
end
+ ready.await
+
+ assert_equal 3, active_connections(@pool).size
+ child.terminate
+ child.join
@pool.reap
- assert_equal 0, @pool.connections.length
+ assert_equal 1, active_connections(@pool).size
ensure
- connections.each(&:close)
+ @pool.connections.each(&:close)
end
def test_remove_connection
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index fdd1914cba..3c2f5d4219 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -82,15 +82,34 @@ module ActiveRecord
assert_equal password, spec["password"]
end
- def test_url_host_db_for_sqlite3
- spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test'
+ def test_url_with_authority_for_sqlite3
+ spec = resolve 'sqlite3:///foo_test'
assert_equal('/foo_test', spec["database"])
end
- def test_url_host_memory_db_for_sqlite3
- spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:'
+ def test_url_absolute_path_for_sqlite3
+ spec = resolve 'sqlite3:/foo_test'
+ assert_equal('/foo_test', spec["database"])
+ end
+
+ def test_url_relative_path_for_sqlite3
+ spec = resolve 'sqlite3:foo_test'
+ assert_equal('foo_test', spec["database"])
+ end
+
+ def test_url_memory_db_for_sqlite3
+ spec = resolve 'sqlite3::memory:'
assert_equal(':memory:', spec["database"])
end
+
+ def test_url_sub_key_for_sqlite3
+ spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'}
+ assert_equal({
+ "adapter" => "sqlite3",
+ "database" => "foo",
+ "encoding" => "utf8" }, spec)
+ end
+
end
end
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 7e3d91e08c..7d438803a1 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -206,7 +206,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db"
end
- def teardown
+ teardown do
@connection.schema_search_path = @old_search_path
Default.reset_column_information
end
diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb
index 9e268dad74..94447addc1 100644
--- a/activerecord/test/cases/disconnected_test.rb
+++ b/activerecord/test/cases/disconnected_test.rb
@@ -10,7 +10,7 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase
@connection = ActiveRecord::Base.connection
end
- def teardown
+ teardown do
return if in_memory_db?
spec = ActiveRecord::Base.connection_config
ActiveRecord::Base.establish_connection(spec)
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 1b95708cb3..47de3dec98 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -194,6 +194,7 @@ class EnumTest < ActiveRecord::TestCase
:valid, # generates #valid?, which conflicts with an AR method
:save, # generates #save!, which conflicts with an AR method
:proposed, # same value as an existing enum
+ :public, :private, :protected, # generates a method that conflict with ruby words
]
conflicts.each_with_index do |value, i|
@@ -222,4 +223,31 @@ class EnumTest < ActiveRecord::TestCase
end
end
end
+
+ test "validate uniqueness" do
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Book'; end
+ enum status: [:proposed, :written]
+ validates_uniqueness_of :status
+ end
+ klass.delete_all
+ klass.create!(status: "proposed")
+ book = klass.new(status: "written")
+ assert book.valid?
+ book.status = "proposed"
+ assert_not book.valid?
+ end
+
+ test "validate inclusion of value in array" do
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Book'; end
+ enum status: [:proposed, :written]
+ validates_inclusion_of :status, in: ["written"]
+ end
+ klass.delete_all
+ invalid_book = klass.new(status: "proposed")
+ assert_not invalid_book.valid?
+ valid_book = klass.new(status: "written")
+ assert valid_book.valid?
+ end
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index b00e2744b9..8de2ddb10d 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -48,7 +48,7 @@ if ActiveRecord::Base.connection.supports_explain?
assert queries.empty?
end
- def teardown
+ teardown do
ActiveRecord::ExplainRegistry.reset
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index b1eded6494..c0440744e9 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -33,6 +33,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ def test_find_passing_active_record_object_is_deprecated
+ assert_deprecated do
+ Topic.find(Topic.last)
+ end
+ end
+
def test_symbols_table_ref
Post.first # warm up
x = Symbol.all_symbols.count
@@ -56,17 +62,33 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
- assert_equal false, Topic.exists?(Topic.new)
+ assert_equal false, Topic.exists?(Topic.new.id)
- begin
- assert_equal false, Topic.exists?("foo")
- rescue ActiveRecord::StatementInvalid
- # PostgreSQL complains about string comparison with integer field
- rescue Exception
- flunk
+ assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ end
+
+ def test_exists_passing_active_record_object_is_deprecated
+ assert_deprecated do
+ Topic.exists?(Topic.new)
end
+ end
- assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ def test_exists_fails_when_parameter_has_invalid_type
+ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter)
+ assert_raises ActiveRecord::StatementInvalid do
+ Topic.exists?(("9"*53).to_i) # number that's bigger than int
+ end
+ else
+ assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_raises ActiveRecord::StatementInvalid do
+ Topic.exists?("foo")
+ end
+ else
+ assert_equal false, Topic.exists?("foo")
+ end
end
def test_exists_does_not_select_columns_without_alias
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 37c6af74da..cf0235b8c5 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -254,7 +254,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_fixtures_are_set_up_with_database_env_variable
db_url_tmp = ENV['DATABASE_URL']
- ENV['DATABASE_URL'] = "sqlite3:///:memory:"
+ ENV['DATABASE_URL'] = "sqlite3::memory:"
ActiveRecord::Base.stubs(:configurations).returns({})
test_case = Class.new(ActiveRecord::TestCase) do
fixtures :accounts
@@ -782,6 +782,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase
assert_equal("frederick", parrots(:frederick).name)
end
+ def test_supports_label_string_interpolation
+ assert_equal("X marks the spot!", pirates(:mark).catchphrase)
+ end
+
def test_supports_polymorphic_belongs_to
assert_equal(pirates(:redbeard), treasures(:sapphire).looter)
assert_equal(parrots(:louis), treasures(:ruby).looter)
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 3758224b0c..5ed508a799 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -41,6 +41,11 @@ def in_memory_db?
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
+def mysql_56?
+ current_adapter?(:Mysql2Adapter) &&
+ ActiveRecord::Base.connection.send(:version).join(".") >= "5.6.0"
+end
+
def supports_savepoints?
ActiveRecord::Base.connection.supports_savepoints?
end
@@ -163,7 +168,7 @@ class SQLSubscriber
def start(name, id, payload)
@payloads << payload
- @logged << [payload[:sql], payload[:name], payload[:binds]]
+ @logged << [payload[:sql].squish, payload[:name], payload[:binds]]
end
def finish(name, id, payload); end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index e2ff2aa451..f5f85f2412 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -339,7 +339,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
ActiveSupport::Dependencies.log_activity = true
end
- def teardown
+ teardown do
ActiveSupport::Dependencies.log_activity = false
self.class.const_remove :FirmOnTheFly rescue nil
Firm.const_remove :FirmOnTheFly rescue nil
diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb
index f6774d7ef4..8416c81f45 100644
--- a/activerecord/test/cases/invalid_connection_test.rb
+++ b/activerecord/test/cases/invalid_connection_test.rb
@@ -12,7 +12,7 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase
Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist'
end
- def teardown
+ teardown do
Bird.remove_connection
end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index debacf815c..285172d33e 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -122,7 +122,7 @@ module ActiveRecord
end
end
- def teardown
+ teardown do
%w[horses new_horses].each do |table|
if ActiveRecord::Base.connection.table_exists?(table)
ActiveRecord::Base.connection.drop_table(table)
@@ -271,16 +271,19 @@ module ActiveRecord
ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
end
- def test_migrate_revert_add_index_with_name
- RevertNamedIndexMigration1.new.migrate(:up)
- RevertNamedIndexMigration2.new.migrate(:up)
- RevertNamedIndexMigration2.new.migrate(:down)
-
- connection = ActiveRecord::Base.connection
- assert connection.index_exists?(:horses, :content),
- "index on content should exist"
- assert !connection.index_exists?(:horses, :content, name: "horses_index_named"),
- "horses_index_named index should not exist"
+ # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns
+ unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :OracleAdapter)
+ def test_migrate_revert_add_index_with_name
+ RevertNamedIndexMigration1.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:up)
+ RevertNamedIndexMigration2.new.migrate(:down)
+
+ connection = ActiveRecord::Base.connection
+ assert connection.index_exists?(:horses, :content),
+ "index on content should exist"
+ assert !connection.index_exists?(:horses, :content, name: "horses_index_named"),
+ "horses_index_named index should not exist"
+ end
end
end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 294f2eb9fe..5418d913b0 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -11,8 +11,7 @@ module ActiveRecord
@table_name = :testings
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index c1d7cd5874..a6d506b04a 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
@connection = Minitest::Mock.new
end
- def teardown
+ teardown do
assert @connection.verify
end
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
index 87e29e41ba..77a752f050 100644
--- a/activerecord/test/cases/migration/column_positioning_test.rb
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -18,8 +18,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index efaec0f823..62b60f7f7b 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -10,8 +10,7 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
- def teardown
- super
+ teardown do
%w(artists_musics musics_videos catalog).each do |table_name|
connection.drop_table table_name if connection.tables.include?(table_name)
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 8d1daa0a04..35af11f672 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -21,8 +21,7 @@ module ActiveRecord
end
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
ActiveRecord::Base.primary_key_prefix_type = nil
end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index 97efb94b66..84224e6e4c 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -19,8 +19,7 @@ module ActiveRecord
ActiveRecord::SchemaMigration.delete_all
end
- def teardown
- super
+ teardown do
ActiveRecord::SchemaMigration.drop_table
end
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
index 19eb7d3c9e..4485701a4e 100644
--- a/activerecord/test/cases/migration/references_index_test.rb
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -11,8 +11,7 @@ module ActiveRecord
@table_name = :testings
end
- def teardown
- super
+ teardown do
connection.drop_table :testings rescue nil
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 1bda472d23..455ec78f68 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -33,7 +33,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.schema_cache.clear!
end
- def teardown
+ teardown do
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
@@ -585,7 +585,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
Person.reset_sequence_name
end
- def teardown
+ teardown do
Person.connection.drop_table(:delete_me) rescue nil
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 3f9854200d..c77a818b93 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -26,8 +26,7 @@ module ActiveRecord
ActiveRecord::SchemaMigration.delete_all rescue nil
end
- def teardown
- super
+ teardown do
ActiveRecord::SchemaMigration.delete_all rescue nil
ActiveRecord::Migration.verbose = true
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 9124105e6d..f7db195521 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -18,7 +18,7 @@ class ModulesTest < ActiveRecord::TestCase
ActiveRecord::Base.store_full_sti_class = false
end
- def teardown
+ teardown do
# reinstate the constants that we undefined in the setup
@undefined_consts.each do |constant, value|
Object.send :const_set, constant, value unless value.nil?
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 2f89699df7..cf96c3fccf 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -11,24 +11,8 @@ require "models/owner"
require "models/pet"
require 'active_support/hash_with_indifferent_access'
-module AssertRaiseWithMessage
- def assert_raise_with_message(expected_exception, expected_message)
- begin
- error_raised = false
- yield
- rescue expected_exception => error
- error_raised = true
- actual_message = error.message
- end
- assert error_raised
- assert_equal expected_message, actual_message
- end
-end
-
class TestNestedAttributesInGeneral < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
- def teardown
+ teardown do
Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
end
@@ -71,9 +55,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_raise_an_ArgumentError_for_non_existing_associations
- assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
+ exception = assert_raise ArgumentError do
Pirate.accepts_nested_attributes_for :honesty
end
+ assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message
end
def test_should_disable_allow_destroy_by_default
@@ -213,17 +198,16 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
def setup
@pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to
- assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do
+ exception = assert_raise ArgumentError do
Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"})
end
+ assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message
end
def test_should_define_an_attribute_writer_method_for_the_association
@@ -275,9 +259,10 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@pirate.ship_attributes = { :id => 1234567890 }
end
+ assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@@ -403,8 +388,6 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
- include AssertRaiseWithMessage
-
def setup
@ship = Ship.new(:name => 'Nights Dirty Lightning')
@pirate = @ship.build_pirate(:catchphrase => 'Aye')
@@ -460,9 +443,10 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@ship.pirate_attributes = { :id => 1234567890 }
end
+ assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message
end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@@ -579,8 +563,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
module NestedAttributesOnACollectionAssociationTests
- include AssertRaiseWithMessage
-
def test_should_define_an_attribute_writer_method_for_the_association
assert_respond_to @pirate, association_setter
end
@@ -670,9 +652,10 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ exception = assert_raise ActiveRecord::RecordNotFound do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end
+ assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message
end
def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing
@@ -727,9 +710,10 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
- assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do
+ exception = assert_raise ArgumentError do
@pirate.send(association_setter, "foo")
end
+ assert_equal 'Hash or Array expected, got String ("foo")', exception.message
end
def test_should_work_with_update_as_well
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b9f0624f76..046fe83e54 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -452,7 +452,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_attribute_for_updated_at_on
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_attribute(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
@@ -523,7 +523,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_column_should_not_modify_updated_at
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_column(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
@@ -620,7 +620,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_columns_should_not_modify_updated_at
developer = Developer.find(1)
- prev_month = Time.now.prev_month
+ prev_month = Time.now.prev_month.change(usec: 0)
developer.update_columns(updated_at: prev_month)
assert_equal prev_month, developer.updated_at
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 626c6aeaf8..dd0e934ec2 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -10,7 +10,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase
@connection = ActiveRecord::Base.remove_connection
end
- def teardown
+ teardown do
ActiveRecord::Base.clear_all_connections!
ActiveRecord::Base.establish_connection(@connection)
@per_test_teardown.each {|td| td.call }
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index da8ae672fe..9d89d6a1e8 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -118,6 +118,14 @@ class QueryCacheTest < ActiveRecord::TestCase
assert ActiveRecord::Base.connection.query_cache.empty?, 'cache should be empty'
end
+ def test_cache_passing_a_relation
+ post = Post.first
+ Post.cache do
+ query = post.categories.select(:post_id)
+ assert Post.connection.select_all(query).is_a?(ActiveRecord::Result)
+ end
+ end
+
def test_find_queries
assert_queries(2) { Task.find(1); Task.find(1) }
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index b62a41c08e..f52fd22489 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -10,8 +10,7 @@ module ActiveRecord
@pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
end
- def teardown
- super
+ teardown do
@pool.connections.each(&:close)
end
@@ -64,17 +63,22 @@ module ActiveRecord
spec.config[:reaping_frequency] = 0.0001
pool = ConnectionPool.new spec
- pool.dead_connection_timeout = 0
- conn = pool.checkout
- count = pool.connections.length
+ conn = nil
+ child = Thread.new do
+ conn = pool.checkout
+ Thread.stop
+ end
+ Thread.pass while conn.nil?
+
+ assert conn.in_use?
- conn.extend(Module.new { def active_threadsafe?; false; end; })
+ child.terminate
- while count == pool.connections.length
+ while conn.in_use?
Thread.pass
end
- assert_equal(count - 1, pool.connections.length)
+ assert !conn.in_use?
end
end
end
diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb
index 14a8d97d36..4057835688 100644
--- a/activerecord/test/cases/relation/predicate_builder_test.rb
+++ b/activerecord/test/cases/relation/predicate_builder_test.rb
@@ -5,10 +5,10 @@ module ActiveRecord
class PredicateBuilderTest < ActiveRecord::TestCase
def test_registering_new_handlers
PredicateBuilder.register_handler(Regexp, proc do |column, value|
- Arel::Nodes::InfixOperation.new('~', column, value.source)
+ Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source))
end)
- assert_match %r{["`]topics["`].["`]title["`] ~ 'rails'}i, Topic.where(title: /rails/).to_sql
+ assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql
end
end
end
diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb
index fd2420cb88..c628ca44ff 100644
--- a/activerecord/test/cases/relation/where_chain_test.rb
+++ b/activerecord/test/cases/relation/where_chain_test.rb
@@ -12,13 +12,13 @@ module ActiveRecord
end
def test_not_eq
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
+ expected = Post.arel_table[@name].not_eq('hello')
relation = Post.where.not(title: 'hello')
assert_equal([expected], relation.where_values)
end
def test_not_null
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], nil)
+ expected = Post.arel_table[@name].not_eq(nil)
relation = Post.where.not(title: nil)
assert_equal([expected], relation.where_values)
end
@@ -30,13 +30,13 @@ module ActiveRecord
end
def test_not_in
- expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye])
+ expected = Post.arel_table[@name].not_in(%w[hello goodbye])
relation = Post.where.not(title: %w[hello goodbye])
assert_equal([expected], relation.where_values)
end
def test_association_not_eq
- expected = Arel::Nodes::NotEqual.new(Comment.arel_table[@name], 'hello')
+ expected = Comment.arel_table[@name].not_eq('hello')
relation = Post.joins(:comments).where.not(comments: {title: 'hello'})
assert_equal(expected.to_sql, relation.where_values.first.to_sql)
end
@@ -44,20 +44,20 @@ module ActiveRecord
def test_not_eq_with_preceding_where
relation = Post.where(title: 'hello').where.not(title: 'world')
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'hello')
+ expected = Post.arel_table[@name].eq('hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'world')
+ expected = Post.arel_table[@name].not_eq('world')
assert_equal(expected, relation.where_values.last)
end
def test_not_eq_with_succeeding_where
relation = Post.where.not(title: 'hello').where(title: 'world')
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello')
+ expected = Post.arel_table[@name].not_eq('hello')
assert_equal(expected, relation.where_values.first)
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'world')
+ expected = Post.arel_table[@name].eq('world')
assert_equal(expected, relation.where_values.last)
end
@@ -76,17 +76,17 @@ module ActiveRecord
def test_chaining_multiple
relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails')
- expected = Arel::Nodes::NotIn.new(Post.arel_table['author_id'], [1, 2])
+ expected = Post.arel_table['author_id'].not_in([1, 2])
assert_equal(expected, relation.where_values[0])
- expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'ruby on rails')
+ expected = Post.arel_table[@name].not_eq('ruby on rails')
assert_equal(expected, relation.where_values[1])
end
def test_rewhere_with_one_condition
relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone')
- expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'alone')
+ expected = Post.arel_table[@name].eq('alone')
assert_equal 1, relation.where_values.size
assert_equal expected, relation.where_values.first
end
@@ -94,8 +94,8 @@ module ActiveRecord
def test_rewhere_with_multiple_overwriting_conditions
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again')
- title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone')
- body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'again')
+ title_expected = Post.arel_table['title'].eq('alone')
+ body_expected = Post.arel_table['body'].eq('again')
assert_equal 2, relation.where_values.size
assert_equal title_expected, relation.where_values.first
@@ -105,8 +105,8 @@ module ActiveRecord
def test_rewhere_with_one_overwriting_condition_and_one_unrelated
relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone')
- title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone')
- body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'world')
+ title_expected = Post.arel_table['title'].eq('alone')
+ body_expected = Post.arel_table['body'].eq('world')
assert_equal 2, relation.where_values.size
assert_equal body_expected, relation.where_values.first
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8718110c36..049c5a0606 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -171,7 +171,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:first).title, topics.first.title
end
-
def test_finding_with_arel_order
topics = Topic.order(Topic.arel_table[:id].asc)
assert_equal 5, topics.to_a.size
@@ -194,8 +193,33 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql
end
+ def test_finding_with_desc_order_with_string
+ topics = Topic.order(id: "desc")
+ assert_equal 5, topics.to_a.size
+ assert_equal [topics(:fifth), topics(:fourth), topics(:third), topics(:second), topics(:first)], topics.to_a
+ end
+
+ def test_finding_with_asc_order_with_string
+ topics = Topic.order(id: 'asc')
+ assert_equal 5, topics.to_a.size
+ assert_equal [topics(:first), topics(:second), topics(:third), topics(:fourth), topics(:fifth)], topics.to_a
+ end
+
+ def test_support_upper_and_lower_case_directions
+ assert_includes Topic.order(id: "ASC").to_sql, "ASC"
+ assert_includes Topic.order(id: "asc").to_sql, "ASC"
+ assert_includes Topic.order(id: :ASC).to_sql, "ASC"
+ assert_includes Topic.order(id: :asc).to_sql, "ASC"
+
+ assert_includes Topic.order(id: "DESC").to_sql, "DESC"
+ assert_includes Topic.order(id: "desc").to_sql, "DESC"
+ assert_includes Topic.order(id: :DESC).to_sql, "DESC"
+ assert_includes Topic.order(id: :desc).to_sql,"DESC"
+ end
+
def test_raising_exception_on_invalid_hash_params
- assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) }
+ e = assert_raise(ArgumentError) { Topic.order(:name, "id DESC", id: :asfsdf) }
+ assert_equal 'Direction "asfsdf" is invalid. Valid directions are: [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]', e.message
end
def test_finding_last_with_arel_order
@@ -549,6 +573,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal expected, actual
end
+ def test_to_sql_on_scoped_proxy
+ auth = Author.first
+ Post.where("1=1").written_by(auth)
+ assert_not auth.posts.to_sql.include?("1=1")
+ end
+
def test_loading_with_one_association_with_non_preload
posts = Post.eager_load(:last_comment).order('comments.id DESC')
post = posts.find { |p| p.id == 1 }
@@ -613,7 +643,7 @@ class RelationTest < ActiveRecord::TestCase
def test_find_with_list_of_ar
author = Author.first
- authors = Author.find([author])
+ authors = Author.find([author.id])
assert_equal author, authors.first
end
@@ -745,7 +775,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.exists?(authors(:mary).id)
assert ! davids.exists?("42")
assert ! davids.exists?(42)
- assert ! davids.exists?(davids.new)
+ assert ! davids.exists?(davids.new.id)
fake = Author.where(:name => 'fake author')
assert ! fake.exists?
@@ -1342,6 +1372,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['comments'], scope.references_values
end
+ def test_automatically_added_where_not_references
+ scope = Post.where.not(comments: { body: "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.where.not('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
def test_automatically_added_having_references
scope = Post.having(:comments => { :body => "Bla" })
assert_equal ['comments'], scope.references_values
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index c085663efb..575eb34a9c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,7 +177,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip
- if current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
@@ -188,7 +188,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+ elsif current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
@@ -319,6 +319,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_citext_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_citext"} =~ output
+ assert_match %r[t.citext "text_citext"], output
+ end
+ end
+
def test_schema_dump_includes_ltrees_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_ltrees"} =~ output
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 170e9a49eb..9a4d8c6740 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -395,4 +395,22 @@ class DefaultScopingTest < ActiveRecord::TestCase
threads.each(&:join)
end
end
+
+ test "additional conditions are ANDed with the default scope" do
+ scope = DeveloperCalledJamis.where(name: "David")
+ assert_equal 2, scope.where_values.length
+ assert_equal [], scope.to_a
+ end
+
+ test "additional conditions in a scope are ANDed with the default scope" do
+ scope = DeveloperCalledJamis.david
+ assert_equal 2, scope.where_values.length
+ assert_equal [], scope.to_a
+ end
+
+ test "a scope can remove the condition from the default scope" do
+ scope = DeveloperCalledJamis.david2
+ assert_equal 1, scope.where_values.length
+ assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
+ end
end
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index f0ad9ebb8a..59ec2dd6a4 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -291,6 +291,9 @@ class NamedScopingTest < ActiveRecord::TestCase
:relation, # private class method on AR::Base
:new, # redefined class method on AR::Base
:all, # a default scope
+ :public,
+ :protected,
+ :private
]
non_conflicts = [
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 0018fc06f2..d8a467ec4d 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -192,8 +192,9 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
Developer.where('salary = 80000').scoping do
Developer.limit(10).scoping do
devs = Developer.all
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
+ sql = devs.to_sql
+ assert_match '(salary = 80000)', sql
+ assert_match 'LIMIT 10', sql
end
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index bc67da8d27..5609cf310c 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -10,8 +10,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
MyObject = Struct.new :attribute1, :attribute2
- def teardown
- super
+ teardown do
Topic.serialize("content")
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 4476ce3410..803a054d7e 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -19,10 +19,14 @@ module ActiveRecord
end
end
- def assert_sql(*patterns_to_match)
+ def capture_sql
SQLCounter.clear_log
yield
- SQLCounter.log_all
+ SQLCounter.log_all.dup
+ end
+
+ def assert_sql(*patterns_to_match)
+ capture_sql { yield }
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 717e0e1866..594b4fb07b 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -71,6 +71,24 @@ class TimestampTest < ActiveRecord::TestCase
assert_equal @previously_updated_at, @developer.updated_at
end
+ def test_saving_when_callback_sets_record_timestamps_to_false_doesnt_update_its_timestamp
+ klass = Class.new(Developer) do
+ before_update :cancel_record_timestamps
+ def cancel_record_timestamps
+ self.record_timestamps = false
+ return true
+ end
+ end
+
+ developer = klass.first
+ previously_updated_at = developer.updated_at
+
+ developer.name = "New Name"
+ developer.save!
+
+ assert_equal previously_updated_at, developer.updated_at
+ end
+
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
@@ -89,6 +107,18 @@ class TimestampTest < ActiveRecord::TestCase
assert_in_delta Time.now, task.ending, 1
end
+ def test_touching_many_attributes_updates_them
+ task = Task.first
+ previous_starting = task.starting
+ previous_ending = task.ending
+ task.touch(:starting, :ending)
+
+ 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
+ end
+
def test_touching_a_record_without_timestamps_is_unexceptional
assert_nothing_raised { Car.first.touch }
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 1664f1a096..e6ed85394b 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -5,6 +5,7 @@ require 'models/developer'
require 'models/book'
require 'models/author'
require 'models/post'
+require 'models/movie'
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -14,6 +15,11 @@ class TransactionTest < ActiveRecord::TestCase
@first, @second = Topic.find(1, 2).sort_by { |t| t.id }
end
+ def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
+ movie = Movie.create
+ assert !movie.persisted?
+ end
+
def test_raise_after_destroy
assert_not @first.frozen?
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index e82ca3f93d..afb893a52c 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -11,7 +11,7 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
@specification = ActiveRecord::Base.remove_connection
end
- def teardown
+ teardown do
@underlying = nil
ActiveRecord::Base.establish_connection(@specification)
load_schema if in_memory_db?
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index efa0c9b934..3db742c15b 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -14,7 +14,7 @@ class I18nValidationTest < ActiveRecord::TestCase
I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}})
end
- def teardown
+ teardown do
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 74c696c858..18221cc73d 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -223,7 +223,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t_utf8.save, "Should save t_utf8 as unique"
# If database hasn't UTF-8 character set, this test fails
- if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!"
+ if Topic.all.merge!(:select => 'LOWER(title) AS title').find(t_utf8.id).title == "я тоже уникальный!"
t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
assert !t2_utf8.valid?, "Shouldn't be valid"
assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index de618902aa..d80da06e27 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -52,6 +52,21 @@ class ValidationsTest < ActiveRecord::TestCase
assert r.save(:context => :special_case)
end
+ def test_validate
+ r = WrongReply.new
+
+ r.validate
+ assert_empty r.errors[:author_name]
+
+ r.validate(:special_case)
+ assert_not_empty r.errors[:author_name]
+
+ r.author_name = "secret"
+
+ r.validate(:special_case)
+ assert_empty r.errors[:author_name]
+ end
+
def test_invalid_record_exception
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! }
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 78fa2f935a..3cb617497d 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "rexml/document"
require 'models/contact'
require 'models/post'
require 'models/author'
diff --git a/activerecord/test/fixtures/computers.yml b/activerecord/test/fixtures/computers.yml
index daf969d7da..7281a4d768 100644
--- a/activerecord/test/fixtures/computers.yml
+++ b/activerecord/test/fixtures/computers.yml
@@ -1,4 +1,5 @@
workstation:
id: 1
+ system: 'Linux'
developer: 1
extendedWarranty: 1
diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml
index 6004f390a4..1bb3bf0051 100644
--- a/activerecord/test/fixtures/pirates.yml
+++ b/activerecord/test/fixtures/pirates.yml
@@ -7,3 +7,6 @@ redbeard:
parrot: louis
created_on: "<%= 2.weeks.ago.to_s(:db) %>"
updated_on: "<%= 2.weeks.ago.to_s(:db) %>"
+
+mark:
+ catchphrase: "X $LABELs the spot!"
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 7da39a8e33..272223e1d8 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -22,6 +22,7 @@ class Category < ActiveRecord::Base
end
has_many :categorizations
+ has_many :special_categorizations
has_many :post_comments, :through => :posts, :source => :comments
has_many :authors, :through => :categorizations
diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb
index c7495d7deb..501af4a8dd 100644
--- a/activerecord/test/models/college.rb
+++ b/activerecord/test/models/college.rb
@@ -1,5 +1,10 @@
require_dependency 'models/arunit2_model'
+require 'active_support/core_ext/object/with_options'
class College < ARUnit2Model
has_many :courses
+
+ with_options dependent: :destroy do |assoc|
+ assoc.has_many :students, -> { where(active: true) }
+ end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 2e2d8a0d37..762259ffa3 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -165,6 +165,8 @@ class DeveloperCalledJamis < ActiveRecord::Base
default_scope { where(:name => 'Jamis') }
scope :poor, -> { where('salary < 150000') }
+ scope :david, -> { where name: "David" }
+ scope :david2, -> { unscoped.where name: "David" }
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
diff --git a/activerecord/test/models/movie.rb b/activerecord/test/models/movie.rb
index c441be2bef..0302abad1e 100644
--- a/activerecord/test/models/movie.rb
+++ b/activerecord/test/models/movie.rb
@@ -1,3 +1,5 @@
class Movie < ActiveRecord::Base
self.primary_key = "movieid"
+
+ validates_presence_of :name
end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 1a282dbce4..c7e54e7b63 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -89,6 +89,19 @@ class RichPerson < ActiveRecord::Base
self.table_name = 'people'
has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+
+ before_validation :run_before_create, on: :create
+ before_validation :run_before_validation
+
+ private
+
+ def run_before_create
+ self.first_name = first_name.to_s + 'run_before_create'
+ end
+
+ def run_before_validation
+ self.first_name = first_name.to_s + 'run_before_validation'
+ end
end
class NestedPerson < ActiveRecord::Base
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 8510c596a7..7bb0caf44b 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -85,11 +85,3 @@ end
class DestructivePirate < Pirate
has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy
end
-
-class FamousPirate < ActiveRecord::Base
- self.table_name = 'pirates'
-
- has_many :famous_ships
-
- validates_presence_of :catchphrase, on: :conference
-end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index faf539a562..099e039255 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -149,6 +149,10 @@ class Post < ActiveRecord::Base
ranked_by_comments.limit_by(limit)
end
+ def self.written_by(author)
+ where(id: author.posts.pluck(:id))
+ end
+
def self.reset_log
@log = []
end
diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb
index 7a369b9d9a..3da031946f 100644
--- a/activerecord/test/models/ship.rb
+++ b/activerecord/test/models/ship.rb
@@ -17,11 +17,3 @@ class Ship < ActiveRecord::Base
false
end
end
-
-class FamousShip < ActiveRecord::Base
- self.table_name = 'ships'
-
- belongs_to :famous_pirate
-
- validates_presence_of :name, on: :conference
-end
diff --git a/activerecord/test/models/student.rb b/activerecord/test/models/student.rb
index f459f2a9a3..28a0b6c99b 100644
--- a/activerecord/test/models/student.rb
+++ b/activerecord/test/models/student.rb
@@ -1,3 +1,4 @@
class Student < ActiveRecord::Base
has_and_belongs_to_many :lessons
+ belongs_to :college
end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index e864295acf..a69d3fd3df 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -3,6 +3,7 @@ class Treasure < ActiveRecord::Base
belongs_to :looter, :polymorphic => true
has_many :price_estimates, :as => :estimate_of
+ has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false
accepts_nested_attributes_for :looter
end
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index a86a188bcf..4fcbf4dbd2 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,7 +1,7 @@
ActiveRecord::Schema.define do
%w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids postgresql_ltrees
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name|
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name|
execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
@@ -99,6 +99,15 @@ _SQL
_SQL
end
+ if 't' == select_value("select 'citext'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_citext (
+ id SERIAL PRIMARY KEY,
+ text_citext citext default ''::citext
+ );
+_SQL
+ end
+
if 't' == select_value("select 'json'=ANY(select typname from pg_type)")
execute <<_SQL
CREATE TABLE postgresql_json_data_type (
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 99a53434f6..a9c4980283 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -198,6 +198,7 @@ ActiveRecord::Schema.define do
end
create_table :computers, force: true do |t|
+ t.string :system
t.integer :developer, null: false
t.integer :extendedWarranty, null: false
end
@@ -637,6 +638,8 @@ ActiveRecord::Schema.define do
create_table :students, force: true do |t|
t.string :name
+ t.boolean :active
+ t.integer :college_id
end
create_table :subscribers, force: true, id: false do |t|
@@ -673,7 +676,11 @@ ActiveRecord::Schema.define do
t.string :title
t.string :author_name
t.string :author_email_address
- t.datetime :written_on
+ if mysql_56?
+ t.datetime :written_on, limit: 6
+ else
+ t.datetime :written_on
+ end
t.time :bonus_time
t.date :last_read
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
diff --git a/activerecord/test/support/connection_helper.rb b/activerecord/test/support/connection_helper.rb
new file mode 100644
index 0000000000..4a19e5df44
--- /dev/null
+++ b/activerecord/test/support/connection_helper.rb
@@ -0,0 +1,14 @@
+module ConnectionHelper
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+
+ # Used to drop all cache query plans in tests.
+ def reset_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+end
diff --git a/activerecord/test/support/ddl_helper.rb b/activerecord/test/support/ddl_helper.rb
new file mode 100644
index 0000000000..0107babaaf
--- /dev/null
+++ b/activerecord/test/support/ddl_helper.rb
@@ -0,0 +1,8 @@
+module DdlHelper
+ def with_example_table(connection, table_name, definition = nil)
+ connection.exec_query("CREATE TABLE #{table_name}(#{definition})")
+ yield
+ ensure
+ connection.exec_query("DROP TABLE #{table_name}")
+ end
+end