aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md1865
-rw-r--r--activerecord/lib/active_record/associations.rb30
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb13
-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/preloader/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb3
-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.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb1
-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.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb85
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb58
-rw-r--r--activerecord/lib/active_record/connection_handling.rb18
-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/railties/databases.rake3
-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.rb41
-rw-r--r--activerecord/lib/active_record/scoping/default.rb9
-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.rb18
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb80
-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.rb17
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb24
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb7
-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.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb79
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb31
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb64
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/json_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb56
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb109
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb134
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/xml_test.rb2
-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.rb25
-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.rb10
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb63
-rw-r--r--activerecord/test/cases/autosave_association_test.rb14
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb6
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb31
-rw-r--r--activerecord/test/cases/connection_pool_test.rb32
-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.rb27
-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.rb4
-rw-r--r--activerecord/test/cases/helper.rb5
-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.rb40
-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/relation_scoping_test.rb5
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb3
-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/developer.rb2
-rw-r--r--activerecord/test/models/movie.rb2
-rw-r--r--activerecord/test/models/person.rb13
-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.rb7
-rw-r--r--activerecord/test/support/ddl_helper.rb8
125 files changed, 1620 insertions, 2437 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 7efd75a239..fe18ae995c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1858 +1,187 @@
-* Default scopes are no longer overriden by chained conditions.
+* Make possible to change `record_timestamps` inside Callbacks.
- 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.
+ *Tieg Zaharia*
- Before:
+* Fixed error where .persisted? throws SystemStackError for an unsaved model with a
+ custom primary key that didn't save due to validation error.
- 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.
-
- Fixes #12867.
-
- *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.
-
- *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 #14393.
- Fixes #11018.
+ *Chris Finne*
- *Jonathan Baudanza*
+* Introduce `validate` as an alias for `valid?`.
-* Parsing PostgreSQL arrays with empty strings now works correctly.
+ This is more intuitive when you want to run validations but don't care about the return value.
- 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.
+ *Henrik Nyh*
- Fixes #13907.
+* Create indexes inline in CREATE TABLE for MySQL.
- *Maurício Linhares*
+ This is important, because adding an index on a temporary table after it has been created
+ would commit the transaction.
-* Associations now raise `ArgumentError` on name conflicts.
-
- Dangerous association names conflicts include instance or class methods already
- defined by `ActiveRecord::Base`.
+ It also allows creating and dropping indexed tables with fewer queries and fewer permissions
+ required.
Example:
- class Car < ActiveRecord::Base
- has_many :errors
+ create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t|
+ t.index :zip
end
- # Will raise ArgumentError.
-
- Fixes #13217.
-
- *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.
+ # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query
- person.lock! 'FOR SHARE NOWAIT'
- # Before: SELECT * ... LIMIT 1 FOR UPDATE
- # After: SELECT * ... LIMIT 1 FOR SHARE NOWAIT
+ *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca*
- Fixes #13788.
+* Save `has_one` association even if the record doesn't changed.
- *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:
-
- conversation = Conversation.new
- conversation.status = :active
- conversation.status = :archived
- conversation.status_was # => "active"
+ Fixes #14407.
*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"'
-
- # 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.
-
- Example:
-
- class Conversation < ActiveRecord::Base
- enum status: [ :active, :archived ]
- end
-
- Before:
-
- Conversation::STATUS # => { "active" => 0, "archived" => 1 }
-
- After:
-
- Conversation.statuses # => { "active" => 0, "archived" => 1 }
-
- *Godfrey Chan*
-
-* Set `NameError#name` when STI-class-lookup fails.
-
- *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.
-
- *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"
-
- # conversation.update! gender: nil
- conversation.gender = nil
- conversation.gender.nil? # => true
- conversation.gender # => nil
-
- *Amr Tamimi*
+* Use singular table name in generated migrations when
+ `ActiveRecord::Base.pluralize_table_names` is `false`.
-* 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.'
+ Fixes #13426.
*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.
+* `touch` accepts many attributes to be touched at once.
Example:
- # Before
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => '0.5' }
+ # touches :signed_at, :sealed_at, and :updated_at/on attributes.
+ Photo.last.touch(:signed_at, :sealed_at)
- # After
- Account.group(:firm_name).sum('0.01 * credit_limit')
- # => { '37signals' => 0.5 }
+ *James Pinto*
- *Paul Nikitochkin*
+* `rake db:structure:dump` only dumps schema information if the schema
+ migration table exists.
-* 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`.
+ Fixes #14217.
*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
-
- *DHH*
-
-* Extend `ActiveRecord::Base#cache_key` to take an optional list of timestamp attributes of which the highest will be used.
-
- 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*
-
-* Added `ActiveRecord::Base#enum` for declaring enum attributes where the values map to integers in the database, but can be queried by name.
-
- Example:
-
- class Conversation < ActiveRecord::Base
- enum status: [:active, :archived]
- end
-
- Conversation::STATUS # => { active: 0, archived: 1 }
-
- # conversation.update! status: 0
- conversation.active!
- conversation.active? # => true
- conversation.status # => "active"
-
- # conversation.update! status: 1
- conversation.archived!
- conversation.archived? # => true
- conversation.status # => "archived"
-
- # conversation.update! status: 1
- conversation.status = :archived
-
- *DHH*
-
-* `ActiveRecord::Base#attribute_for_inspect` now truncates long arrays (more than 10 elements).
-
- *Jan Bernacki*
-
-* Allow for the name of the `schema_migrations` table to be configured.
+* 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.
- *Jerad Phelps*
+ *Matthew Draper*
-* Do not add to scope includes values from through associations.
- Fixed bug when providing `includes` in through association scope, and fetching targets.
+* `pk_and_sequence_for` now ensures that only the pg_depend entries
+ pointing to pg_class, and thus only sequence objects, are considered.
- Example:
-
- class Vendor < ActiveRecord::Base
- has_many :relationships, -> { includes(:user) }
- has_many :users, through: :relationships
- end
-
- vendor = Vendor.first
-
- # Before
-
- vendor.users.to_a # => Raises exception: not found `:user` for `User`
+ *Josh Williams*
- # 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:
+* `where.not` adds `references` for `includes` like normal `where` calls do.
- 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.
+ Fixes #14406.
*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.
+* Extend fixture `$LABEL` replacement to allow string interpolation.
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))
+ martin:
+ email: $LABEL@email.com
- # 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)
+ users(:martin).email # => martin@email.com
- Fixes #12415.
+ *Eric Steele*
- *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*
+* Add support for `Relation` be passed as parameter on `QueryCache#select_all`.
-* 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.
+ Fixes #14361.
*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.
-
- *kennyj*
-
-* 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*
+* Passing an Active Record object to `find` is now deprecated. Call `.id`
+ on the object first.
-* Fix inserts with prepared statements disabled.
+* Passing an Active Record object to `find` or `exists?` is now deprecated.
+ Call `.id` on the object first.
- Fixes #12023.
+* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation.
- *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*
+ *Ryuta Kamizono*
-* Stop interpreting SQL 'string' columns as :string type because there is no
- common STRING datatype in SQL.
+* Support for MySQL 5.6 fractional seconds.
- *Ben Woosley*
+ *arthurnn*, *Tatsuhiko Miyagawa*
-* `ActiveRecord::FinderMethods#exists?` returns `true`/`false` in all cases.
+* Support for Postgres `citext` data type enabling case-insensitive where
+ values without needing to wrap in UPPER/LOWER sql functions.
- *Xavier Noria*
+ *Troy Kruthoff*, *Lachlan Sylvester*
-* Assign inet/cidr attribute with `nil` value for invalid address.
+* Allow strings to specify the `#order` value.
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*
-
-* `add_to_target` now accepts a second optional `skip_callbacks` argument
-
- 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*
-
-* Type cast hstore values on write, so that the value is consistent
- with reading from the database.
-
- Example:
-
- x = Hstore.new tags: {"bool" => true, "number" => 5}
-
- # Before:
- x.tags # => {"bool" => true, "number" => 5}
-
- # After:
- x.tags # => {"bool" => "true", "number" => "5"}
-
- *Yves Senn* , *Severin Schoepke*
-
-* Fix multidimensional PostgreSQL arrays containing non-string items.
-
- *Yves Senn*
-
-* Fixes bug when using includes combined with select, the select statement was overwritten.
-
- Fixes #11773.
-
- *Edo Balvers*
-
-* Load fixtures from linked folders.
-
- *Kassio Borges*
-
-* Create a directory for sqlite3 file if not present on the system.
-
- *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:
+ Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY created_at desc, name asc
+ *Marcelo Casiraghi*, *Robin Dupret*
- After:
+* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID"
+ warnings on enum columns.
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY name asc, created_at desc
+ *Dieter Komendera*
- This also affects order defined in `default_scope` or any kind of associations.
+* `includes` is able to detect the right preloading strategy when string
+ joins are involved.
-* Add ability to define how a class is converted to Arel predicates.
- For example, adding a very vendor specific regex implementation:
+ Fixes #14109.
- regex_handler = proc do |column, value|
- Arel::Nodes::InfixOperation.new('~', column, value.source)
- end
- ActiveRecord::PredicateBuilder.register_handler(Regexp, regex_handler)
-
- *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.
-
- *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
+ *Aaron Patterson*, *Yves Senn*
- Post.column_defaults["my_locking_column"]
- => nil # expected value is 0 !
+* Fixed error with validation with enum fields for records where the
+ value for any enum attribute is always evaluated as 0 during
+ uniqueness validation.
- *kennyj*
+ Fixes #14172.
-* 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*
+ *Vilius Luneckas* *Ahmed AbouElhamayed*
-* Remove deprecated `ActiveRecord::Fixtures.find_table_name` in favor of
- `ActiveRecord::Fixtures.default_fixture_model_name`.
+* `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.
- *Vipul A M*
+ Fixes #14144.
-* Removed deprecated `columns_for_remove` from `SchemaStatements`.
+* Fixed STI classes not defining an attribute method if there is a
+ conflicting private method defined on its ancestors.
- *Neeraj Singh*
+ Fixes #11569.
-* 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.
-
- Example:
-
- # before with implicit joins
- Comment.where('posts.author_id' => 7)
-
- # after
- Comment.references(:posts).where('posts.author_id' => 7)
-
- *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
-
- When calling `Comment.joins(:post)`, we expect to receive only
- comments on published posts, since that is the default scope for
- posts.
-
- Before this change, the default scope from `Post` was not applied,
- so we'd get comments on unpublished posts.
-
- *Jon Leighton*
-
-* 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.
-
- Fixes #10669.
-
- *Neeraj Singh and iwiznia*
-
-* Do not load all child records for inverse case.
-
- currently `post.comments.find(Comment.first.id)` would load all
- comments for the given post to set the inverse association.
-
- 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.
-
- Fix is to use in-memory records only if loaded? is true. Otherwise
- load the records using full sql.
-
- Fixes #10509.
-
- *Neeraj Singh*
-
-* `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.
-
- Example:
-
- Author.inspect # => "Author(no database connection)"
-
- *Yves Senn*
-
-* Handle single quotes in PostgreSQL default column values.
- Fixes #10881.
-
- *Dylan Markow*
-
-* Log the sql that is actually sent to the database.
-
- 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'`.
-
- Do not squeeze whitespace out of sql queries. Fixes #10982.
-
- *Neeraj Singh*
-
-* Fixture setup no longer depends on `ActiveRecord::Base.configurations`.
- This is relevant when `ENV["DATABASE_URL"]` is used in place of a `database.yml`.
-
- *Yves Senn*
-
-* Fix mysql2 adapter raises the correct exception when executing a query on a
- closed connection.
-
- *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
-
- class Tagging < ActiveRecord::Base
- end
-
- To this:
-
- class Author < ActiveRecord::Base
- has_many :posts
- has_many :taggings, through: :posts, source: :tagging
- end
-
- class Post < ActiveRecord::Base
- has_one :tagging
- has_many :taggings
- end
-
- class Tagging < ActiveRecord::Base
- end
-
- *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.
+* 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.
- 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
-
- *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..860e76fa18 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -530,8 +530,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 +1036,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 +1097,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 +1211,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 +1232,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 +1317,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 +1335,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 +1454,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 +1498,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 +1584,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/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 89b7945c78..1f314e0677 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -145,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) }
@@ -359,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
@@ -368,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
@@ -513,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/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/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..ea48a13ea8 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -106,7 +106,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
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..e0516c0773 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
@@ -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_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..e4c6502bb7 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -6,12 +6,25 @@ 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
@@ -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..9a133168f8 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -237,8 +237,8 @@ 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
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index b07b0cb826..2b5049f5a5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -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/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index fae260a921..5d32aaed50 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -6,6 +6,10 @@ module ActiveRecord
module OID
class Type
def type; end
+
+ def infinity(options = {})
+ ::Float::INFINITY * (options[:negative] ? -1 : 1)
+ end
end
class Identity < Type
@@ -14,6 +18,14 @@ module ActiveRecord
end
end
+ class Text < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_s
+ end
+ end
+
class Bit < Type
def type_cast(value)
if String === value
@@ -109,23 +121,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,27 +141,20 @@ 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
@@ -222,6 +223,16 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_decimal value
end
+
+ def infinity(options = {})
+ BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ end
+ end
+
+ class Enum < Type
+ def type_cast(value)
+ value.to_s
+ end
end
class Hstore < Type
@@ -331,15 +342,8 @@ module ActiveRecord
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'
-
register_type 'numeric', OID::Decimal.new
- register_type 'text', OID::Identity.new
+ register_type 'text', OID::Text.new
alias_type 'varchar', 'text'
alias_type 'char', 'text'
alias_type 'bpchar', 'text'
@@ -365,13 +369,14 @@ module ActiveRecord
register_type 'date', OID::Date.new
register_type 'time', OID::Time.new
- register_type 'path', OID::Identity.new
+ register_type 'path', OID::Text.new
register_type 'point', OID::Point.new
- register_type 'polygon', OID::Identity.new
- register_type 'circle', OID::Identity.new
+ register_type 'polygon', OID::Text.new
+ register_type 'circle', OID::Text.new
register_type 'hstore', OID::Hstore.new
register_type 'json', OID::Json.new
- register_type 'ltree', OID::Identity.new
+ register_type 'citext', OID::Text.new
+ register_type 'ltree', OID::Text.new
register_type 'cidr', OID::Cidr.new
alias_type 'inet', 'cidr'
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..e0afa989cd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -327,6 +327,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..bcad9f30d7 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -215,6 +215,8 @@ module ActiveRecord
# Character types
when /^(?:character varying|bpchar)(?:\(\d+\))?$/
:string
+ when /^citext(?:\(\d+\))?$/
+ :citext
# Binary data types
when 'bytea'
:binary
@@ -353,6 +355,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 +399,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 +451,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 +603,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 +612,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
@@ -785,18 +797,35 @@ 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
+ 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
+ FROM pg_type as t
+ SQL
+ end
+ ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' }
+ leaves, nodes = nodes.partition { |row| row['typelem'] == '0' }
+ arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' }
+
+ # populate the enum types
+ enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' }
+ 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 +836,13 @@ 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
end
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
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/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 b1b35ed940..1a2581f579 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/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 1d5c80bc01..ff1f0f5911 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -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/relation.rb b/activerecord/lib/active_record/relation.rb
index fb213dc6f7..9eaba4a655 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..e41df0ea29 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
@@ -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
@@ -275,15 +277,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 +285,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 +1032,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 +1062,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 8ca5e8d6f6..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"
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..ed4d0d503d 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -146,7 +146,7 @@ module ActiveRecord
end
def test_foreign_key_violations_are_translated_to_specific_exception
- unless @connection.adapter_name == 'SQLite'
+ unless current_adapter?(:SQLite3Adapter)
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?
@@ -159,6 +159,20 @@ module ActiveRecord
end
end
+ def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
+ unless current_adapter?(:SQLite3Adapter)
+ 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
assert_nothing_raised do
@connection.disable_referential_integrity do
@@ -218,7 +232,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..d1c644c016 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -11,13 +11,14 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(@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 +117,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..42ffb91095 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -1,6 +1,9 @@
require "cases/helper"
+require 'support/ddl_helper'
class MysqlConnectionTest < ActiveRecord::TestCase
+ include DdlHelper
+
class Klass < ActiveRecord::Base
end
@@ -69,59 +72,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
@@ -174,4 +168,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase
ActiveRecord::Base.establish_connection(original_connection)
end
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..81c614924f 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -11,13 +11,14 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
end
- def teardown
+ teardown do
ActiveRecord::Base.remove_connection
ActiveRecord::Base.establish_connection(@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 +117,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..98febd2d75 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -97,6 +97,13 @@ class MysqlConnectionTest < ActiveRecord::TestCase
@connection.execute "DROP TABLE `bar_baz`"
end
+ 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
+
private
def run_without_connection
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..f5f1c791e1 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -19,7 +19,7 @@ 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
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..0bb2949371
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -0,0 +1,79 @@
+# 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
+ @column = Citext.columns_hash['cival']
+ 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_type
+ assert_equal :citext, @column.type
+ end
+
+ def test_column_sql_type
+ assert_equal 'citext', @column.sql_type
+ 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..ed39204145 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
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 4715fa002d..aa4996133f 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -45,6 +45,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]
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/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
new file mode 100644
index 0000000000..7208edef5f
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlEnumTest < ActiveRecord::TestCase
+ class PostgresqlEnum < ActiveRecord::Base
+ self.table_name = "postgresql_enums"
+ end
+
+ teardown do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_enums'
+ @connection.execute 'DROP TYPE IF EXISTS mood'
+ 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
+
+ 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..90ec1524b5 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
diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb
index 3daef399d8..10c0a6c395 100644
--- a/activerecord/test/cases/adapters/postgresql/json_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/json_test.rb
@@ -26,7 +26,7 @@ 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
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index 5d12ca75ca..e72fd4360d 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -19,7 +19,7 @@ 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
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/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index 5c2d8e1c1d..a9d4312fb3 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -8,14 +8,24 @@ if ActiveRecord::Base.connection.supports_ranges?
end
class PostgresqlRangeTest < ActiveRecord::TestCase
- def teardown
+ teardown do
@connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.execute 'DROP TYPE IF EXISTS floatrange'
end
def setup
- @connection = ActiveRecord::Base.connection
+ @connection = PostgresqlRange.connection
begin
@connection.transaction do
+ @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges'
+ @connection.execute 'DROP TYPE IF EXISTS floatrange'
+ @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 +34,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 +49,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 +76,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)
@@ -88,24 +107,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 +152,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 +230,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 +288,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 +297,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..5e5f653d17 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
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 3f5d981444..f581c4cf39 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -4,31 +4,77 @@ 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
+
+ teardown do
+ drop_table "uuid_data_type"
+ end
+
+ def test_data_type_of_uuid_types
+ assert_equal :uuid, UUIDType.columns_hash["guid"].type
+ end
- unless @connection.extension_enabled?('uuid-ossp')
- @connection.enable_extension 'uuid-ossp'
- @connection.commit_db_transaction
+ 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 +95,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 +110,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!
-
- 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_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 +137,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 +149,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..08071894c4 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -24,7 +24,7 @@ class ViewTest < ActiveRecord::TestCase
@connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}"
end
- def teardown
+ teardown do
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
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/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 9340bc0a83..a65f2da7a0 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..e59161fc5b 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -407,19 +407,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
@@ -1194,4 +1194,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 a86fb15719..49d3301044 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1242,6 +1242,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)
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 d2f97df0fc..f7584c3a51 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -64,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
@@ -76,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
@@ -622,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..6acb342d0b 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
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 291751c435..24eb91bef9 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
ActiveSupport::Notifications.subscribe('sql.active_record', @listener)
end
- def teardown
+ teardown do
ActiveSupport::Notifications.unsubscribe(@listener)
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..2992ceb211 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -13,10 +13,24 @@ module ActiveRecord
@previous_database_url = ENV.delete("DATABASE_URL")
end
- def teardown
+ teardown do
ENV["DATABASE_URL"] = @previous_database_url
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 +83,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/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..f8ebd7caee 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -222,4 +222,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..1147418815 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -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..8a49dfbb44 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
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..fddb7c204a 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
@@ -613,7 +637,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 +769,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 +1366,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/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/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/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/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..b44e72a67c 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
@@ -673,7 +674,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/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