aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md1847
-rw-r--r--activerecord/README.rdoc4
-rw-r--r--activerecord/activerecord.gemspec4
-rw-r--r--activerecord/examples/associations.pngbin40623 -> 0 bytes
-rw-r--r--activerecord/lib/active_record.rb10
-rw-r--r--activerecord/lib/active_record/associations.rb14
-rw-r--r--activerecord/lib/active_record/associations/association.rb10
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb1
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb20
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb13
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb37
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb32
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb12
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many_through.rb8
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb10
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb23
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb11
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--activerecord/lib/active_record/base.rb6
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb112
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb76
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb82
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb67
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/cast.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb32
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_handling.rb14
-rw-r--r--activerecord/lib/active_record/core.rb41
-rw-r--r--activerecord/lib/active_record/errors.rb21
-rw-r--r--activerecord/lib/active_record/explain.rb15
-rw-r--r--activerecord/lib/active_record/explain_registry.rb30
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb5
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb20
-rw-r--r--activerecord/lib/active_record/inheritance.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb30
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb64
-rw-r--r--activerecord/lib/active_record/persistence.rb20
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb14
-rw-r--r--activerecord/lib/active_record/railties/databases.rake124
-rw-r--r--activerecord/lib/active_record/reflection.rb39
-rw-r--r--activerecord/lib/active_record/relation.rb46
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb4
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb14
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb41
-rw-r--r--activerecord/lib/active_record/relation/merger.rb32
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb8
-rw-r--r--activerecord/lib/active_record/runtime_registry.rb17
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/scoping.rb58
-rw-r--r--activerecord/lib/active_record/scoping/default.rb23
-rw-r--r--activerecord/lib/active_record/scoping/named.rb9
-rw-r--r--activerecord/lib/active_record/statement_cache.rb26
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb73
-rw-r--r--activerecord/lib/active_record/tasks/firebird_database_tasks.rb56
-rw-r--r--activerecord/lib/active_record/tasks/oracle_database_tasks.rb45
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb48
-rw-r--r--activerecord/lib/active_record/transactions.rb8
-rw-r--r--activerecord/lib/active_record/validations.rb3
-rw-r--r--activerecord/lib/active_record/version.rb13
-rw-r--r--activerecord/test/cases/adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb37
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb87
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb36
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb24
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb20
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb10
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb13
-rw-r--r--activerecord/test/cases/aggregations_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb9
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb16
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb32
-rw-r--r--activerecord/test/cases/associations_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb9
-rw-r--r--activerecord/test/cases/autosave_association_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb91
-rw-r--r--activerecord/test/cases/batches_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb30
-rw-r--r--activerecord/test/cases/clone_test.rb7
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb14
-rw-r--r--activerecord/test/cases/column_definition_test.rb13
-rw-r--r--activerecord/test/cases/column_test.rb36
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb9
-rw-r--r--activerecord/test/cases/counter_cache_test.rb1
-rw-r--r--activerecord/test/cases/dirty_test.rb8
-rw-r--r--activerecord/test/cases/dup_test.rb2
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb59
-rw-r--r--activerecord/test/cases/finder_test.rb9
-rw-r--r--activerecord/test/cases/fixtures_test.rb9
-rw-r--r--activerecord/test/cases/inheritance_test.rb4
-rw-r--r--activerecord/test/cases/locking_test.rb8
-rw-r--r--activerecord/test/cases/migration/columns_test.rb13
-rw-r--r--activerecord/test/cases/migration_test.rb26
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb2
-rw-r--r--activerecord/test/cases/persistence_test.rb47
-rw-r--r--activerecord/test/cases/relation_test.rb19
-rw-r--r--activerecord/test/cases/relations_test.rb8
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb21
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb (renamed from activerecord/test/cases/relation_scoping_test.rb)344
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb (renamed from activerecord/test/cases/named_scope_test.rb)20
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb331
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb14
-rw-r--r--activerecord/test/cases/statement_cache_test.rb64
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb7
-rw-r--r--activerecord/test/cases/tasks/firebird_rake_test.rb100
-rw-r--r--activerecord/test/cases/tasks/oracle_rake_test.rb93
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb4
-rw-r--r--activerecord/test/cases/tasks/sqlserver_rake_test.rb87
-rw-r--r--activerecord/test/cases/timestamp_test.rb100
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb32
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb2
-rw-r--r--activerecord/test/fixtures/pets.yml5
-rw-r--r--activerecord/test/fixtures/toys.yml10
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/pet.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb12
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb12
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb1
-rw-r--r--activerecord/test/schema/schema.rb1
152 files changed, 2899 insertions, 2927 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 771f1333f0..9f10a31512 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,1848 +1,15 @@
-## Rails 4.0.0 (unreleased) ##
+* Mute `psql` output when running rake db:schema:load.
-* Models with multiple counter cache associations now update correctly on destroy.
- See #7706.
+ *Godfrey Chan*
- *Ian Young*
+* Trigger a save on `has_one association=(associate)` when the associate contents have changed.
-* If inverse_of is true on an association, then when one calls +find()+ on
- the association, ActiveRecord will first look through the in-memory objects
- in the association for a particular id. Then, it will go to the DB if it
- is not found. This is accomplished by calling +find_by_scan+ in
- collection associations whenever +options[:inverse_of]+ is not nil.
+ Fix #8856.
- Fixes #9470.
+ *Chris Thompson*
- *John Wang*
-
-* `rake db:create` does not change permissions of the MySQL root user.
- Fixes #8079.
-
- *Yves Senn*
-
-* The length of the `version` column in the `schema_migrations` table
- created by the `mysql2` adapter is 191 if the encoding is "utf8mb4".
-
- The "utf8" encoding in MySQL has support for a maximum of 3 bytes per character,
- and only contains characters from the BMP. The recently added
- [utf8mb4](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)
- encoding extends the support to four bytes. As of this writing, said encoding
- is supported in the betas of the `mysql2` gem.
-
- Setting the encoding to "utf8mb4" has
- [a few implications](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-upgrading.html).
- This change addresses the max length for indexes, which is 191 instead of 255.
-
- *Xavier Noria*
-
-* Counter caches on associations will now stay valid when attributes are
- updated (not just when records are created or destroyed), for example,
- when calling `update_attributes`. The following code now works:
-
- class Comment < ActiveRecord::Base
- belongs_to :post, counter_cache: true
- end
-
- class Post < ActiveRecord::Base
- has_many :comments
- end
-
- post = Post.create
- comment = Comment.create
-
- post.comments << comment
- post.save.reload.comments_count # => 1
- comment.update_attributes(post_id: nil)
-
- post.save.reload.comments_count # => 0
-
- Updating the id of a `belongs_to` object with the id of a new object will
- also keep the count accurate.
-
- *John Wang*
-
-* Referencing join tables implicitly was deprecated. There is a
- possibility that these deprecation warnings are shown even if you
- don't make use of that feature. You can now disable the feature entirely.
- Fixes #9712.
-
- Example:
-
- # in your configuration
- config.active_record.disable_implicit_join_references = true
-
- # or directly
- ActiveRecord::Base.disable_implicit_join_references = true
-
- *Yves Senn*
-
-* The `:distinct` option for `Relation#count` is deprecated. You
- should use `Relation#distinct` instead.
-
- Example:
-
- # Before
- Post.select(:author_name).count(distinct: true)
-
- # After
- Post.select(:author_name).distinct.count
-
- *Yves Senn*
-
-* Rename `Relation#uniq` to `Relation#distinct`. `#uniq` is still
- available as an alias but we encourage to use `#distinct` instead.
- Also `Relation#uniq_value` is aliased to `Relation#distinct_value`,
- this is a temporary solution and you should migrate to `distinct_value`.
-
- *Yves Senn*
-
-* Fix quoting for sqlite migrations using copy_table_contents() with binary
- columns.
-
- These would fail with "SQLite3::SQLException: unrecognized token" because
- the column was not being passed to quote() so the data was not quoted
- correctly.
-
- *Matthew M. Boedicker*
-
-* Promotes `change_column_null` to the migrations API. This macro sets/removes
- `NOT NULL` constraints, and accepts an optional argument to replace existing
- `NULL`s if needed. The adapters for SQLite, MySQL, PostgreSQL, and (at least)
- Oracle, already implement this method.
-
- *Xavier Noria*
-
-* Uniqueness validation allows you to pass `:conditions` to limit
- the constraint lookup.
-
- Example:
-
- validates_uniqueness_of :title, conditions: -> { where('approved = ?', true) }
-
- *Mattias Pfeiffer + Yves Senn*
-
-* `connection` is deprecated as an instance method.
- This allows end-users to have a `connection` method on their models
- without clashing with Active Record internals.
-
- *Ben Moss*
-
-* When copying migrations, preserve their magic comments and content encoding.
-
- *OZAWA Sakuro*
-
-* Fix `subclass_from_attrs` when `eager_load` is false. It cannot find
- subclass because all classes are loaded automatically when it needs.
-
- *Dmitry Vorotilin*
-
-* When `:name` option is provided to `remove_index`, use it if there is no
- index by the conventional name.
-
- For example, previously if an index was removed like so
- `remove_index :values, column: :value, name: 'a_different_name'`
- the generated SQL would not contain the specified index name,
- and hence the migration would fail.
- Fixes #8858.
-
- *Ezekiel Smithburg*
-
-* Created block to by-pass the prepared statement bindings.
- This will allow to compose fragments of large SQL statements to
- avoid multiple round-trips between Ruby and the DB.
-
- Example:
-
- sql = Post.connection.unprepared_statement do
- Post.first.comments.to_sql
- end
-
- *Cédric Fabianski*
-
-* Change the semantics of combining scopes to be the same as combining
- class methods which return scopes. For example:
-
- class User < ActiveRecord::Base
- scope :active, -> { where state: 'active' }
- scope :inactive, -> { where state: 'inactive' }
- end
-
- class Post < ActiveRecord::Base
- def self.active
- where state: 'active'
- end
-
- def self.inactive
- where state: 'inactive'
- end
- end
-
- ### BEFORE ###
-
- User.where(state: 'active').where(state: 'inactive')
- # => SELECT * FROM users WHERE state = 'active' AND state = 'inactive'
-
- User.active.inactive
- # => SELECT * FROM users WHERE state = 'inactive'
-
- Post.active.inactive
- # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
-
- ### AFTER ###
-
- User.active.inactive
- # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
-
- Before this change, invoking a scope would merge it into the current
- scope and return the result. `Relation#merge` applies "last where
- wins" logic to de-duplicate the conditions, but this lead to
- confusing and inconsistent behaviour. This fixes that.
-
- If you really do want the "last where wins" logic, you can opt-in to
- it like so:
-
- User.active.merge(User.inactive)
-
- Fixes #7365.
-
- *Neeraj Singh* and *Jon Leighton*
-
-* Expand `#cache_key` to consult all relevant updated timestamps.
-
- Previously only `updated_at` column was checked, now it will
- consult other columns that received updated timestamps on save,
- such as `updated_on`. When multiple columns are present it will
- use the most recent timestamp.
- Fixes #9033.
-
- *Brendon Murphy*
-
-* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class.
-
- *Aaron Weiner*
-
-* Warn when `rake db:structure:dump` with a MySQL database and
- `mysqldump` is not in the PATH or fails.
- Fixes #9518.
-
- *Yves Senn*
-
-* Remove `connection#structure_dump`, which is no longer used. *Yves Senn*
-
-* Make it possible to execute migrations without a transaction even
- if the database adapter supports DDL transactions.
- Fixes #9483.
-
- Example:
-
- class ChangeEnum < ActiveRecord::Migration
- disable_ddl_transaction!
-
- def up
- execute "ALTER TYPE model_size ADD VALUE 'new_value'"
- end
- end
-
- *Yves Senn*
-
-* Assigning "0.0" to a nullable numeric column does not make it dirty.
- Fixes #9034.
-
- Example:
-
- product = Product.create price: 0.0
- product.price = '0.0'
- product.changed? # => false (this used to return true)
- product.changes # => {} (this used to return { price: [0.0, 0.0] })
-
- *Yves Senn*
-
-* Added functionality to unscope relations in a relations chain. For
- instance, if you are passed in a chain of relations as follows:
-
- User.where(name: "John").order('id DESC')
-
- but you want to get rid of order, then this feature allows you to do:
-
- User.where(name: "John").order('id DESC').unscope(:order)
- == User.where(name: "John")
-
- The .unscope() function is more general than the .except() method because
- .except() only works on the relation it is acting on. However, .unscope()
- works for any relation in the entire relation chain.
-
- *John Wang*
-
-* Postgresql timestamp with time zone (timestamptz) datatype now returns a
- ActiveSupport::TimeWithZone instance instead of a string
-
- *Troy Kruthoff*
-
-* The `#append` method for collection associations behaves like`<<`.
- `#prepend` is not defined and `<<` or `#append` should be used.
- Fixes #7364.
-
- *Yves Senn*
-
-* Added support for creating a table via Rails migration generator.
- For example,
-
- rails g migration create_books title:string content:text
-
- will generate a migration that creates a table called books with
- the listed attributes, without creating a model.
-
- *Sammy Larbi*
-
-* Fix bug that raises the wrong exception when the exception handled by PostgreSQL adapter
- doesn't respond to `#result`.
- Fixes #8617.
-
- *kennyj*
-
-* Support PostgreSQL specific column types when using `change_table`.
- Fixes #9480.
-
- Example:
-
- change_table :authors do |t|
- t.hstore :books
- t.json :metadata
- end
-
- *Yves Senn*
-
-* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions.
-
- *Steve Klabnik*
-
-* Fix calculation of `db_runtime` property in
- `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`.
- Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was
- not populated.
- Fixes #9215.
-
- *Igor Fedoronchuk*
-
-* Do not try to touch invalid (and thus not persisted) parent record
- for a `belongs_to :parent, touch: true` association
-
- *Olek Janiszewski*
-
-* Fix when performing an ordered join query. The bug only
- affected queries where the order was given with a symbol.
- Fixes #9275.
-
- Example:
-
- # This will expand the order :name to "authors".name.
- Author.joins(:books).where('books.published = 1').order(:name)
-
-
-## Rails 4.0.0.beta1 (February 25, 2013) ##
-
-* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`.
-
- *Hiroshige UMINO*
-
-* Update queries now use prepared statements.
-
- *Olli Rissanen*
-
-* Fixing issue #8345. Now throwing an error when one attempts to touch a
- new object that has not yet been persisted. For instance:
-
- Example:
-
- ball = Ball.new
- ball.touch :updated_at # => raises error
-
- It is not until the ball object has been persisted that it can be touched.
- This follows the behavior of update_column.
-
- *John Wang*
-
-* Preloading ordered `has_many :through` associations no longer applies
- invalid ordering to the `:through` association.
- Fixes #8663.
-
- *Yves Senn*
-
-* The auto explain feature has been removed. This feature was
- activated by configuring `config.active_record.auto_explain_threshold_in_seconds`.
- The configuration option was deprecated and has no more effect.
-
- You can still use `ActiveRecord::Relation#explain` to see the EXPLAIN output for
- any given relation.
-
- *Yves Senn*
-
-* The `:on` option for `after_commit` and `after_rollback` now
- accepts an Array of actions.
- Fixes #988.
-
- Example:
-
- after_commit :update_cache on: [:create, :update]
-
- *Yves Senn*
-
-* Rename related indexes on `rename_table` and `rename_column`. This
- does not affect indexes with custom names.
-
- *Yves Senn*
-
-* Prevent the creation of indices with too long names, which cause
- internal operations to fail (sqlite3 adapter only). The method
- `allowed_index_name_length` defines the length limit enforced by
- rails. It's value defaults to `index_name_length` but can vary per adapter.
- Fixes #8264.
-
- *Yves Senn*
-
-* Fixing issue #776.
-
- Memory bloat in transactions is handled by having the transaction hold only
- the AR objects which it absolutely needs to know about. These are the AR
- objects with callbacks (they need to be updated as soon as something in the
- transaction occurs).
-
- All other AR objects can be updated lazily by keeping a reference to a
- TransactionState object. If an AR object gets inside a transaction, then
- the transaction will add its TransactionState to the AR object. When the
- user makes a call to some attribute on an AR object (which has no
- callbacks) associated with a transaction, the AR object will call the
- sync_with_transaction_state method and make sure it is up to date with the
- transaction. After it has synced with the transaction state, the AR object
- will return the attribute that was requested.
-
- Most of the logic in the changes are used to handle multiple transactions,
- in which case the AR object has to recursively follow parent pointers of
- TransactionState objects.
-
- *John Wang*
-
-* Descriptive error message when the necessary AR adapter gem was not found.
- Fixes #7313.
-
- *Yves Senn*
-
-* Active Record now raises an error when blank arguments are passed to query
- methods for which blank arguments do not make sense.
-
- Example:
-
- Post.includes() # => raises error
-
- *John Wang*
-
-* Simplified type casting code for timezone aware attributes to use the
- `in_time_zone` method if it is available. This introduces a subtle change
- of behavior when using `Date` instances as they are directly converted to
- `ActiveSupport::TimeWithZone` instances without first being converted to
- `Time` instances. For example:
-
- # Rails 3.2 behavior
- >> Date.today.to_time.in_time_zone
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
-
- # Rails 4.0 behavior
- >> Date.today.in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- On the plus side it now behaves the same whether you pass a `String` date
- or an actual `Date` instance. For example:
-
- # Rails 3.2 behavior
- >> Date.civil(2013, 2, 13).to_time.in_time_zone
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
- >> Time.zone.parse("2013-02-13")
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- # Rails 4.0 behavior
- >> Date.civil(2013, 2, 13).in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
- >> "2013-02-13".in_time_zone
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- If you need the old behavior you can convert the dates to times manually.
- For example:
-
- >> Post.new(created_at: Date.today).created_at
- => Wed, 13 Feb 2013 00:00:00 UTC +00:00
-
- >> Post.new(created_at: Date.today.to_time).created_at
- => Wed, 13 Feb 2013 07:00:00 UTC +00:00
-
- *Andrew White*
-
-* Preloading `has_many :through` associations with conditions won't
- cache the `:through` association. This will prevent invalid
- subsets to be cached.
- Fixes #8423.
-
- Example:
-
- class User
- has_many :posts
- has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts
- end
-
- a_user = User.includes(:recent_comments).first
-
- # This is preloaded.
- a_user.recent_comments
-
- # This is not preloaded, fetched now.
- a_user.posts
-
- *Yves Senn*
-
-* Don't run `after_commit` callbacks when creating through an association
- if saving the record fails.
-
- *James Miller*
-
-* Allow store accessors to be overrided like other attribute methods, e.g.:
-
- class User < ActiveRecord::Base
- store :settings, accessors: [ :color, :homepage ], coder: JSON
-
- def color
- super || 'red'
- end
- end
-
- *Sergey Nartimov*
-
-* Quote numeric values being compared to non-numeric columns. Otherwise,
- in some database, the string column values will be coerced to a numeric
- allowing 0, 0.0 or false to match any string starting with a non-digit.
-
- Example:
-
- App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0'
-
- *Dylan Smith*
-
-* Schema dumper supports dumping the enabled database extensions to `schema.rb`
- (currently only supported by postgresql).
-
- *Justin George*
-
-* The database adapters now converts the options passed thought `DATABASE_URL`
- environment variable to the proper Ruby types before using. For example, SQLite requires
- that the timeout value is an integer, and PostgreSQL requires that the
- prepared_statements option is a boolean. These now work as expected:
-
- Example:
-
- DATABASE_URL=sqlite3://localhost/test_db?timeout=500
- DATABASE_URL=postgresql://localhost/test_db?prepared_statements=false
-
- *Aaron Stone + Rafael Mendonça França*
-
-* `Relation#merge` now only overwrites where values on the LHS of the
- merge. Consider:
-
- left = Person.where(age: [13, 14, 15])
- right = Person.where(age: [13, 14]).where(age: [14, 15])
-
- `left` results in the following SQL:
-
- WHERE age IN (13, 14, 15)
-
- `right` results in the following SQL:
-
- WHERE age IN (13, 14) AND age IN (14, 15)
-
- Previously, `left.merge(right)` would result in all but the last
- condition being removed:
-
- WHERE age IN (14, 15)
-
- Now it results in the LHS condition(s) for `age` being removed, but
- the RHS remains as it is:
-
- WHERE age IN (13, 14) AND age IN (14, 15)
-
- *Jon Leighton*
-
-* Fix handling of dirty time zone aware attributes
-
- Previously, when `time_zone_aware_attributes` were enabled, after
- changing a datetime or timestamp attribute and then changing it back
- to the original value, `changed_attributes` still tracked the
- attribute as changed. This caused `[attribute]_changed?` and
- `changed?` methods to return true incorrectly.
-
- Example:
-
- in_time_zone 'Paris' do
- order = Order.new
- original_time = Time.local(2012, 10, 10)
- order.shipped_at = original_time
- order.save
- order.changed? # => false
-
- # changing value
- order.shipped_at = Time.local(2013, 1, 1)
- order.changed? # => true
-
- # reverting to original value
- order.shipped_at = original_time
- order.changed? # => false, used to return true
- end
-
- *Lilibeth De La Cruz*
-
-* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`.
- Fixes #6865.
-
- Example:
-
- relation.uniq.count # => SELECT COUNT(DISTINCT *)
-
- *Yves Senn + Kaspar Schiess*
-
-* PostgreSQL ranges type support. Includes: int4range, int8range,
- numrange, tsrange, tstzrange, daterange
-
- Ranges can be created with inclusive and exclusive bounds.
-
- Example:
-
- create_table :Room do |t|
- t.daterange :availability
- end
-
- Room.create(availability: (Date.today..Float::INFINITY))
- Room.first.availability # => Wed, 19 Sep 2012..Infinity
-
- One thing to note: Range class does not support exclusive lower
- bound.
-
- *Alexander Grebennik*
-
-* Added a state instance variable to each transaction. Will allow other objects
- to know whether a transaction has been committed or rolled back.
-
- *John Wang*
-
-* Collection associations `#empty?` always respects built records.
- Fixes #8879.
-
- Example:
-
- widget = Widget.new
- widget.things.build
- widget.things.empty? # => false
-
- *Yves Senn*
-
-* Support for PostgreSQL's `ltree` data type.
-
- *Rob Worley*
-
-* Fix undefined method `to_i` when calling `new` on a scope that uses an
- Array; Fix FloatDomainError when setting integer column to NaN.
- Fixes #8718, #8734, #8757.
-
- *Jason Stirk + Tristan Harward*
-
-* Rename `update_attributes` to `update`, keep `update_attributes` as an alias for `update` method.
- This is a soft-deprecation for `update_attributes`, although it will still work without any
- deprecation message in 4.0 is recommended to start using `update` since `update_attributes` will be
- deprecated and removed in future versions of Rails.
-
- *Amparo Luna + Guillermo Iguaran*
-
-* `after_commit` and `after_rollback` now validate the `:on` option and raise an `ArgumentError`
- if it is not one of `:create`, `:destroy` or `:update`
-
- *Pascal Friederich*
-
-* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
-
- * The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
- The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not reversible).
- The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
-
- * New method `reversible` makes it possible to specify code to be run when migrating up or down.
- See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
-
- * New method `revert` will revert a whole migration or the given block.
- If migrating down, the given migration / block is run normally.
- See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
-
- Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now
- raise an `IrreversibleMigration` instead of actually executing them without any output.
-
- *Marc-André Lafortune*
-
-* Serialized attributes can be serialized in integer columns.
- Fixes #8575.
-
- *Rafael Mendonça França*
-
-* Keep index names when using `alter_table` with sqlite3.
- Fixes #3489.
-
- *Yves Senn*
-
-* Add ability for postgresql adapter to disable user triggers in `disable_referential_integrity`.
- Fixes #5523.
-
- *Gary S. Weaver*
-
-* Added support for `validates_uniqueness_of` in PostgreSQL array columns.
- Fixes #8075.
-
- *Pedro Padron*
-
-* Allow int4range and int8range columns to be created in PostgreSQL and properly convert to/from database.
-
- *Alexey Vasiliev aka leopard*
-
-* Do not log the binding values for binary columns.
-
- *Matthew M. Boedicker*
-
-* Fix counter cache columns not updated when replacing `has_many :through`
- associations.
-
- *Matthew Robertson*
-
-* Recognize migrations placed in directories containing numbers and 'rb'.
- Fixes #8492.
-
- *Yves Senn*
-
-* Add `ActiveRecord::Base.cache_timestamp_format` class attribute to control
- the format of the timestamp value in the cache key. Defaults to `:nsec`.
- Fixes #8195.
-
- *Rafael Mendonça França*
-
-* Session variables can be set for the `mysql`, `mysql2`, and `postgresql` adapters
- in the `variables: <hash>` parameter in `config/database.yml`. The key-value pairs of this
- hash will be sent in a `SET key = value` query on new database connections. See also:
- http://dev.mysql.com/doc/refman/5.0/en/set-statement.html
- http://www.postgresql.org/docs/8.3/static/sql-set.html
-
- *Aaron Stone*
-
-* Allow `Relation#where` with no arguments to be chained with new `not` query method.
-
- Example:
-
- Developer.where.not(name: 'Aaron')
-
- *Akira Matsuda*
-
-* Unscope `update_column(s)` query to ignore default scope.
-
- When applying `default_scope` to a class with a where clause, using
- `update_column(s)` could generate a query that would not properly update
- the record due to the where clause from the `default_scope` being applied
- to the update query.
-
- class User < ActiveRecord::Base
- default_scope -> { where(active: true) }
- end
-
- user = User.first
- user.active = false
- user.save!
-
- user.update_column(:active, true) # => false
-
- In this situation we want to skip the default_scope clause and just
- update the record based on the primary key. With this change:
-
- user.update_column(:active, true) # => true
-
- Fixes #8436.
-
- *Carlos Antonio da Silva*
-
-* SQLite adapter no longer corrupts binary data if the data contains `%00`.
-
- *Chris Feist*
-
-* Fix performance problem with `primary_key` method in PostgreSQL adapter when having many schemas.
- Uses `pg_constraint` table instead of `pg_depend` table which has many records in general.
- Fixes #8414.
-
- *kennyj*
-
-* Do not instantiate intermediate Active Record objects when eager loading.
- These records caused `after_find` to run more than expected.
- Fixes #3313.
-
- *Yves Senn*
-
-* Add STI support to init and building associations.
- Allows you to do `BaseClass.new(type: "SubClass")` as well as
- `parent.children.build(type: "SubClass")` or `parent.build_child`
- to initialize an STI subclass. Ensures that the class name is a
- valid class and that it is in the ancestors of the super class
- that the association is expecting.
-
- *Jason Rush*
-
-* Observers was extracted from Active Record as `rails-observers` gem.
-
- *Rafael Mendonça França*
-
-* Ensure that associations take a symbol argument. *Steve Klabnik*
-
-* Fix dirty attribute checks for `TimeZoneConversion` with nil and blank
- datetime attributes. Setting a nil datetime to a blank string should not
- result in a change being flagged.
- Fixes #8310.
-
- *Alisdair McDiarmid*
-
-* Prevent mass assignment to the type column of polymorphic associations when using `build`
- Fixes #8265.
-
- *Yves Senn*
-
-* Deprecate calling `Relation#sum` with a block. To perform a calculation over
- the array result of the relation, use `to_a.sum(&block)`.
-
- *Carlos Antonio da Silva*
-
-* Fix postgresql adapter to handle BC timestamps correctly
-
- HistoryEvent.create!(name: "something", occured_at: Date.new(0) - 5.years)
-
- *Bogdan Gusiev*
-
-* When running migrations on Postgresql, the `:limit` option for `binary` and `text` columns is silently dropped.
- Previously, these migrations caused sql exceptions, because Postgresql doesn't support limits on these types.
-
- *Victor Costan*
-
-* Don't change STI type when calling `ActiveRecord::Base#becomes`.
- Add `ActiveRecord::Base#becomes!` with the previous behavior.
-
- See #3023 for more information.
-
- *Thomas Hollstegge*
-
-* `rename_index` can be used inside a `change_table` block.
-
- change_table :accounts do |t|
- t.rename_index :user_id, :account_id
- end
-
- *Jarek Radosz*
-
-* `#pluck` can be used on a relation with `select` clause. Fix #7551
-
- Example:
-
- Topic.select([:approved, :id]).order(:id).pluck(:id)
-
- *Yves Senn*
-
-* Do not create useless database transaction when building `has_one` association.
-
- Example:
-
- User.has_one :profile
- User.new.build_profile
-
- *Bogdan Gusiev*
-
-* `:counter_cache` option for `has_many` associations to support custom named counter caches.
- Fixes #7993.
-
- *Yves Senn*
-
-* Deprecate the possibility to pass a string as third argument of `add_index`.
- Pass `unique: true` instead.
-
- add_index(:users, :organization_id, unique: true)
-
- *Rafael Mendonça França*
-
-* Raise an `ArgumentError` when passing an invalid option to `add_index`.
-
- *Rafael Mendonça França*
-
-* Fix `find_in_batches` crashing when IDs are strings and start option is not specified.
-
- *Alexis Bernard*
-
-* `AR::Base#attributes_before_type_cast` now returns unserialized values for serialized attributes.
-
- *Nikita Afanasenko*
-
-* Use query cache/uncache when using `DATABASE_URL`.
- Fixes #6951.
-
- *kennyj*
-
-* Fix bug where `update_columns` and `update_column` would not let you update the primary key column.
-
- *Henrik Nyh*
-
-* The `create_table` method raises an `ArgumentError` when the primary key column is redefined.
- Fixes #6378.
-
- *Yves Senn*
-
-* `ActiveRecord::AttributeMethods#[]` raises `ActiveModel::MissingAttributeError`
- error if the given attribute is missing. Fixes #5433.
-
- class Person < ActiveRecord::Base
- belongs_to :company
- end
-
- # Before:
- person = Person.select('id').first
- person[:name] # => nil
- person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
- person[:company_id] # => nil
- person.company # => nil
-
- # After:
- person = Person.select('id').first
- person[:name] # => ActiveModel::MissingAttributeError: missing_attribute: name
- person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
- person[:company_id] # => ActiveModel::MissingAttributeError: missing_attribute: company_id
- person.company # => ActiveModel::MissingAttributeError: missing_attribute: company_id
-
- *Francesco Rodriguez*
-
-* Small binary fields use the `VARBINARY` MySQL type, instead of `TINYBLOB`.
-
- *Victor Costan*
-
-* Decode URI encoded attributes on database connection URLs.
-
- *Shawn Veader*
-
-* Add `find_or_create_by`, `find_or_create_by!` and
- `find_or_initialize_by` methods to `Relation`.
-
- These are similar to the `first_or_create` family of methods, but
- the behaviour when a record is created is slightly different:
-
- User.where(first_name: 'Penélope').first_or_create
-
- will execute:
-
- User.where(first_name: 'Penélope').create
-
- Causing all the `create` callbacks to execute within the context of
- the scope. This could affect queries that occur within callbacks.
-
- User.find_or_create_by(first_name: 'Penélope')
-
- will execute:
-
- User.create(first_name: 'Penélope')
-
- Which obviously does not affect the scoping of queries within
- callbacks.
-
- The `find_or_create_by` version also reads better, frankly.
-
- If you need to add extra attributes during create, you can do one of:
-
- User.create_with(active: true).find_or_create_by(first_name: 'Jon')
- User.find_or_create_by(first_name: 'Jon') { |u| u.active = true }
-
- The `first_or_create` family of methods have been nodoc'ed in favour
- of this API. They may be deprecated in the future but their
- implementation is very small and it's probably not worth putting users
- through lots of annoying deprecation warnings.
-
- *Jon Leighton*
-
-* Fix bug with presence validation of associations. Would incorrectly add duplicated errors
- when the association was blank. Bug introduced in 1fab518c6a75dac5773654646eb724a59741bc13.
-
- *Scott Willson*
-
-* Fix bug where sum(expression) returns string '0' for no matching records.
- Fixes #7439
-
- *Tim Macfarlane*
-
-* PostgreSQL adapter correctly fetches default values when using multiple schemas and domains in a db. Fixes #7914
-
- *Arturo Pie*
-
-* Learn ActiveRecord::QueryMethods#order work with hash arguments
-
- When symbol or hash passed we convert it to Arel::Nodes::Ordering.
- If we pass invalid direction(like name: :DeSc) ActiveRecord::QueryMethods#order will raise an exception
-
- User.order(:name, email: :desc)
- # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC
-
- *Tima Maslyuchenko*
-
-* Rename `ActiveRecord::Fixtures` class to `ActiveRecord::FixtureSet`.
- Instances of this class normally hold a collection of fixtures (records)
- loaded either from a single YAML file, or from a file and a folder
- with the same name. This change make the class name singular and makes
- the class easier to distinguish from the modules like
- `ActiveRecord::TestFixtures`, which operates on multiple fixture sets,
- or `DelegatingFixtures`, `::Fixtures`, etc.,
- and from the class `ActiveRecord::Fixture`, which corresponds to a single
- fixture.
-
- *Alexey Muranov*
-
-* The postgres adapter now supports tables with capital letters.
- Fixes #5920.
-
- *Yves Senn*
-
-* `CollectionAssociation#count` returns `0` without querying if the
- parent record is not persisted.
-
- Before:
-
- person.pets.count
- # SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" IS NULL
- # => 0
-
- After:
-
- person.pets.count
- # fires without sql query
- # => 0
-
- *Francesco Rodriguez*
-
-* Fix `reset_counters` crashing on `has_many :through` associations.
- Fixes #7822.
-
- *lulalala*
-
-* Support for partial inserts.
-
- When inserting new records, only the fields which have been changed
- from the defaults will actually be included in the INSERT statement.
- The other fields will be populated by the database.
-
- This is more efficient, and also means that it will be safe to
- remove database columns without getting subsequent errors in running
- app processes (so long as the code in those processes doesn't
- contain any references to the removed column).
-
- The `partial_updates` configuration option is now renamed to
- `partial_writes` to reflect the fact that it now impacts both inserts
- and updates.
-
- *Jon Leighton*
-
-* Allow before and after validations to take an array of lifecycle events
-
- *John Foley*
-
-* Support for specifying transaction isolation level
-
- If your database supports setting the isolation level for a transaction, you can set
- it like so:
-
- Post.transaction(isolation: :serializable) do
- # ...
- end
-
- Valid isolation levels are:
-
- * `:read_uncommitted`
- * `:read_committed`
- * `:repeatable_read`
- * `:serializable`
-
- You should consult the documentation for your database to understand the
- semantics of these different levels:
-
- * http://www.postgresql.org/docs/9.1/static/transaction-iso.html
- * https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
-
- An `ActiveRecord::TransactionIsolationError` will be raised if:
-
- * The adapter does not support setting the isolation level
- * You are joining an existing open transaction
- * You are creating a nested (savepoint) transaction
-
- The mysql, mysql2 and postgresql adapters support setting the transaction
- isolation level. However, support is disabled for mysql versions below 5,
- because they are affected by a bug (http://bugs.mysql.com/bug.php?id=39170)
- which means the isolation level gets persisted outside the transaction.
-
- *Jon Leighton*
-
-* `ActiveModel::ForbiddenAttributesProtection` is included by default
- in Active Record models. Check the docs of `ActiveModel::ForbiddenAttributesProtection`
- for more details.
-
- *Guillermo Iguaran*
-
-* Remove integration between Active Record and
- `ActiveModel::MassAssignmentSecurity`, `protected_attributes` gem
- should be added to use `attr_accessible`/`attr_protected`. Mass
- assignment options has been removed from all the AR methods that
- used it (ex. `AR::Base.new`, `AR::Base.create`, `AR::Base#update_attributes`, etc).
-
- *Guillermo Iguaran*
-
-* Fix the return of querying with an empty hash.
- Fixes #6971.
-
- User.where(token: {})
-
- Before:
-
- #=> SELECT * FROM users;
-
- After:
-
- #=> SELECT * FROM users WHERE 1=0;
-
- *Damien Mathieu*
-
-* Fix creation of through association models when using `collection=[]`
- on a `has_many :through` association from an unsaved model.
- Fixes #7661.
-
- *Ernie Miller*
-
-* Explain only normal CRUD sql (select / update / insert / delete).
- Fix problem that explains unexplainable sql.
- Closes #7544 #6458.
-
- *kennyj*
-
-* You can now override the generated accessor methods for stored attributes
- and reuse the original behavior with `read_store_attribute` and `write_store_attribute`,
- which are counterparts to `read_attribute` and `write_attribute`.
-
- *Matt Jones*
-
-* Accept `belongs_to` (including polymorphic) association keys in queries.
-
- The following queries are now equivalent:
-
- Post.where(author: author)
- Post.where(author_id: author)
-
- PriceEstimate.where(estimate_of: treasure)
- PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure)
-
- *Peter Brown*
-
-* Use native `mysqldump` command instead of `structure_dump` method
- when dumping the database structure to a sql file. Fixes #5547.
-
- *kennyj*
-
-* PostgreSQL inet and cidr types are converted to `IPAddr` objects.
-
- *Dan McClain*
-
-* PostgreSQL array type support. Any datatype can be used to create an
- array column, with full migration and schema dumper support.
-
- To declare an array column, use the following syntax:
-
- create_table :table_with_arrays do |t|
- t.integer :int_array, array: true
- # integer[]
- t.integer :int_array, array: true, length: 2
- # smallint[]
- t.string :string_array, array: true, length: 30
- # char varying(30)[]
- end
-
- This respects any other migration detail (limits, defaults, etc).
- Active Record will serialize and deserialize the array columns on
- their way to and from the database.
-
- One thing to note: PostgreSQL does not enforce any limits on the
- number of elements, and any array can be multi-dimensional. Any
- array that is multi-dimensional must be rectangular (each sub array
- must have the same number of elements as its siblings).
-
- If the `pg_array_parser` gem is available, it will be used when
- parsing PostgreSQL's array representation.
-
- *Dan McClain*
-
-* Attribute predicate methods, such as `article.title?`, will now raise
- `ActiveModel::MissingAttributeError` if the attribute being queried for
- truthiness was not read from the database, instead of just returning `false`.
-
- *Ernie Miller*
-
-* `ActiveRecord::SchemaDumper` uses Ruby 1.9 style hash, which means that the
- schema.rb file will be generated using this new syntax from now on.
-
- *Konstantin Shabanov*
-
-* Map interval with precision to string datatype in PostgreSQL. Fixes #7518.
-
- *Yves Senn*
-
-* Fix eagerly loading associations without primary keys. Fixes #4976.
-
- *Kelley Reynolds*
-
-* Rails now raise an exception when you're trying to run a migration that has an invalid
- file name. Only lower case letters, numbers, and '_' are allowed in migration's file name.
- Please see #7419 for more details.
-
- *Jan Bernacki*
-
-* Fix bug when calling `store_accessor` multiple times.
- Fixes #7532.
-
- *Matt Jones*
-
-* Fix store attributes that show the changes incorrectly.
- Fixes #7532.
-
- *Matt Jones*
-
-* Fix `ActiveRecord::Relation#pluck` when columns or tables are reserved words.
-
- *Ian Lesperance*
-
-* Allow JSON columns to be created in PostgreSQL and properly encoded/decoded.
- to/from database.
-
- *Dickson S. Guedes*
-
-* Fix time column type casting for invalid time string values to correctly return `nil`.
-
- *Adam Meehan*
-
-* Allow to pass Symbol or Proc into `:limit` option of #accepts_nested_attributes_for.
-
- *Mikhail Dieterle*
-
-* ActiveRecord::SessionStore has been extracted from Active Record as `activerecord-session_store`
- gem. Please read the `README.md` file on the gem for the usage.
-
- *Prem Sichanugrist*
-
-* Fix `reset_counters` when there are multiple `belongs_to` association with the
- same foreign key and one of them have a counter cache.
- Fixes #5200.
-
- *Dave Desrochers*
-
-* `serialized_attributes` and `_attr_readonly` become class method only. Instance reader methods are deprecated.
+* Abort a rake task when missing db/structure.sql like `db:schema:load` task.
*kennyj*
-* Round usec when comparing timestamp attributes in the dirty tracking.
- Fixes #6975.
-
- *kennyj*
-
-* Use inversed parent for first and last child of `has_many` association.
-
- *Ravil Bayramgalin*
-
-* Fix `Column.microseconds` and `Column.fast_string_to_time` to avoid converting
- timestamp seconds to a float, since it occasionally results in inaccuracies
- with microsecond-precision times. Fixes #7352.
-
- *Ari Pollak*
-
-* Fix AR#dup to nullify the validation errors in the dup'ed object. Previously the original
- and the dup'ed object shared the same errors.
-
- *Christian Seiler*
-
-* Raise `ArgumentError` if list of attributes to change is empty in `update_all`.
-
- *Roman Shatsov*
-
-* Fix AR#create to return an unsaved record when AR::RecordInvalid is
- raised. Fixes #3217.
-
- *Dave Yeu*
-
-* Fixed table name prefix that is generated in engines for namespaced models.
-
- *Wojciech Wnętrzak*
-
-* Make sure `:environment` task is executed before `db:schema:load` or `db:structure:load`.
- Fixes #4772.
-
- *Seamus Abshere*
-
-* Allow Relation#merge to take a proc.
-
- This was requested by DHH to allow creating of one's own custom
- association macros.
-
- For example:
-
- module Commentable
- def has_many_comments(extra)
- has_many :comments, -> { where(:foo).merge(extra) }
- end
- end
-
- class Post < ActiveRecord::Base
- extend Commentable
- has_many_comments -> { where(:bar) }
- end
-
- *Jon Leighton*
-
-* Add CollectionProxy#scope.
-
- This can be used to get a Relation from an association.
-
- Previously we had a #scoped method, but we're deprecating that for
- AR::Base, so it doesn't make sense to have it here.
-
- This was requested by DHH, to facilitate code like this:
-
- Project.scope.order('created_at DESC').page(current_page).tagged_with(@tag).limit(5).scoping do
- @topics = @project.topics.scope
- @todolists = @project.todolists.scope
- @attachments = @project.attachments.scope
- @documents = @project.documents.scope
- end
-
- *Jon Leighton*
-
-* Add `Relation#load`.
-
- This method explicitly loads the records and then returns `self`.
-
- Rather than deciding between "do I want an array or a relation?",
- most people are actually asking themselves "do I want to eager load
- or lazy load?" Therefore, this method provides a way to explicitly
- eager-load without having to switch from a `Relation` to an array.
-
- Example:
-
- @posts = Post.where(published: true).load
-
- *Jon Leighton*
-
-* `Relation#order`: make new order prepend old one.
-
- User.order("name asc").order("created_at desc")
- # SELECT * FROM users ORDER BY created_at desc, name asc
-
- This also affects order defined in `default_scope` or any kind of associations.
-
- *Bogdan Gusiev*
-
-* `Model.all` now returns an `ActiveRecord::Relation`, rather than an
- array of records. Use `Relation#to_a` if you really want an array.
-
- In some specific cases, this may cause breakage when upgrading.
- However in most cases the `ActiveRecord::Relation` will just act as a
- lazy-loaded array and there will be no problems.
-
- Note that calling `Model.all` with options (e.g.
- `Model.all(conditions: '...')` was already deprecated, but it will
- still return an array in order to make the transition easier.
-
- `Model.scoped` is deprecated in favour of `Model.all`.
-
- `Relation#all` still returns an array, but is deprecated (since it
- would serve no purpose if we made it return a `Relation`).
-
- *Jon Leighton*
-
-* `:finder_sql` and `:counter_sql` options on collection associations
- are deprecated. Please transition to using scopes.
-
- *Jon Leighton*
-
-* `:insert_sql` and `:delete_sql` options on `has_and_belongs_to_many`
- associations are deprecated. Please transition to using `has_many
- :through`.
-
- *Jon Leighton*
-
-* Added `#update_columns` method which updates the attributes from
- the passed-in hash without calling save, hence skipping validations and
- callbacks. `ActiveRecordError` will be raised when called on new objects
- or when at least one of the attributes is marked as read only.
-
- post.attributes # => {"id"=>2, "title"=>"My title", "body"=>"My content", "author"=>"Peter"}
- post.update_columns(title: 'New title', author: 'Sebastian') # => true
- post.attributes # => {"id"=>2, "title"=>"New title", "body"=>"My content", "author"=>"Sebastian"}
-
- *Sebastian Martinez + Rafael Mendonça França*
-
-* The migration generator now creates a join table with (commented) indexes every time
- the migration name contains the word `join_table`:
-
- rails g migration create_join_table_for_artists_and_musics artist_id:index music_id
-
- *Aleksey Magusev*
-
-* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to`
- and `remove_belongs_to` are acceptable. References are reversible.
-
- Examples:
-
- # Create a user_id column
- add_reference(:products, :user)
- # Create a supplier_id, supplier_type columns and appropriate index
- add_reference(:products, :supplier, polymorphic: true, index: true)
- # Remove polymorphic reference
- remove_reference(:products, :supplier, polymorphic: true)
-
- *Aleksey Magusev*
-
-* Add `:default` and `:null` options to `column_exists?`.
-
- column_exists?(:testings, :taggable_id, :integer, null: false)
- column_exists?(:testings, :taggable_type, :string, default: 'Photo')
-
- *Aleksey Magusev*
-
-* `ActiveRecord::Relation#inspect` now makes it clear that you are
- dealing with a `Relation` object rather than an array:.
-
- User.where(age: 30).inspect
- # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]>
-
- User.where(age: 30).to_a.inspect
- # => [#<User ...>, #<User ...>]
-
- The number of records displayed will be limited to 10.
-
- *Brian Cardarella, Jon Leighton & Damien Mathieu*
-
-* Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later.
- Example:
-
- development:
- adapter: postgresql
- host: localhost
- database: rails_development
- username: foo
- password: bar
- encoding: UTF8
- collation: ja_JP.UTF8
- ctype: ja_JP.UTF8
-
- *kennyj*
-
-* Changed `validates_presence_of` on an association so that children objects
- do not validate as being present if they are marked for destruction. This
- prevents you from saving the parent successfully and thus putting the parent
- in an invalid state.
-
- *Nick Monje & Brent Wheeldon*
-
-* `FinderMethods#exists?` now returns `false` with the `false` argument.
-
- *Egor Lynko*
-
-* Added support for specifying the precision of a timestamp in the postgresql
- adapter. So, instead of having to incorrectly specify the precision using the
- `:limit` option, you may use `:precision`, as intended. For example, in a migration:
-
- def change
- create_table :foobars do |t|
- t.timestamps precision: 0
- end
- end
-
- *Tony Schneider*
-
-* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an
- array of arrays containing the typecasted values:
-
- Person.pluck(:id, :name)
- # SELECT people.id, people.name FROM people
- # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
-
- *Jeroen van Ingen & Carlos Antonio da Silva*
-
-* Improve the derivation of HABTM join table name to take account of nesting.
- It now takes the table names of the two models, sorts them lexically and
- then joins them, stripping any common prefix from the second table name.
-
- Some examples:
-
- Top level models (Category <=> Product)
- Old: categories_products
- New: categories_products
-
- Top level models with a global table_name_prefix (Category <=> Product)
- Old: site_categories_products
- New: site_categories_products
-
- Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: categories_products
-
- Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product)
- Old: categories_products
- New: admin_categories_products
-
- Nested models in a parent model (Catalog::Category <=> Catalog::Product)
- Old: categories_products
- New: catalog_categories_products
-
- Nested models in different parent models (Catalog::Category <=> Content::Page)
- Old: categories_pages
- New: catalog_categories_content_pages
-
- *Andrew White*
-
-* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of
- this is to move when the exceptions are raised from the point of declaration
- to when the association is built. This is consistent with other association
- validity checks.
-
- *Andrew White*
-
-* Added `stored_attributes` hash which contains the attributes stored using
- `ActiveRecord::Store`. This allows you to retrieve the list of attributes
- you've defined.
-
- class User < ActiveRecord::Base
- store :settings, accessors: [:color, :homepage]
- end
-
- User.stored_attributes[:settings] # [:color, :homepage]
-
- *Joost Baaij & Carlos Antonio da Silva*
-
-* PostgreSQL default log level is now 'warning', to bypass the noisy notice
- messages. You can change the log level using the `min_messages` option
- available in your config/database.yml.
-
- *kennyj*
-
-* Add uuid datatype support to PostgreSQL adapter.
-
- *Konstantin Shabanov*
-
-* Added `ActiveRecord::Migration.check_pending!` that raises an error if
- migrations are pending.
-
- *Richard Schneeman*
-
-* Added `#destroy!` which acts like `#destroy` but will raise an
- `ActiveRecord::RecordNotDestroyed` exception instead of returning `false`.
-
- *Marc-André Lafortune*
-
-* Added support to `CollectionAssociation#delete` for passing `fixnum`
- or `string` values as record ids. This finds the records responding
- to the `id` and executes delete on them.
-
- class Person < ActiveRecord::Base
- has_many :pets
- end
-
- person.pets.delete("1") # => [#<Pet id: 1>]
- person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>]
-
- *Francesco Rodriguez*
-
-* Deprecated most of the 'dynamic finder' methods. All dynamic methods
- except for `find_by_...` and `find_by_...!` are deprecated. Here's
- how you can rewrite the code:
-
- * `find_all_by_...` can be rewritten using `where(...)`
- * `find_last_by_...` can be rewritten using `where(...).last`
- * `scoped_by_...` can be rewritten using `where(...)`
- * `find_or_initialize_by_...` can be rewritten using
- `where(...).first_or_initialize`
- * `find_or_create_by_...` can be rewritten using
- `find_or_create_by(...)` or where(...).first_or_create`
- * `find_or_create_by_...!` can be rewritten using
- `find_or_create_by!(...) or `where(...).first_or_create!`
-
- The implementation of the deprecated dynamic finders has been moved
- to the `activerecord-deprecated_finders` gem. See below for details.
-
- *Jon Leighton*
-
-* Deprecated the old-style hash based finder API. This means that
- methods which previously accepted "finder options" no longer do. For
- example this:
-
- Post.find(:all, conditions: { comments_count: 10 }, limit: 5)
-
- Should be rewritten in the new style which has existed since Rails 3:
-
- Post.where(comments_count: 10).limit(5)
-
- Note that as an interim step, it is possible to rewrite the above as:
-
- Post.all.merge(where: { comments_count: 10 }, limit: 5)
-
- This could save you a lot of work if there is a lot of old-style
- finder usage in your application.
-
- `Relation#merge` now accepts a hash of
- options, but they must be identical to the names of the equivalent
- finder method. These are mostly identical to the old-style finder
- option names, except in the following cases:
-
- * `:conditions` becomes `:where`.
- * `:include` becomes `:includes`.
-
- The code to implement the deprecated features has been moved out to the
- `activerecord-deprecated_finders` gem. This gem is a dependency of Active
- Record in Rails 4.0, so the interface works out of the box. It will no
- longer be a dependency from Rails 4.1 (you'll need to add it to the
- `Gemfile` in 4.1), and will be maintained until Rails 5.0.
-
- *Jon Leighton*
-
-* It's not possible anymore to destroy a model marked as read only.
-
- *Johannes Barre*
-
-* Added ability to ActiveRecord::Relation#from to accept other ActiveRecord::Relation objects.
-
- Record.from(subquery)
- Record.from(subquery, :a)
-
- *Radoslav Stankov*
-
-* Added custom coders support for ActiveRecord::Store. Now you can set
- your custom coder like this:
-
- store :settings, accessors: [ :color, :homepage ], coder: JSON
-
- *Andrey Voronkov*
-
-* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by
- default to avoid silent data loss. This can be disabled by specifying
- `strict: false` in your `database.yml`.
-
- *Michael Pearson*
-
-* Added default order to `first` to assure consistent results among
- different database engines. Introduced `take` as a replacement to
- the old behavior of `first`.
-
- *Marcelo Silveira*
-
-* Added an `:index` option to automatically create indexes for references
- and belongs_to statements in migrations.
-
- The `references` and `belongs_to` methods now support an `index`
- option that receives either a boolean value or an options hash
- that is identical to options available to the add_index method:
-
- create_table :messages do |t|
- t.references :person, index: true
- end
-
- Is the same as:
-
- create_table :messages do |t|
- t.references :person
- end
- add_index :messages, :person_id
-
- Generators have also been updated to use the new syntax.
-
- *Joshua Wood*
-
-* Added `#find_by` and `#find_by!` to mirror the functionality
- provided by dynamic finders in a way that allows dynamic input more
- easily:
-
- Post.find_by name: 'Spartacus', rating: 4
- Post.find_by "published_at < ?", 2.weeks.ago
- Post.find_by! name: 'Spartacus'
-
- *Jon Leighton*
-
-* Added ActiveRecord::Base#slice to return a hash of the given methods with
- their names as keys and returned values as values.
-
- *Guillermo Iguaran*
-
-* Deprecate eager-evaluated scopes.
-
- Don't use this:
-
- scope :red, where(color: 'red')
- default_scope where(color: 'red')
-
- Use this:
-
- scope :red, -> { where(color: 'red') }
- default_scope { where(color: 'red') }
-
- The former has numerous issues. It is a common newbie gotcha to do
- the following:
-
- scope :recent, where(published_at: Time.now - 2.weeks)
-
- Or a more subtle variant:
-
- scope :recent, -> { where(published_at: Time.now - 2.weeks) }
- scope :recent_red, recent.where(color: 'red')
-
- Eager scopes are also very complex to implement within Active
- Record, and there are still bugs. For example, the following does
- not do what you expect:
-
- scope :remove_conditions, except(:where)
- where(...).remove_conditions # => still has conditions
-
- *Jon Leighton*
-
-* Remove IdentityMap
-
- IdentityMap has never graduated to be an "enabled-by-default" feature, due
- to some inconsistencies with associations, as described in this commit:
-
- https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6
-
- Hence the removal from the codebase, until such issues are fixed.
-
- *Carlos Antonio da Silva*
-
-* Added the schema cache dump feature.
-
- `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
- because we want to boot rails more quickly when we have many models.
-
- Usage notes:
-
- 1) execute rake task.
- RAILS_ENV=production bundle exec rake db:schema:cache:dump
- => generate db/schema_cache.dump
-
- 2) add config.active_record.use_schema_cache_dump = true in config/production.rb. BTW, true is default.
-
- 3) boot rails.
- RAILS_ENV=production bundle exec rails server
- => use db/schema_cache.dump
-
- 4) If you remove clear dumped cache, execute rake task.
- RAILS_ENV=production bundle exec rake db:schema:cache:clear
- => remove db/schema_cache.dump
-
- *kennyj*
-
-* Added support for partial indices to PostgreSQL adapter.
-
- The `add_index` method now supports a `where` option that receives a
- string with the partial index criteria.
-
- add_index(:accounts, :code, where: 'active')
-
- Generates
-
- CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
-
- *Marcelo Silveira*
-
-* Implemented ActiveRecord::Relation#none method.
-
- The `none` method returns a chainable relation with zero records
- (an instance of the NullRelation class).
-
- Any subsequent condition chained to the returned relation will continue
- generating an empty relation and will not fire any query to the database.
-
- *Juanjo Bazán*
-
-* Added the `ActiveRecord::NullRelation` class implementing the null
- object pattern for the Relation class.
-
- *Juanjo Bazán*
-
-* Added new `dependent: :restrict_with_error` option. This will add
- an error to the model, rather than raising an exception.
-
- The `:restrict` option is renamed to `:restrict_with_exception` to
- make this distinction explicit.
-
- *Manoj Kumar & Jon Leighton*
-
-* Added `create_join_table` migration helper to create HABTM join tables.
-
- create_join_table :products, :categories
- # =>
- # create_table :categories_products, id: false do |td|
- # td.integer :product_id, null: false
- # td.integer :category_id, null: false
- # end
-
- *Rafael Mendonça França*
-
-* The primary key is always initialized in the @attributes hash to `nil` (unless
- another value has been specified).
-
- *Aaron Paterson*
-
-* In previous releases, the following would generate a single query with
- an `OUTER JOIN comments`, rather than two separate queries:
-
- Post.includes(:comments)
- .where("comments.name = 'foo'")
-
- This behaviour relies on matching SQL string, which is an inherently
- flawed idea unless we write an SQL parser, which we do not wish to
- do.
-
- Therefore, it is now deprecated.
-
- To avoid deprecation warnings and for future compatibility, you must
- explicitly state which tables you reference, when using SQL snippets:
-
- Post.includes(:comments)
- .where("comments.name = 'foo'")
- .references(:comments)
-
- Note that you do not need to explicitly specify references in the
- following cases, as they can be automatically inferred:
-
- Post.includes(:comments).where(comments: { name: 'foo' })
- Post.includes(:comments).where('comments.name' => 'foo')
- Post.includes(:comments).order('comments.name')
-
- You do not need to worry about this unless you are doing eager
- loading. Basically, don't worry unless you see a deprecation warning
- or (in future releases) an SQL error due to a missing JOIN.
-
- *Jon Leighton*
-
-* Support for the `schema_info` table has been dropped. Please
- switch to `schema_migrations`.
-
- *Aaron Patterson*
-
-* Connections *must* be closed at the end of a thread. If not, your
- connection pool can fill and an exception will be raised.
-
- *Aaron Patterson*
-
-* PostgreSQL hstore records can be created.
-
- *Aaron Patterson*
-
-* PostgreSQL hstore types are automatically deserialized from the database.
-
- *Aaron Patterson*
-
-
-Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index ed1e171d58..822e460918 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -190,7 +190,7 @@ The latest version of Active Record can be installed with RubyGems:
% [sudo] gem install activerecord
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/activerecord
@@ -204,7 +204,7 @@ Active Record is released under the MIT license:
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 3e3475f709..337106cb92 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -24,6 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'activesupport', version
s.add_dependency 'activemodel', version
- s.add_dependency 'arel', '~> 4.0.0.beta2'
- s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3'
+ s.add_dependency 'arel', '~> 4.0.0'
+ s.add_dependency 'activerecord-deprecated_finders', '~> 1.0.2'
end
diff --git a/activerecord/examples/associations.png b/activerecord/examples/associations.png
deleted file mode 100644
index 661c7a8bbc..0000000000
--- a/activerecord/examples/associations.png
+++ /dev/null
Binary files differ
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index c33f03f13f..0330c0f37f 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -35,8 +35,8 @@ module ActiveRecord
autoload :Base
autoload :Callbacks
autoload :Core
- autoload :CounterCache
autoload :ConnectionHandling
+ autoload :CounterCache
autoload :DynamicMatchers
autoload :Explain
autoload :Inheritance
@@ -50,12 +50,14 @@ module ActiveRecord
autoload :Querying
autoload :ReadonlyAttributes
autoload :Reflection
+ autoload :RuntimeRegistry
autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
autoload :SchemaMigration
autoload :Scoping
autoload :Serialization
+ autoload :StatementCache
autoload :Store
autoload :Timestamp
autoload :Transactions
@@ -69,8 +71,8 @@ module ActiveRecord
autoload :Aggregations
autoload :Associations
- autoload :AttributeMethods
autoload :AttributeAssignment
+ autoload :AttributeMethods
autoload :AutosaveAssociation
autoload :Relation
@@ -143,6 +145,10 @@ module ActiveRecord
autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
autoload :PostgreSQLDatabaseTasks,
'active_record/tasks/postgresql_database_tasks'
+
+ autoload :FirebirdDatabaseTasks, 'active_record/tasks/firebird_database_tasks'
+ autoload :SqlserverDatabaseTasks, 'active_record/tasks/sqlserver_database_tasks'
+ autoload :OracleDatabaseTasks, 'active_record/tasks/oracle_database_tasks'
end
autoload :TestCase
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index eb08f72286..5e5995f566 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -988,7 +988,7 @@ module ActiveRecord
# associated objects themselves. So with +has_and_belongs_to_many+ and +has_many+
# <tt>:through</tt>, the join records will be deleted, but the associated records won't.
#
- # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by_name('food'))</tt>
+ # This makes sense if you think about it: if you were to call <tt>post.tags.delete(Tag.find_by(name: 'food'))</tt>
# you would want the 'food' tag to be unlinked from the post, rather than for the tag itself
# to be removed from the database.
#
@@ -1073,6 +1073,9 @@ module ActiveRecord
# with +attributes+, linked to this object through a foreign key, and that has already
# been saved (if it passed the validation). *Note*: This only works if the base model
# already exists in the DB, not if it is a new (unsaved) record!
+ # [collection.create!(attributes = {})]
+ # 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>.)
@@ -1094,6 +1097,7 @@ module ActiveRecord
# * <tt>Firm#clients.exists?(name: 'ACME')</tt> (similar to <tt>Client.exists?(name: 'ACME', firm_id: firm.id)</tt>)
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
+ # * <tt>Firm#clients.create!</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save!</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1115,11 +1119,11 @@ module ActiveRecord
# similar callbacks may affect the :dependent behavior, and the
# :dependent behavior may affect other callbacks.
#
- # * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
- # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
+ # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
+ # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 4c4b0f08e5..db0553ea76 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -30,7 +30,7 @@ module ActiveRecord
reset_scope
end
- # Returns the name of the table of the related class:
+ # Returns the name of the table of the associated class:
#
# post.comments.aliased_table_name # => "comments"
#
@@ -92,7 +92,7 @@ module ActiveRecord
# The scope for this association.
#
# Note that the association_scope is merged into the target_scope only when the
- # scoped method is called. This is because at that point the call may be surrounded
+ # scope method is called. This is because at that point the call may be surrounded
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
# actually gets built.
def association_scope
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # This class of the target. belongs_to polymorphic overrides this to look at the
+ # Returns the class of the target. belongs_to polymorphic overrides this to look at the
# polymorphic_type field on the owner.
def klass
reflection.klass
@@ -217,7 +217,8 @@ module ActiveRecord
reflection.inverse_of
end
- # Is this association invertible? Can be redefined by subclasses.
+ # Returns true if inverse association on the given record needs to be set.
+ # This method is redefined by subclasses.
def invertible_for?(record)
inverse_reflection_for(record)
end
@@ -235,6 +236,7 @@ module ActiveRecord
skip_assign = [reflection.foreign_key, reflection.type].compact
attributes = create_scope.except(*(record.changed - skip_assign))
record.assign_attributes(attributes)
+ set_inverse_instance(record)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a9525436fb..aa5551fe0c 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -101,6 +101,7 @@ module ActiveRecord
scope.includes! item.includes_values
scope.where_values += item.where_values
+ scope.order_values |= item.order_values
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 9ac561b997..543a0247d1 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -25,9 +25,10 @@ module ActiveRecord::Associations::Builder
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_counter_cache_after_create_for_#{name}
- record = #{name}
- record.class.increment_counter(:#{cache_column}, record.id) unless record.nil?
- @_after_create_counter_called = true
+ if record = #{name}
+ record.class.increment_counter(:#{cache_column}, record.id)
+ @_after_create_counter_called = true
+ end
end
def belongs_to_counter_cache_before_destroy_for_#{name}
@@ -66,8 +67,19 @@ module ActiveRecord::Associations::Builder
def add_touch_callbacks(reflection)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
def belongs_to_touch_after_save_or_destroy_for_#{name}
- record = #{name}
+ foreign_key_field = #{reflection.foreign_key.inspect}
+ old_foreign_id = attribute_was(foreign_key_field)
+
+ if old_foreign_id
+ klass = association(#{name.inspect}).klass
+ old_record = klass.find_by(klass.primary_key => old_foreign_id)
+ if old_record
+ old_record.touch #{options[:touch].inspect if options[:touch] != true}
+ end
+ end
+
+ record = #{name}
unless record.nil? || record.new_record?
record.touch #{options[:touch].inspect if options[:touch] != true}
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 2385c90c1a..2a00ac1386 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -79,8 +79,20 @@ module ActiveRecord
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
else
- if options[:finder_sql] || options[:inverse_of]
+ if options[:finder_sql]
find_by_scan(*args)
+ elsif options[:inverse_of]
+ args = args.flatten
+ raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank?
+
+ result = find_by_scan(*args)
+
+ result_size = Array(result).size
+ if !result || result_size != args.size
+ scope.raise_record_not_found_exception!(args, result_size, args.size)
+ else
+ result
+ end
else
scope.find(*args)
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index c2add32aa6..56e57cc36e 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -92,7 +92,7 @@ module ActiveRecord
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
#
# *Second:* You can pass a block so it can be used just like Array#select.
- # This build an array of objects from the database for the scope,
+ # This builds an array of objects from the database for the scope,
# converting them into an array and iterating through them using
# Array#select.
#
@@ -228,6 +228,7 @@ module ActiveRecord
def build(attributes = {}, &block)
@association.build(attributes, &block)
end
+ alias_method :new, :build
# Returns a new object of the collection type that has been instantiated with
# attributes, linked to this object and that has already been saved (if it
@@ -303,7 +304,7 @@ module ActiveRecord
@association.concat(*records)
end
- # Replace this collection with +other_array+. This will perform a diff
+ # Replaces this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
# class Person < ActiveRecord::Base
@@ -832,8 +833,6 @@ module ActiveRecord
@association.include?(record)
end
- alias_method :new, :build
-
def proxy_association
@association
end
@@ -848,10 +847,8 @@ module ActiveRecord
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- association = @association
-
- @association.scope.extending! do
- define_method(:proxy_association) { association }
+ @association.scope.tap do |scope|
+ scope.proxy_association = @association
end
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 98bd010f70..920038a543 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -25,9 +25,8 @@ module ActiveRecord
raise_on_type_mismatch!(record) if record
load_target
- # If target and record are nil, or target is equal to record,
- # we don't need to have transaction.
- if (target || record) && target != record
+ return self.target if !(target || record)
+ if (target != record) || record.changed?
transaction_if(save) do
remove_target!(options[:dependent]) if target && !target.destroyed?
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index f40368cfeb..28e081c03c 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -5,10 +5,31 @@ module ActiveRecord
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
-
+ attr_reader :join_parts, :reflections, :alias_tracker, :base_klass
+
+ # base is the base class on which operation is taking place.
+ # associations is the list of associations which are joined using hash, symbol or array.
+ # joins is the list of all string join commnads and arel nodes.
+ #
+ # Example :
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # has_many :patients, through: :appointments
+ # end
+ #
+ # If I execute `@physician.patients.to_a` then
+ # base #=> Physician
+ # associations #=> []
+ # joins #=> [#<Arel::Nodes::InnerJoin: ...]
+ #
+ # However if I execute `Physician.joins(:appointments).to_a` then
+ # base #=> Physician
+ # associations #=> [:appointments]
+ # joins #=> []
+ #
def initialize(base, associations, joins)
- @active_record = base
+ @base_klass = base
@table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@@ -54,10 +75,12 @@ module ActiveRecord
parent
}.uniq
- remove_duplicate_results!(active_record, records, @associations)
+ remove_duplicate_results!(base_klass, records, @associations)
records
end
+ protected
+
def remove_duplicate_results!(base, records, associations)
case associations
when Symbol, String
@@ -88,8 +111,6 @@ module ActiveRecord
end
end
- protected
-
def cache_joined_association(association)
associations = []
parent = association.parent
@@ -108,8 +129,8 @@ module ActiveRecord
parent ||= join_parts.last
case associations
when Symbol, String
- reflection = parent.reflections[associations.to_s.intern] or
- raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ reflection = parent.reflections[associations.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?"
unless join_association = find_join_association(reflection, parent)
@reflections << reflection
join_association = build_join_association(reflection, parent)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0d3b4dbab1..e4d17451dc 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -55,14 +55,19 @@ module ActiveRecord
def find_parent_in(other_join_dependency)
other_join_dependency.join_parts.detect do |join_part|
- parent == join_part
+ case parent
+ when JoinBase
+ parent.base_klass == join_part.base_klass
+ else
+ parent == join_part
+ end
end
end
- def join_to(relation)
+ def join_to(manager)
tables = @tables.dup
foreign_table = parent_table
- foreign_klass = parent.active_record
+ foreign_klass = parent.base_klass
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
@@ -75,7 +80,7 @@ module ActiveRecord
foreign_key = reflection.foreign_key
when :has_and_belongs_to_many
# Join the join table first...
- relation.from(join(
+ manager.from(join(
table,
table[reflection.foreign_key].
eq(foreign_table[reflection.active_record_primary_key])
@@ -109,15 +114,30 @@ module ActiveRecord
constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty?
end
- relation.from(join(table, constraint))
+ manager.from(join(table, constraint))
# The current table in this iteration becomes the foreign table in the next
foreign_table, foreign_klass = table, reflection.klass
end
- relation
+ manager
end
+ # Builds equality condition.
+ #
+ # Example:
+ #
+ # class Physician < ActiveRecord::Base
+ # has_many :appointments
+ # end
+ #
+ # If I execute `Physician.joins(:appointments).to_a` then
+ # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...>
+ # table #=> #<Arel::Table @name="appointments" ...>
+ # key #=> physician_id
+ # foreign_table #=> #<Arel::Table @name="physicians" ...>
+ # foreign_key #=> id
+ #
def build_constraint(reflection, table, key, foreign_table, foreign_key)
constraint = table[key].eq(foreign_table[foreign_key])
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 3920e84976..a7dacdbfd6 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -4,7 +4,7 @@ module ActiveRecord
class JoinBase < JoinPart # :nodoc:
def ==(other)
other.class == self.class &&
- other.active_record == active_record
+ other.base_klass == base_klass
end
def aliased_prefix
@@ -16,7 +16,7 @@ module ActiveRecord
end
def aliased_table_name
- active_record.table_name
+ base_klass.table_name
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 5604687b57..b534569063 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module Associations
class JoinDependency # :nodoc:
- # A JoinPart represents a part of a JoinDependency. It is an abstract class, inherited
+ # A JoinPart represents a part of a JoinDependency. It is inherited
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
# everything else is being joined onto. A JoinAssociation represents an association which
# is joining to the base. A JoinAssociation may result in more than one actual join
@@ -11,12 +11,12 @@ module ActiveRecord
# The Active Record class which this join part is associated 'about'; for a JoinBase
# this is the actual base model, for a JoinAssociation this is the target model of the
# association.
- attr_reader :active_record
+ attr_reader :base_klass
- delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record
+ delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass
- def initialize(active_record)
- @active_record = active_record
+ def initialize(base_klass)
+ @base_klass = base_klass
@cached_record = {}
@column_names_with_alias = nil
end
@@ -70,7 +70,7 @@ module ActiveRecord
end
def instantiate(row)
- @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
+ @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row))
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
index 38bc7ce7da..157b627ad5 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb
@@ -5,9 +5,13 @@ module ActiveRecord
include ThroughAssociation
def associated_records_by_owner
- super.each do |owner, records|
- records.uniq! if reflection_scope.distinct_value
+ records_by_owner = super
+
+ if reflection_scope.distinct_value
+ records_by_owner.each_value { |records| records.uniq! }
end
+
+ records_by_owner
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 43520142bf..35f29b37a2 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -14,7 +14,7 @@ module ActiveRecord
def target_scope
scope = super
chain[1..-1].each do |reflection|
- scope = scope.merge(
+ scope.merge!(
reflection.klass.all.with_default_scope.
except(:select, :create_with, :includes, :preload, :joins, :eager_load)
)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index ecfa556ab4..e536f5ebcc 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -81,7 +81,7 @@ module ActiveRecord
end
def extract_callstack_for_multiparameter_attributes(pairs)
- attributes = { }
+ attributes = {}
pairs.each do |(multiparameter_name, value)|
attribute_name = multiparameter_name.split("(").first
@@ -146,7 +146,7 @@ module ActiveRecord
end
else
# else column is a timestamp, so if Date bits were not provided, error
- validate_missing_parameters!([1,2,3])
+ validate_required_parameters!([1,2,3])
# If Date bits were provided but blank, then return nil
return if blank_date_parameter?
@@ -172,14 +172,14 @@ module ActiveRecord
def read_other(klass)
max_position = extract_max_param
positions = (1..max_position)
- validate_missing_parameters!(positions)
+ validate_required_parameters!(positions)
set_values = values.values_at(*positions)
klass.new(*set_values)
end
# Checks whether some blank date parameter exists. Note that this is different
- # than the validate_missing_parameters! method, since it just checks for blank
+ # than the validate_required_parameters! method, since it just checks for blank
# positions instead of missing ones, and does not raise in case one blank position
# exists. The caller is responsible to handle the case of this returning true.
def blank_date_parameter?
@@ -187,7 +187,7 @@ module ActiveRecord
end
# If some position is not provided, it errors out a missing parameter exception.
- def validate_missing_parameters!(positions)
+ def validate_required_parameters!(positions)
if missing_parameter = positions.detect { |position| !values.key?(position) }
raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter})")
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index aa92343f76..609c6e8cab 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -56,7 +56,7 @@ module ActiveRecord
# # => false
def instance_method_already_implemented?(method_name)
if dangerous_attribute_method?(method_name)
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
+ raise DangerousAttributeError, "#{method_name} is defined by Active Record"
end
if superclass == Base
@@ -163,8 +163,22 @@ module ActiveRecord
# person.respond_to('age?') # => true
# person.respond_to(:nothing) # => false
def respond_to?(name, include_private = false)
+ name = name.to_s
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
- super
+ result = super
+
+ # If the result is false the answer is false.
+ return false unless result
+
+ # If the result is true then check for the select case.
+ # For queries selecting a subset of columns, return false for unselected columns.
+ # We check defined?(@attributes) not to issue warnings if called on objects that
+ # have been allocated but not yet initialized.
+ if defined?(@attributes) && @attributes.present? && self.class.column_names.include?(name)
+ return has_attribute?(name)
+ end
+
+ return true
end
# Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
@@ -328,13 +342,14 @@ module ActiveRecord
end
def attribute_method?(attr_name) # :nodoc:
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
defined?(@attributes) && @attributes.include?(attr_name)
end
private
# Returns a Hash of the Arel::Attributes and attribute values that have been
- # type casted for use in an Arel insert/update method.
+ # typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
attrs = {}
arel_table = self.class.arel_table
@@ -348,7 +363,7 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.select do |name|
- column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
+ column_for_attribute(name) && !readonly_attribute?(name)
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 25d62fdb85..7f1ebab4cd 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -11,6 +11,12 @@ module ActiveRecord
end
module ClassMethods
+ ##
+ # :method: serialized_attributes
+ #
+ # Returns a hash of all the attributes that have been specified for
+ # serialization as keys and their class restriction as values.
+
# If you have an attribute that needs to be saved to the database as an
# object, and retrieved as the same object, then specify the name of that
# attribute using this method and it will be handled automatically. The
@@ -44,6 +50,7 @@ module ActiveRecord
end
end
+ # *DEPRECATED*: Use ActiveRecord::AttributeMethods::Serialization::ClassMethods#serialized_attributes class level method instead.
def serialized_attributes
message = "Instance level serialized_attributes method is deprecated, please use class level method."
ActiveSupport::Deprecation.warn message
@@ -86,10 +93,10 @@ module ActiveRecord
# This is only added to the model when serialize is called, which
# ensures we do not make things slower when serialization is not used.
- module Behavior #:nodoc:
+ module Behavior # :nodoc:
extend ActiveSupport::Concern
- module ClassMethods
+ module ClassMethods # :nodoc:
def initialize_attributes(attributes, options = {})
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
super(attributes, options)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0df3e57947..44323ce9db 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -62,14 +62,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.author.id
- # Author.find_by_id(id).nil? # => false
+ # Author.find_by(id: id).nil? # => false
#
# post.save
# post.reload.author # => nil
#
# Now it _is_ removed from the database:
#
- # Author.find_by_id(id).nil? # => true
+ # Author.find_by(id: id).nil? # => true
#
# === One-to-many Example
#
@@ -113,14 +113,14 @@ module ActiveRecord
# Note that the model is _not_ yet removed from the database:
#
# id = post.comments.last.id
- # Comment.find_by_id(id).nil? # => false
+ # Comment.find_by(id: id).nil? # => false
#
# post.save
# post.reload.comments.length # => 1
#
# Now it _is_ removed from the database:
#
- # Comment.find_by_id(id).nil? # => true
+ # Comment.find_by(id: id).nil? # => true
module AutosaveAssociation
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index e262401da6..b06add096f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -160,10 +160,10 @@ module ActiveRecord #:nodoc:
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
# by simple queries without turning to SQL. They work by appending the name of an attribute
# to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
- # Instead of writing <tt>Person.where(user_name: user_name).first</tt>, you just do
+ # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
# <tt>Person.find_by_user_name(user_name)</tt>.
#
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
@@ -172,7 +172,7 @@ module ActiveRecord #:nodoc:
#
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
#
- # Person.where(user_name: user_name, password: password).first
+ # Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
#
# It's even possible to call these dynamic finder methods on relations and named scopes.
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 22226b2f4f..e4c484d64b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -83,7 +83,7 @@ module ActiveRecord
#
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
- # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
+ # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
# to decide whether they want to call +super+ and trigger the inherited callbacks.
#
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index f6cdc67b4d..d3d7396c91 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -3,7 +3,6 @@ require 'yaml'
module ActiveRecord
module Coders # :nodoc:
class YAMLColumn # :nodoc:
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ]
attr_accessor :object_class
@@ -24,19 +23,15 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
- begin
- obj = YAML.load(yaml)
-
- unless obj.is_a?(object_class) || obj.nil?
- raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
- end
- obj ||= object_class.new if object_class != Object
-
- obj
- rescue *RESCUE_ERRORS
- yaml
+ obj = YAML.load(yaml)
+
+ unless obj.is_a?(object_class) || obj.nil?
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
end
+ obj ||= object_class.new if object_class != Object
+
+ obj
end
end
end
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 bf2f945448..816b397fcf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -253,14 +253,6 @@ module ActiveRecord
@available = Queue.new self
end
- # Hack for tests to be able to add connections. Do not call outside of tests
- def insert_connection_for_test!(c) #:nodoc:
- synchronize do
- @connections << c
- @available.add c
- end
- end
-
# Retrieve the connection associated with the current thread, or call
# #checkout to obtain one if necessary.
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 902dbd148e..566550cbe2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -8,41 +8,21 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc:
def string_to_binary(value)
value
end
- def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale)
- end
-
def primary_key?
- type.to_sym == :primary_key
- end
-
- def to_sql
- column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- column_options = {}
- column_options[:null] = null unless null.nil?
- column_options[:default] = default unless default.nil?
- column_options[:column] = self
- add_column_options!(column_sql, column_options) unless primary_key?
- column_sql
+ primary_key || type.to_sym == :primary_key
end
-
- private
-
- def add_column_options!(sql, options)
- base.add_column_options!(sql, options)
- end
end
# Represents the schema of an SQL table in an abstract way. This class
@@ -68,19 +48,25 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns, :indexes
+ attr_accessor :indexes
+ attr_reader :name, :temporary, :options
- def initialize(base)
- @columns = []
+ def initialize(types, name, temporary, options)
@columns_hash = {}
@indexes = {}
- @base = base
+ @native = types
+ @temporary = temporary
+ @options = options
+ @name = name
end
+ def columns; @columns_hash.values; end
+
# Appends a primary key definition to the table definition.
# Can be called multiple times, but this is probably not a good idea.
- def primary_key(name)
- column(name, :primary_key)
+ def primary_key(name, type = :primary_key, options = {})
+ options[:primary_key] = true
+ column(name, type, options)
end
# Returns a ColumnDefinition for the column with name +name+.
@@ -233,20 +219,14 @@ module ActiveRecord
raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
end
- column = self[name] || new_column_definition(@base, name, type)
-
- limit = options.fetch(:limit) do
- native[type][:limit] if native[type].is_a?(Hash)
- end
-
- column.limit = limit
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
+ @columns_hash[name] = new_column_definition(name, type, options)
self
end
+ def remove_column(name)
+ @columns_hash.delete name.to_s
+ end
+
[:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
define_method column_type do |*args|
options = args.extract_options!
@@ -278,28 +258,31 @@ module ActiveRecord
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
- index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
end
end
alias :belongs_to :references
- # Returns a String whose contents are the column definitions
- # concatenated together. This string can then be prepended and appended to
- # to generate the final SQL to create the table.
- def to_sql
- columns.map { |c| c.to_sql } * ', '
- end
+ def new_column_definition(name, type, options) # :nodoc:
+ column = create_column_definition name, type
+ limit = options.fetch(:limit) do
+ native[type][:limit] if native[type].is_a?(Hash)
+ end
- private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ column.limit = limit
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ column.first = options[:first]
+ column.after = options[:after]
+ column.primary_key = type == :primary_key || options[:primary_key]
+ column
end
- def new_column_definition(base, name, type)
- definition = create_column_definition base, name, type
- @columns << definition
- @columns_hash[name] = definition
- definition
+ private
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
def primary_key_column_name
@@ -308,7 +291,24 @@ module ActiveRecord
end
def native
- @base.native_database_types
+ @native
+ end
+ end
+
+ class AlterTable # :nodoc:
+ attr_reader :adds
+
+ def initialize(td)
+ @td = td
+ @adds = []
+ end
+
+ def name; @td.name; end
+
+ def add_column(name, type, options)
+ name = name.to_s
+ type = type.to_sym
+ @adds << @td.new_column_definition(name, type, options)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index f587bf8140..cdf0cbe218 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -5,7 +5,7 @@ module ActiveRecord
# The goal of this module is to move Adapter specific column
# definitions to the Adapter instead of having it in the schema
# dumper itself. This code represents the normal case.
- # We can then redefine how certain data types may be handled in the schema dumper on the
+ # We can then redefine how certain data types may be handled in the schema dumper on the
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column, types)
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 cd4409295f..9c0c4e3ef0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -171,14 +171,14 @@ module ActiveRecord
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, options = {})
- td = create_table_definition
+ td = create_table_definition table_name, options[:temporary], options[:options]
unless options[:id] == false
pk = options.fetch(:primary_key) {
Base.get_primary_key table_name.to_s.singularize
}
- td.primary_key pk
+ td.primary_key pk, options.fetch(:id, :primary_key), options
end
yield td if block_given?
@@ -187,11 +187,7 @@ module ActiveRecord
drop_table(table_name, options)
end
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
- create_sql << "#{quote_table_name(table_name)} ("
- create_sql << td.to_sql
- create_sql << ") #{options[:options]}"
- execute create_sql
+ execute schema_creation.accept td
td.indexes.each_pair { |c,o| add_index table_name, c, o }
end
@@ -359,9 +355,9 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- execute(add_column_sql)
+ at = create_alter_table table_name
+ at.add_column(column_name, type, options)
+ execute schema_creation.accept at
end
# Removes the given columns from the table definition.
@@ -447,7 +443,7 @@ module ActiveRecord
#
# add_index(:suppliers, :name)
#
- # generates
+ # generates:
#
# CREATE INDEX suppliers_name_index ON suppliers(name)
#
@@ -455,7 +451,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true)
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
#
@@ -463,7 +459,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
#
@@ -471,13 +467,13 @@ module ActiveRecord
#
# add_index(:accounts, :name, name: 'by_name', length: 10)
#
- # generates
+ # generates:
#
# CREATE INDEX by_name ON accounts(name(10))
#
# add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
#
- # generates
+ # generates:
#
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
@@ -487,7 +483,7 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
#
- # generates
+ # generates:
#
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
@@ -497,11 +493,30 @@ module ActiveRecord
#
# add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
#
- # generates
+ # generates:
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
- # Note: only supported by PostgreSQL.
+ # ====== Creating an index with a specific method
+ #
+ # add_index(:developers, :name, using: 'btree')
+ #
+ # generates:
+ #
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
+ #
+ # Note: only supported by PostgreSQL and MySQL
+ #
+ # ====== Creating an index with a specific type
+ #
+ # add_index(:developers, :name, type: :fulltext)
+ #
+ # generates:
+ #
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
+ #
+ # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -685,6 +700,9 @@ module ActiveRecord
if options[:null] == false
sql << " NOT NULL"
end
+ if options[:auto_increment] == true
+ sql << " AUTO_INCREMENT"
+ end
end
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
@@ -749,12 +767,21 @@ module ActiveRecord
index_name = index_name(table_name, column: column_names)
if Hash === options # legacy support, since this param was a string
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal)
+ 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
@@ -769,6 +796,7 @@ module ActiveRecord
index_type = options
max_index_length = allowed_index_name_length
+ algorithm = using = nil
end
if index_name.length > max_index_length
@@ -779,7 +807,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options]
+ [index_name, index_type, index_columns, index_options, algorithm, using]
end
def index_name_for_remove(table_name, options = {})
@@ -829,8 +857,12 @@ module ActiveRecord
end
private
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
+ end
+
+ def create_alter_table(name)
+ AlterTable.new create_table_definition(name, false, {})
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 7949bcb5ce..26586f0974 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -18,6 +18,7 @@ module ActiveRecord
autoload :ColumnDefinition
autoload :TableDefinition
autoload :Table
+ autoload :AlterTable
end
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
@@ -100,6 +101,79 @@ module ActiveRecord
@visitor = nil
end
+ def valid_type?(type)
+ true
+ end
+
+ class SchemaCreation
+ def initialize(conn)
+ @conn = conn
+ @cache = {}
+ end
+
+ def accept(o)
+ m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}"
+ send m, o
+ end
+
+ private
+
+ def visit_AlterTable(o)
+ sql = "ALTER TABLE #{quote_table_name(o.name)} "
+ sql << o.adds.map { |col| visit_AddColumn col }.join(' ')
+ end
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ column_sql = "#{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(column_sql, column_options(o)) unless o.primary_key?
+ column_sql
+ end
+
+ def visit_TableDefinition(o)
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE "
+ create_sql << "#{quote_table_name(o.name)} ("
+ create_sql << o.columns.map { |c| accept c }.join(', ')
+ create_sql << ") #{o.options}"
+ create_sql
+ end
+
+ def column_options(o)
+ column_options = {}
+ column_options[:null] = o.null unless o.null.nil?
+ column_options[:default] = o.default unless o.default.nil?
+ column_options[:column] = o
+ column_options
+ end
+
+ def quote_column_name(name)
+ @conn.quote_column_name name
+ end
+
+ def quote_table_name(name)
+ @conn.quote_table_name name
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ @conn.type_to_sql type.to_sym, limit, precision, scale
+ end
+
+ def add_column_options!(column_sql, column_options)
+ @conn.add_column_options! column_sql, column_options
+ column_sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
def lease
synchronize do
unless in_use
@@ -212,6 +286,12 @@ module ActiveRecord
[]
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
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
@@ -353,7 +433,7 @@ module ActiveRecord
def translate_exception(exception, message)
# override in derived class
- ActiveRecord::StatementInvalid.new(message)
+ ActiveRecord::StatementInvalid.new(message, exception)
end
end
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 f88f5742a8..2a7b855f95 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -3,13 +3,34 @@ require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ add_column_position!(super, o)
+ end
+
+ def add_column_position!(sql, column)
+ if column.first
+ sql << " FIRST"
+ elsif column.after
+ sql << " AFTER #{quote_column_name(column.after)}"
+ end
+ sql
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
- attr_reader :collation, :strict
+ attr_reader :collation, :strict, :extra
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false)
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict
@collation = collation
-
+ @extra = extra
super(name, default, sql_type, null)
end
@@ -61,6 +82,8 @@ module ActiveRecord
def extract_limit(sql_type)
case sql_type
+ when /^enum\((.+)\)/i
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
when /blob|text/i
case sql_type
when /tiny/i
@@ -77,8 +100,6 @@ module ActiveRecord
when /^mediumint/i; 3
when /^smallint/i; 2
when /^tinyint/i; 1
- when /^enum\((.+)\)/i
- $1.split(',').map{|enum| enum.strip.length - 2}.max
else
super
end
@@ -130,6 +151,9 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ INDEX_TYPES = [:fulltext, :spatial]
+ INDEX_USINGS = [:btree, :hash]
+
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
include Arel::Visitors::BindVisitor
end
@@ -187,6 +211,10 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
+ def index_algorithms
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
+ end
+
# HELPER METHODS ===========================================
# The two drivers have slightly different ways of yielding hashes of results, so
@@ -196,8 +224,8 @@ module ActiveRecord
end
# Overridden by the adapters to instantiate their specific Column type.
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, extra)
end
# Must return the Mysql error number from the exception, if the exception has an
@@ -259,7 +287,7 @@ module ActiveRecord
end
rescue ActiveRecord::StatementInvalid => exception
if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ raise ActiveRecord::StatementInvalid.new("'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings.", exception.original_exception)
else
raise
end
@@ -337,6 +365,7 @@ module ActiveRecord
def recreate_database(name, options = {})
drop_database(name)
create_database(name, options)
+ reconnect!
end
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
@@ -410,7 +439,11 @@ module ActiveRecord
if current_index != row[:Key_name]
next if row[:Key_name] == 'PRIMARY' # skip the primary key
current_index = row[:Key_name]
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+
+ mysql_index_type = row[:Index_type].downcase.to_sym
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
end
indexes.last.columns << row[:Column_name]
@@ -426,7 +459,7 @@ module ActiveRecord
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
end
end
end
@@ -459,10 +492,6 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
def change_column_default(table_name, column_name, default)
column = column_for(table_name, column_name)
change_column table_name, column_name, column.sql_type, :default => default
@@ -487,6 +516,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
+ end
+
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
case type.to_s
@@ -572,6 +606,10 @@ module ActiveRecord
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# MySQL is too stupid to create a temporary table for use subquery, so we have
@@ -651,6 +689,7 @@ module ActiveRecord
if column = columns(table_name).find { |c| c.name == column_name.to_s }
options[:default] = column.default
options[:null] = column.null
+ options[:auto_increment] = (column.extra == "auto_increment")
else
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a4b3a0c584..609ccc2ed2 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -161,7 +161,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.blank?
+ return nil if value.empty?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -172,14 +172,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.blank?
+ return nil if string.empty?
dummy_time_string = "2000-01-01 #{string}"
@@ -192,7 +192,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value.is_a?(String) && value.blank?
+ if value.is_a?(String) && value.empty?
nil
else
TRUE_VALUES.include?(value)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 25b8aef617..530a27d099 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -4,7 +4,7 @@ gem 'mysql2', '~> 0.3.10'
require 'mysql2'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
def mysql2_connection(config)
config = config.symbolize_keys
@@ -63,8 +63,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7544c2a783..f23521430d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -16,9 +16,9 @@ class Mysql
end
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# Establishes a connection to the database that's used by all Active Record objects.
- def mysql_connection(config) # :nodoc:
+ def mysql_connection(config)
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -150,8 +150,8 @@ module ActiveRecord
end
end
- def new_column(field, default, type, null, collation) # :nodoc:
- Column.new(field, default, type, null, collation, strict_mode?)
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
end
def error_number(exception) # :nodoc:
@@ -383,7 +383,7 @@ module ActiveRecord
TYPES = {}
- # Register an MySQL +type_id+ with a typcasting object in
+ # Register an MySQL +type_id+ with a typecasting object in
# +type+.
def self.register_type(type_id, type)
TYPES[type_id] = type
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
index 3d8f0b575c..a9ef11aa83 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb
@@ -2,6 +2,17 @@ module ActiveRecord
module ConnectionAdapters
class PostgreSQLColumn < Column
module Cast
+ def point_to_string(point)
+ "(#{point[0]},#{point[1]})"
+ end
+
+ def string_to_point(string)
+ if string[0] == '(' && string[-1] == ')'
+ string = string[1...-1]
+ end
+ string.split(',').map{ |v| Float(v) }
+ end
+
def string_to_time(string)
return string unless String === string
@@ -15,6 +26,15 @@ module ActiveRecord
end
end
+ def string_to_bit(value)
+ case value
+ when /^0x/i
+ value[2..-1].hex.to_s(2) # Hexadecimal notation
+ else
+ value # Bit-string notation
+ end
+ end
+
def hstore_to_string(object)
if Hash === object
object.map { |k,v|
@@ -30,8 +50,8 @@ module ActiveRecord
nil
elsif String === string
Hash[string.scan(HstorePair).map { |k,v|
- v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
- k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
[k,v]
}]
else
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 68f2f2ca7b..1be116ce10 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -18,8 +18,19 @@ module ActiveRecord
end
end
+ class Bit < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_bit value
+ else
+ value
+ end
+ end
+ end
+
class Bytea < Type
def type_cast(value)
+ return if value.nil?
PGconn.unescape_bytea value
end
end
@@ -63,6 +74,16 @@ module ActiveRecord
end
end
+ class Point < Type
+ def type_cast(value)
+ if String === value
+ ConnectionAdapters::PostgreSQLColumn.string_to_point value
+ else
+ value
+ end
+ end
+ end
+
class Array < Type
attr_reader :subtype
def initialize(subtype)
@@ -312,14 +333,14 @@ module ActiveRecord
# FIXME: why are we keeping these types as strings?
alias_type 'tsvector', 'text'
alias_type 'interval', 'text'
- alias_type 'bit', 'text'
- alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
alias_type 'uuid', 'text'
register_type 'money', OID::Money.new
register_type 'bytea', OID::Bytea.new
register_type 'bool', OID::Boolean.new
+ register_type 'bit', OID::Bit.new
+ register_type 'varbit', OID::Bit.new
register_type 'float4', OID::Float.new
alias_type 'float8', 'float4'
@@ -330,6 +351,7 @@ module ActiveRecord
register_type 'time', OID::Time.new
register_type 'path', OID::Identity.new
+ register_type 'point', OID::Point.new
register_type 'polygon', OID::Identity.new
register_type 'circle', OID::Identity.new
register_type 'hstore', OID::Hstore.new
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 43f991b362..40a3b82839 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -18,27 +18,33 @@ module ActiveRecord
def quote(value, column = nil) #:nodoc:
return super unless column
+ sql_type = type_to_sql(column.type, column.limit, column.precision, column.scale)
+
case value
when Range
- if /range$/ =~ column.sql_type
- "'#{PostgreSQLColumn.range_to_string(value)}'::#{column.sql_type}"
+ if /range$/ =~ sql_type
+ "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}"
else
super
end
when Array
- if column.array
- "'#{PostgreSQLColumn.array_to_string(value, column, self)}'"
+ case sql_type
+ when 'point' then super(PostgreSQLColumn.point_to_string(value))
else
- super
+ if column.array
+ "'#{PostgreSQLColumn.array_to_string(value, column, self).gsub(/'/, "''")}'"
+ else
+ super
+ end
end
when Hash
- case column.sql_type
+ case sql_type
when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
when 'json' then super(PostgreSQLColumn.json_to_string(value), column)
else super
end
when IPAddr
- case column.sql_type
+ case sql_type
when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
else super
end
@@ -51,14 +57,14 @@ module ActiveRecord
super
end
when Numeric
- if column.sql_type == 'money' || [:string, :text].include?(column.type)
+ if sql_type == 'money' || [:string, :text].include?(column.type)
# Not truly string input, so doesn't require (or allow) escape string syntax.
"'#{value}'"
else
super
end
when String
- case column.sql_type
+ case sql_type
when 'bytea' then "'#{escape_bytea(value)}'"
when 'xml' then "xml '#{quote_string(value)}'"
when /^bit/
@@ -90,8 +96,12 @@ module ActiveRecord
super(value, column)
end
when Array
- return super(value, column) unless column.array
- PostgreSQLColumn.array_to_string(value, column, self)
+ case column.sql_type
+ when 'point' then PostgreSQLColumn.point_to_string(value)
+ else
+ return super(value, column) unless column.array
+ PostgreSQLColumn.array_to_string(value, column, self)
+ end
when String
return super(value, column) unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
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 3bc61c5e0c..d9b807bba4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,6 +1,42 @@
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class SchemaCreation < AbstractAdapter::SchemaCreation
+ private
+
+ def visit_AddColumn(o)
+ sql_type = type_to_sql(o.type.to_sym, o.limit, o.precision, o.scale)
+ sql = "ADD COLUMN #{quote_column_name(o.name)} #{sql_type}"
+ add_column_options!(sql, column_options(o))
+ end
+
+ def visit_ColumnDefinition(o)
+ sql = super
+ if o.primary_key? && o.type == :uuid
+ sql << " PRIMARY KEY "
+ add_column_options!(sql, column_options(o))
+ end
+ sql
+ end
+
+ def add_column_options!(sql, options)
+ if options[:array] || options[:column].try(:array)
+ sql << '[]'
+ end
+
+ column = options.fetch(:column) { return super }
+ if column.type == :uuid && options[:default] =~ /\(\)/
+ sql << " DEFAULT #{options[:default]}"
+ else
+ super
+ end
+ end
+ end
+
+ def schema_creation
+ SchemaCreation.new self
+ end
+
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -120,12 +156,15 @@ module ActiveRecord
column_names = columns.values_at(*indkey).compact
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ unless column_names.empty?
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ end
end.compact
end
@@ -337,10 +376,7 @@ module ActiveRecord
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
clear_cache!
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
-
- execute add_column_sql
+ super
end
# Changes the column of a table.
@@ -375,6 +411,11 @@ module ActiveRecord
rename_column_indexes(table_name, column_name, new_column_name)
end
+ def add_index(table_name, column_name, options = {}) #:nodoc:
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ end
+
def remove_index!(table_name, index_name) #:nodoc:
execute "DROP INDEX #{quote_table_name(index_name)}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 940de7e4f6..bf403c3ae0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -16,7 +16,7 @@ require 'pg'
require 'ipaddr'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
VALID_CONN_PARAMS = [:host, :hostaddr, :port, :dbname, :user, :password, :connect_timeout,
:client_encoding, :options, :application_name, :fallback_application_name,
:keepalives, :keepalives_idle, :keepalives_interval, :keepalives_count,
@@ -24,7 +24,7 @@ module ActiveRecord
:requirepeer, :krbsrvname, :gsslib, :service]
# Establishes a connection to the database that's used by all Active Record objects
- def postgresql_connection(config) # :nodoc:
+ def postgresql_connection(config)
conn_params = config.symbolize_keys
conn_params.delete_if { |_, v| v.nil? }
@@ -80,7 +80,7 @@ module ActiveRecord
when /\A'(.*)'::(num|date|tstz|ts|int4|int8)range\z/m
$1
# Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ when /\A\(?(-?\d+(\.\d*)?\)?(::bigint)?)\z/
$1
# Character types
when /\A\(?'(.*)'::.*\b(?:character varying|bpchar|text)\z/m
@@ -330,6 +330,13 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+ def primary_key(name, type = :primary_key, options = {})
+ return super unless type == :uuid
+ options[:default] ||= 'uuid_generate_v4()'
+ options[:primary_key] = true
+ column name, type, options
+ end
+
def column(name, type = nil, options = {})
super
column = self[name]
@@ -344,8 +351,8 @@ module ActiveRecord
private
- def create_column_definition(base, name, type)
- ColumnDefinition.new base, name, type
+ def create_column_definition(name, type)
+ ColumnDefinition.new name, type
end
end
@@ -426,6 +433,10 @@ module ActiveRecord
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -627,19 +638,6 @@ module ActiveRecord
@table_alias_length ||= query('SHOW max_identifier_length', 'SCHEMA')[0][0].to_i
end
- def add_column_options!(sql, options)
- if options[:array] || options[:column].try(:array)
- sql << '[]'
- end
-
- column = options.fetch(:column) { return super }
- if column.type == :uuid && options[:default] =~ /\(\)/
- sql << " DEFAULT #{options[:default]}"
- else
- super
- end
- end
-
# Set the authorized user for this session
def session_auth=(user)
clear_cache!
@@ -669,6 +667,10 @@ module ActiveRecord
@use_insert_returning
end
+ def valid_type?(type)
+ !native_database_types[type].nil?
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
@@ -713,7 +715,14 @@ module ActiveRecord
# populate composite types
nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
- vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ if OID.registered_type? row['typname']
+ # this composite type is explicitly registered
+ vector = OID::NAMES[row['typname']]
+ else
+ # use the default for composite types
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ end
+
OID::TYPE_MAP[row['oid'].to_i] = vector
end
@@ -900,8 +909,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition
- TableDefinition.new(self)
+ def create_table_definition(name, temporary, options)
+ TableDefinition.new native_database_types, name, temporary, options
end
def update_table_definition(table_name, base)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index d3ffee3a8b..7d940fe1c9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,9 +6,9 @@ gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
- module ConnectionHandling
+ module ConnectionHandling # :nodoc:
# sqlite3 adapter reuses sqlite_connection.
- def sqlite3_connection(config) # :nodoc:
+ def sqlite3_connection(config)
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
@@ -458,7 +458,7 @@ module ActiveRecord
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
+ definition.remove_column column_name
end
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index d6d998c7be..a1943dfcb0 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -15,15 +15,15 @@ module ActiveRecord
# Example for SQLite database:
#
# ActiveRecord::Base.establish_connection(
- # adapter: "sqlite",
- # database: "path/to/dbfile"
+ # adapter: "sqlite",
+ # database: "path/to/dbfile"
# )
#
# Also accepts keys as strings (for parsing from YAML for example):
#
# ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
- # "database" => "path/to/dbfile"
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
# )
#
# Or a URL:
@@ -54,11 +54,11 @@ module ActiveRecord
end
def connection_id
- Thread.current['ActiveRecord::Base.connection_id']
+ ActiveRecord::RuntimeRegistry.connection_id
end
def connection_id=(connection_id)
- Thread.current['ActiveRecord::Base.connection_id'] = connection_id
+ ActiveRecord::RuntimeRegistry.connection_id = connection_id
end
# Returns the configuration of the associated connection as a hash:
@@ -79,7 +79,7 @@ module ActiveRecord
connection_handler.retrieve_connection(self)
end
- # Returns true if Active Record is connected.
+ # Returns +true+ if Active Record is connected.
def connected?
connection_handler.connected?(self)
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 968dad5844..ba053700f2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -77,8 +77,17 @@ module ActiveRecord
mattr_accessor :disable_implicit_join_references, instance_writer: false
self.disable_implicit_join_references = false
- class_attribute :connection_handler, instance_writer: false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+ class_attribute :default_connection_handler, instance_writer: false
+
+ def self.connection_handler
+ ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
+ end
+
+ def self.connection_handler=(handler)
+ ActiveRecord::RuntimeRegistry.connection_handler = handler
+ end
+
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
end
module ClassMethods
@@ -176,6 +185,7 @@ module ActiveRecord
@columns_hash = self.class.column_types.dup
init_internals
+ init_changed_attributes
ensure_proper_type
populate_with_current_scope_attributes
@@ -246,9 +256,7 @@ module ActiveRecord
run_callbacks(:initialize) unless _initialize_callbacks.empty?
@changed_attributes = {}
- self.class.column_defaults.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
- end
+ init_changed_attributes
@aggregation_cache = {}
@association_cache = {}
@@ -299,9 +307,11 @@ module ActiveRecord
id.hash
end
- # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
+ # Clone and freeze the attributes hash such that associations are still
+ # accessible, even on destroyed records, but cloned models will not be
+ # frozen.
def freeze
- @attributes.freeze
+ @attributes = @attributes.clone.freeze
self
end
@@ -336,9 +346,15 @@ module ActiveRecord
self.class.connection
end
+ def connection_handler
+ self.class.connection_handler
+ end
+
# Returns the contents of the record as a nicely formatted string.
def inspect
- inspection = if @attributes
+ # We check defined?(@attributes) not to issue warnings if the object is
+ # allocated but not initialized.
+ inspection = if defined?(@attributes) && @attributes
self.class.column_names.collect { |name|
if has_attribute?(name)
"#{name}: #{attribute_for_inspect(name)}"
@@ -434,5 +450,14 @@ module ActiveRecord
@transaction_state = nil
@reflects_state = [false]
end
+
+ def init_changed_attributes
+ # Intentionally avoid using #column_defaults since overridden defaults (as is done in
+ # optimistic locking) won't get written unless they get marked as changed
+ self.class.columns.each do |c|
+ attr, orig_value = c.name, c.default
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index c615d59725..cd31147414 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -57,24 +57,25 @@ module ActiveRecord
class RecordNotDestroyed < ActiveRecordError
end
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
- # MySQL when Ruby driver used is too old).
+ # Superclass for all database execution errors.
+ #
+ # Wraps the underlying database error as +original_exception+.
class StatementInvalid < ActiveRecordError
+ attr_reader :original_exception
+
+ def initialize(message, original_exception = nil)
+ super(message)
+ @original_exception = original_exception
+ end
end
# Raised when SQL statement is invalid and the application gets a blank result.
class ThrowResult < ActiveRecordError
end
- # Parent class for all specific exceptions which wrap database driver exceptions
- # provides access to the original exception also.
+ # Defunct wrapper class kept for compatibility.
+ # +StatementInvalid+ wraps the original exception now.
class WrappedDatabaseException < StatementInvalid
- attr_reader :original_exception
-
- def initialize(message, original_exception)
- super(message)
- @original_exception = original_exception
- end
end
# Raised when a record cannot be inserted because it would violate a uniqueness constraint.
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 15736575a2..e65dab07ba 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,22 +1,22 @@
require 'active_support/lazy_load_hooks'
+require 'active_record/explain_registry'
module ActiveRecord
module Explain
- # Relation#explain needs to be able to collect the queries.
+ # Executes the block with the collect flag enabled. Queries are collected
+ # asynchronously by the subscriber and returned.
def collecting_queries_for_explain # :nodoc:
- current = Thread.current
- original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
+ ExplainRegistry.collect = true
yield
- return current[:available_queries_for_explain]
+ ExplainRegistry.queries
ensure
- # Note that the return value above does not depend on this assignment.
- current[:available_queries_for_explain] = original
+ ExplainRegistry.reset
end
# Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
# Returns a formatted string ready to be logged.
def exec_explain(queries) # :nodoc:
- str = queries && queries.map do |sql, bind|
+ str = queries.map do |sql, bind|
[].tap do |msg|
msg << "EXPLAIN for: #{sql}"
unless bind.empty?
@@ -31,6 +31,7 @@ module ActiveRecord
def str.inspect
self
end
+
str
end
end
diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb
new file mode 100644
index 0000000000..f5cd57e075
--- /dev/null
+++ b/activerecord/lib/active_record/explain_registry.rb
@@ -0,0 +1,30 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for EXPLAIN. For example
+ #
+ # ActiveRecord::ExplainRegistry.queries
+ #
+ # returns the collected queries local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class ExplainRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :queries, :collect
+
+ def initialize
+ reset
+ end
+
+ def collect?
+ @collect
+ end
+
+ def reset
+ @collect = false
+ @queries = []
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 0f927496fb..a3bc56d600 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,4 +1,5 @@
require 'active_support/notifications'
+require 'active_record/explain_registry'
module ActiveRecord
class ExplainSubscriber # :nodoc:
@@ -7,8 +8,8 @@ module ActiveRecord
end
def finish(name, id, payload)
- if queries = Thread.current[:available_queries_for_explain]
- queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
end
end
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index 11b53275e1..fbd7a4d891 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -24,7 +24,6 @@ module ActiveRecord
rows.each(&block)
end
- RESCUE_ERRORS = [ ArgumentError, Psych::SyntaxError ] # :nodoc:
private
def rows
@@ -32,7 +31,7 @@ module ActiveRecord
begin
data = YAML.load(render(IO.read(@file)))
- rescue *RESCUE_ERRORS => error
+ rescue ArgumentError, Psych::SyntaxError => error
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
end
@rows = data ? validate(data).to_a : []
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 2958d08210..45dc26f0ed 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -708,11 +708,18 @@ module ActiveRecord
module TestFixtures
extend ActiveSupport::Concern
- included do
- setup :setup_fixtures
- teardown :teardown_fixtures
+ def before_setup
+ setup_fixtures
+ super
+ end
+
+ def after_teardown
+ super
+ teardown_fixtures
+ end
- class_attribute :fixture_path
+ included do
+ class_attribute :fixture_path, :instance_writer => false
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
@@ -752,7 +759,7 @@ module ActiveRecord
def fixtures(*fixture_set_names)
if fixture_set_names.first == :all
fixture_set_names = Dir["#{fixture_path}/**/*.{yml}"]
- fixture_set_names.map! { |f| f[(fixture_path.size + 1)..-5] }
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
else
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
@@ -765,8 +772,7 @@ module ActiveRecord
def try_to_load_dependency(file_name)
require_dependency file_name
rescue LoadError => e
- # Let's hope the developer has included it himself
-
+ # Let's hope the developer has included it
# Let's warn in case this is a subdependency, otherwise
# subdependency error messages are totally cryptic
if ActiveRecord::Base.logger
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index f54865c86e..8df76c7f5f 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -174,7 +174,7 @@ module ActiveRecord
if subclass_name.present? && subclass_name != self.name
subclass = subclass_name.safe_constantize
- unless subclasses.include?(subclass)
+ unless descendants.include?(subclass)
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 48c73d7781..2589b2f3da 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -21,7 +21,7 @@ module ActiveRecord
# <tt>resources :users</tt> route. Normally, +user_path+ will
# construct a path with the user object's 'id' in it:
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/1"
#
# You can override +to_param+ in your model to make +user_path+ construct
@@ -33,7 +33,7 @@ module ActiveRecord
# end
# end
#
- # user = User.find_by_name('Phusion')
+ # user = User.find_by(name: 'Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index c1ba524c84..61e5c120df 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -3,11 +3,11 @@ module ActiveRecord
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
def self.runtime=(value)
- Thread.current[:active_record_sql_runtime] = value
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
end
def self.runtime
- Thread.current[:active_record_sql_runtime] ||= 0
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
end
def self.reset_runtime
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 27d37766b7..6c020e1d57 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -102,7 +102,7 @@ module ActiveRecord
# table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
# * <tt>change_table(name, options)</tt>: Allows to make column alterations to
- # the table called +name+. It makes the table object availabe to a block that
+ # the table called +name+. It makes the table object available to a block that
# can then add/remove columns, indexes or foreign keys to it.
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
# to +new_name+.
@@ -466,7 +466,7 @@ module ActiveRecord
@connection.respond_to?(:reverting) && @connection.reverting
end
- class ReversibleBlockHelper < Struct.new(:reverting)
+ class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
def up
yield unless reverting
end
@@ -867,11 +867,15 @@ module ActiveRecord
alias :current :current_migration
def run
- target = migrations.detect { |m| m.version == @target_version }
- raise UnknownMigrationVersionError.new(@target_version) if target.nil?
- unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
- target.migrate(@direction)
- record_version_state_after_migrating(target.version)
+ migration = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
+ unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
+ begin
+ execute_migration_in_transaction(migration, @direction)
+ rescue => e
+ canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
+ raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
+ end
end
end
@@ -892,10 +896,7 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
begin
- ddl_transaction(migration) do
- migration.migrate(@direction)
- record_version_state_after_migrating(migration.version)
- end
+ execute_migration_in_transaction(migration, @direction)
rescue => e
canceled_msg = use_transaction?(migration) ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
@@ -932,6 +933,13 @@ module ActiveRecord
migrated.include?(migration.version.to_i)
end
+ def execute_migration_in_transaction(migration, direction)
+ ddl_transaction(migration) do
+ migration.migrate(direction)
+ record_version_state_after_migrating(migration.version)
+ end
+ end
+
def target
migrations.detect { |m| m.version == @target_version }
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 602ab9e2f4..d607f49e2b 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -90,8 +90,9 @@ module ActiveRecord
# accepts_nested_attributes_for :posts
# end
#
- # You can now set or update attributes on an associated post model through
- # the attribute hash.
+ # You can now set or update attributes on the associated posts through
+ # an attribute hash for a member: include the key +:posts_attributes+
+ # with an array of hashes of post attributes as a value.
#
# For each hash that does _not_ have an <tt>id</tt> key a new record will
# be instantiated, unless the hash also contains a <tt>_destroy</tt> key
@@ -114,10 +115,10 @@ module ActiveRecord
# hashes if they fail to pass your criteria. For example, the previous
# example could be rewritten as:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
+ # end
#
# params = { member: {
# name: 'joe', posts_attributes: [
@@ -134,19 +135,19 @@ module ActiveRecord
#
# Alternatively, :reject_if also accepts a symbol for using methods:
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :new_record?
- # end
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :new_record?
+ # end
#
- # class Member < ActiveRecord::Base
- # has_many :posts
- # accepts_nested_attributes_for :posts, reject_if: :reject_posts
+ # class Member < ActiveRecord::Base
+ # has_many :posts
+ # accepts_nested_attributes_for :posts, reject_if: :reject_posts
#
- # def reject_posts(attributed)
- # attributed['title'].blank?
- # end
- # end
+ # def reject_posts(attributed)
+ # attributed['title'].blank?
+ # end
+ # end
#
# If the hash contains an <tt>id</tt> key that matches an already
# associated record, the matching record will be modified:
@@ -183,6 +184,29 @@ module ActiveRecord
# member.save
# member.reload.posts.length # => 1
#
+ # Nested attributes for an associated collection can also be passed in
+ # the form of a hash of hashes instead of an array of hashes:
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: { first: { title: 'Foo' },
+ # second: { title: 'Bar' } })
+ #
+ # has the same effect as
+ #
+ # Member.create(name: 'joe',
+ # posts_attributes: [ { title: 'Foo' },
+ # { title: 'Bar' } ])
+ #
+ # The keys of the hash which is the value for +:posts_attributes+ are
+ # ignored in this case.
+ # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # such keys, otherwise the hash will be wrapped in an array and
+ # interpreted as an attribute hash for a single post.
+ #
+ # Passing attributes for an associated collection in the form of a hash
+ # of hashes can be used with hashes generated from HTTP/HTML parameters,
+ # where there maybe no natural way to submit an array of hashes.
+ #
# === Saving
#
# All changes to models, including the destruction of those marked for
@@ -338,7 +362,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present?
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -428,7 +452,7 @@ module ActiveRecord
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
end
else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ raise_nested_attributes_record_not_found!(association_name, attributes['id'])
end
end
end
@@ -490,7 +514,7 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
+ def raise_nested_attributes_record_not_found!(association_name, record_id)
raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index b25d0601cb..178db07ca1 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -51,7 +51,7 @@ module ActiveRecord
# how this "single-table" inheritance mapping is implemented.
def instantiate(record, column_types = {})
klass = discriminate_class_for_record(record)
- column_types = klass.decorate_columns(column_types)
+ column_types = klass.decorate_columns(column_types.dup)
klass.allocate.init_with('attributes' => record, 'column_types' => column_types)
end
@@ -428,23 +428,11 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update_record(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
- if attributes_with_values.empty?
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
+ if attributes_values.empty?
0
else
- klass = self.class
- column_hash = klass.connection.schema_cache.columns_hash klass.table_name
- db_columns_with_values = attributes_with_values.map { |attr,value|
- real_column = column_hash[attr.name]
- [real_column, value]
- }
- bind_attrs = attributes_with_values.dup
- bind_attrs.keys.each_with_index do |column, i|
- real_column = db_columns_with_values[i].first
- bind_attrs[column] = klass.connection.substitute_at(real_column, i)
- end
- stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(bind_attrs)
- klass.connection.update stmt, 'SQL', db_columns_with_values
+ self.class.unscoped.update_record attributes_values, id, id_was
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 902fd90c54..f78ccb01aa 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module ActiveRecord
delegate :find_each, :find_in_batches, :to => :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
- :having, :create_with, :uniq, :distinct, :references, :none, :to => :all
+ :having, :create_with, :uniq, :distinct, :references, :none, :unscope, :to => :all
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 99117b74c5..e36888d4a8 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -36,6 +36,20 @@ module ActiveRecord
rake_tasks do
require "active_record/base"
+
+ ActiveRecord::Tasks::DatabaseTasks.env = Rails.env
+ ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first
+ ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application
+ ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
+
+ if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
+ if engine.paths['db/migrate'].existent
+ ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a
+ end
+ end
+
load "active_record/railties/databases.rake"
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d92e268109..92bef09ff5 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,14 +2,8 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {}
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
-
- if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
- if engine.paths['db/migrate'].existent
- ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a
- end
- end
+ ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
+ ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
namespace :create do
@@ -156,7 +150,7 @@ db_namespace = namespace :db do
begin
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
rescue NoMethodError
- $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch'
+ $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.'
end
end
@@ -166,11 +160,11 @@ db_namespace = namespace :db do
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => [:environment, :load_config] do
+ task :abort_if_pending_migrations => :environment do
pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
- puts "You have #{pending_migrations.size} pending migrations:"
+ puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
pending_migrations.each do |pending_migration|
puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
@@ -184,7 +178,7 @@ db_namespace = namespace :db do
desc 'Load the seed data from db/seeds.rb'
task :seed do
db_namespace['abort_if_pending_migrations'].invoke
- Rails.application.load_seed
+ ActiveRecord::Tasks::DatabaseTasks.load_seed
end
namespace :fixtures do
@@ -192,7 +186,15 @@ db_namespace = namespace :db do
task :load => [:environment, :load_config] do
require 'active_record/fixtures'
- base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
@@ -209,7 +211,16 @@ db_namespace = namespace :db do
puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label
- base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
+ base_dir = if ENV['FIXTURES_PATH']
+ STDERR.puts "Using FIXTURES_PATH env variable is deprecated, please use " +
+ "ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' " +
+ "instead."
+ File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ else
+ ActiveRecord::Tasks::DatabaseTasks.fixtures_path
+ end
+
+
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
@@ -228,7 +239,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => [:environment, :load_config] do
require 'active_record/schema_dumper'
- filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
+ filename = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
@@ -237,12 +248,9 @@ db_namespace = namespace :db do
desc 'Load a schema.rb file into the database'
task :load => [:environment, :load_config] do
- file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
- if File.exists?(file)
- load(file)
- else
- abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
- end
+ file = ENV['SCHEMA'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'schema.rb')
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(file)
+ load(file)
end
task :load_if_ruby => ['db:create', :environment] do
@@ -253,7 +261,7 @@ db_namespace = namespace :db do
desc 'Create a db/schema_cache.dump file.'
task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
con.schema_cache.clear!
con.tables.each { |table| con.schema_cache.add(table) }
@@ -262,7 +270,7 @@ db_namespace = namespace :db do
desc 'Clear a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
- filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
end
@@ -270,32 +278,11 @@ db_namespace = namespace :db do
end
namespace :structure do
- def set_firebird_env(config)
- ENV['ISC_USER'] = config['username'].to_s if config['username']
- ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
- end
-
- def firebird_db_string(config)
- FireRuby::Database.db_string_for(config.symbolize_keys)
- end
-
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- case current_config['adapter']
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when 'sqlserver'
- `smoscript -s #{current_config['host']} -d #{current_config['database']} -u #{current_config['username']} -p #{current_config['password']} -f #{filename} -A -U`
- when "firebird"
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -a #{db_string} > #{filename}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
if ActiveRecord::Base.connection.supports_migrations?
File.open(filename, "a") do |f|
@@ -307,23 +294,10 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
+ filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file(filename)
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
- filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case current_config['adapter']
- when 'sqlserver'
- `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}`
- when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(current_config)
- IO.read(filename).split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- set_firebird_env(current_config)
- db_string = firebird_db_string(current_config)
- sh "isql -i #{filename} #{db_string}"
- else
- ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
- end
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
end
task :load_if_sql => ['db:create', :environment] do
@@ -378,29 +352,11 @@ db_namespace = namespace :db do
# desc "Empty the test database"
task :purge => [:environment, :load_config] do
- abcs = ActiveRecord::Base.configurations
- case abcs['test']['adapter']
- when 'sqlserver'
- test = abcs.deep_dup['test']
- test_database = test['database']
- test['database'] = 'master'
- ActiveRecord::Base.establish_connection(test)
- ActiveRecord::Base.connection.recreate_database!(test_database)
- when "oci", "oracle"
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
- ActiveRecord::Base.connection.execute(ddl)
- end
- when 'firebird'
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database!
- else
- ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
- end
+ ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test']
end
# desc 'Check for pending migrations and load the test schema'
- task :prepare => 'db:abort_if_pending_migrations' do
+ task :prepare do
unless ActiveRecord::Base.configurations.blank?
db_namespace['test:load'].invoke
end
@@ -426,7 +382,7 @@ namespace :railties do
puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists."
end
- on_copy = Proc.new do |name, migration, old_path|
+ on_copy = Proc.new do |name, migration|
puts "Copied migration #{migration.basename} from #{name}"
end
@@ -436,5 +392,5 @@ namespace :railties do
end
end
-task 'test:prepare' => 'db:test:prepare'
+task 'test:prepare' => ['db:test:prepare', 'db:test:load', 'db:abort_if_pending_migrations']
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0995750ecd..60eda96f08 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -75,8 +75,13 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
+ # Base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ #
+ # MacroReflection
+ # AggregateReflection
+ # AssociationReflection
+ # ThroughReflection
class MacroReflection
# Returns the name of the macro.
#
@@ -401,6 +406,16 @@ module ActiveRecord
# has_many :tags, through: :taggings
# end
#
+ # class Tagging < ActiveRecord::Base
+ # belongs_to :post
+ # belongs_to :tag
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ #
+ # taggings_reflection = tags_reflection.source_reflection
+ # # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
+ #
def source_reflection
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
end
@@ -426,6 +441,17 @@ module ActiveRecord
# The chain is built by recursively calling #chain on the source reflection and the through
# reflection. The base case for the recursion is a normal association, which just returns
# [self] as its #chain.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.chain
+ # # => [<ActiveRecord::Reflection::ThroughReflection: @macro=:has_many, @name=:tags, @options={:through=>:taggings}, @active_record=Post>,
+ # <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @options={}, @active_record=Post>]
+ #
def chain
@chain ||= begin
chain = source_reflection.chain + through_reflection.chain
@@ -496,9 +522,16 @@ module ActiveRecord
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- # Gets an array of possible <tt>:through</tt> source reflection names:
+ # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, through: :taggings
+ # end
#
- # [:singularized, :pluralized]
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # tags_reflection.source_reflection_names
+ # # => [:tag, :tags]
#
def source_reflection_names
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 037097d2dd..913f6f88f2 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,14 +10,14 @@ module ActiveRecord
:extending]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :distinct, :create_with]
+ :reverse_order, :distinct, :create_with, :uniq]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :default_scoped
+ attr_accessor :default_scoped, :proxy_association
alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
@@ -39,7 +39,7 @@ module ActiveRecord
reset
end
- def insert(values)
+ def insert(values) # :nodoc:
primary_key_value = nil
if primary_key && Hash === values
@@ -56,16 +56,7 @@ module ActiveRecord
im = arel.create_insert
im.into @table
- conn = @klass.connection
-
- substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
- binds = substitutes.map do |arel_attr, value|
- [@klass.columns_hash[arel_attr.name], value]
- end
-
- substitutes.each_with_index do |tuple, i|
- tuple[1] = conn.substitute_at(binds[i][0], i)
- end
+ substitutes, binds = substitute_values values
if values.empty? # empty insert
im.values = Arel.sql(connection.empty_insert_statement_value)
@@ -73,7 +64,7 @@ module ActiveRecord
im.insert substitutes
end
- conn.insert(
+ @klass.connection.insert(
im,
'SQL',
primary_key,
@@ -82,6 +73,29 @@ module ActiveRecord
binds)
end
+ def update_record(values, id, id_was) # :nodoc:
+ substitutes, binds = substitute_values values
+ um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes)
+
+ @klass.connection.update(
+ um,
+ 'SQL',
+ binds)
+ end
+
+ def substitute_values(values) # :nodoc:
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = @klass.connection.substitute_at(binds[i][0], i)
+ end
+
+ [substitutes, binds]
+ end
+
# Initializes new record from relation while maintaining the current
# scope.
#
@@ -603,7 +617,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
"\n" \
- "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "Currently, Active Record recognizes the table in the string, and knows to JOIN the " \
"comments table to the query, rather than loading comments in a separate query. " \
"However, doing this without writing a full-blown SQL parser is inherently flawed. " \
"Since we don't want to write an SQL parser, we are removing this functionality. " \
@@ -612,7 +626,7 @@ module ActiveRecord
"\n" \
" Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n" \
"\n" \
- "If you don't rely on implicit join references you can disable the feature entirely" \
+ "If you don't rely on implicit join references you can disable the feature entirely " \
"by setting `config.active_record.disable_implicit_join_references = true`."
)
true
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index be011b22af..64e1ff9a6a 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# puts values["Drake"]
# # => 43
#
- # drake = Family.find_by_last_name('Drake')
+ # drake = Family.find_by(last_name: 'Drake')
# values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# # => 43
@@ -135,7 +135,7 @@ module ActiveRecord
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
- # Person.uniq.pluck(:role)
+ # Person.pluck('DISTINCT role')
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
#
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 00a506c3a7..8d6740246c 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -14,14 +14,14 @@ module ActiveRecord
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
- module ClassSpecificRelation
+ module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
included do
@delegation_mutex = Mutex.new
end
- module ClassMethods
+ module ClassMethods # :nodoc:
def name
superclass.name
end
@@ -37,11 +37,9 @@ module ActiveRecord
end
RUBY
else
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method}(*args, &block)
- scoping { @klass.send(#{method.inspect}, *args, &block) }
- end
- RUBY
+ define_method method do |*args, &block|
+ scoping { @klass.send(method, *args, &block) }
+ end
end
end
end
@@ -72,7 +70,7 @@ module ActiveRecord
end
end
- module ClassMethods
+ module ClassMethods # :nodoc:
@@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2)
def new(klass, *args)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 14520381c9..72e9272cd7 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -37,7 +37,7 @@ module ActiveRecord
end
# Finds the first record matching the specified conditions. There
- # is no implied ording so if order matters, you should specify it
+ # is no implied ordering so if order matters, you should specify it
# yourself.
#
# If no record is found, returns <tt>nil</tt>.
@@ -130,8 +130,8 @@ module ActiveRecord
last or raise RecordNotFound
end
- # Returns +true+ if a record exists in the table that matches the +id+ or
- # conditions given, or +false+ otherwise. The argument can take six forms:
+ # Returns truthy if a record exists in the table that matches the +id+ or
+ # conditions given, or falsy otherwise. The argument can take six forms:
#
# * Integer - Finds the record with this primary key.
# * String - Finds the record with a primary key corresponding to this
@@ -176,6 +176,28 @@ module ActiveRecord
false
end
+ # This method is called whenever no records are found with either a single
+ # id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
+ #
+ # The error message is different depending on whether a single id or
+ # multiple ids are provided. If multiple ids are provided, then the number
+ # of results obtained should be provided in the +result_size+ argument and
+ # the expected number of results should be provided in the +expected_size+
+ # argument.
+ def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ conditions = arel.where_sql
+ conditions = " [#{conditions}]" if conditions
+
+ if Array(ids).size == 1
+ error = "Couldn't find #{@klass.name} with #{primary_key}=#{ids}#{conditions}"
+ else
+ error = "Couldn't find all #{@klass.name.pluralize} with IDs "
+ error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
+ end
+
+ raise RecordNotFound, error
+ end
+
protected
def find_with_associations
@@ -259,11 +281,7 @@ module ActiveRecord
relation.bind_values += [[column, id]]
record = relation.take
- unless record
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
- end
+ raise_record_not_found_exception!(id, 0, 1) unless record
record
end
@@ -286,12 +304,7 @@ module ActiveRecord
if result.size == expected_size
result
else
- conditions = arel.where_sql
- conditions = " [#{conditions}]" if conditions
-
- error = "Couldn't find all #{@klass.name.pluralize} with IDs "
- error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
- raise RecordNotFound, error
+ raise_record_not_found_exception!(ids, result.size, expected_size)
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index eb23e92fb8..936b83261e 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
class Merger # :nodoc:
- attr_reader :relation, :values
+ attr_reader :relation, :values, :other
def initialize(relation, other)
if other.default_scoped? && other.klass != relation.klass
@@ -48,11 +48,12 @@ module ActiveRecord
@relation = relation
@values = other.values
+ @other = other
end
NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS +
Relation::MULTI_VALUE_METHODS -
- [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
+ [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -66,12 +67,39 @@ module ActiveRecord
merge_multi_values
merge_single_values
+ merge_joins
relation
end
private
+ def merge_joins
+ return if values[:joins].blank?
+
+ if other.klass == relation.klass
+ relation.joins!(*values[:joins])
+ else
+ joins_dependency, rest = values[:joins].partition do |join|
+ case join
+ when Hash, Symbol, Array
+ true
+ else
+ false
+ end
+ end
+
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass,
+ joins_dependency,
+ [])
+ relation.joins! rest
+
+ join_dependency.join_associations.each do |association|
+ @relation = association.join_relation(relation)
+ end
+ end
+ end
+
def merge_multi_values
relation.where_values = merged_wheres
relation.bind_values = merged_binds
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 257221174b..9fcd2d06c5 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -34,7 +34,6 @@ module ActiveRecord
#
# User.where.not(name: "Jon", role: "admin")
# # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
- #
def not(opts, *rest)
where_value = @scope.send(:build_where, opts, rest).map do |rel|
case rel
@@ -353,7 +352,7 @@ module ActiveRecord
spawn.unscope!(*args)
end
- def unscope!(*args)
+ def unscope!(*args) # :nodoc:
args.flatten!
args.each do |scope|
@@ -552,7 +551,6 @@ module ActiveRecord
# Order.having('SUM(price) > 30').group('user_id')
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
- spawn.having!(opts, *rest)
end
def having!(opts, *rest) # :nodoc:
@@ -934,9 +932,7 @@ module ActiveRecord
association_joins = buckets[:association_join] || []
stashed_association_joins = buckets[:stashed_join] || []
join_nodes = (buckets[:join_node] || []).uniq
- string_joins = (buckets[:string_join] || []).map { |x|
- x.strip
- }.uniq
+ string_joins = (buckets[:string_join] || []).map { |x| x.strip }.uniq
join_list = join_nodes + custom_join_ast(manager, string_joins)
diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb
new file mode 100644
index 0000000000..63e6738622
--- /dev/null
+++ b/activerecord/lib/active_record/runtime_registry.rb
@@ -0,0 +1,17 @@
+require 'active_support/per_thread_registry'
+
+module ActiveRecord
+ # This is a thread locals registry for Active Record. For example:
+ #
+ # ActiveRecord::RuntimeRegistry.connection_handler
+ #
+ # returns the connection handler local to the current thread.
+ #
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
+ # for further details.
+ class RuntimeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ attr_accessor :connection_handler, :sql_runtime, :connection_id
+ end
+end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index df090b972d..10c6d272cd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -118,7 +118,7 @@ HEADER
# then dump all non-primary key columns
column_specs = columns.map do |column|
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
next if column.name == pk
@connection.column_spec(column, @types)
end.compact
@@ -185,6 +185,10 @@ HEADER
statement_parts << ('where: ' + index.where.inspect) if index.where
+ statement_parts << ('using: ' + index.using.inspect) if index.using
+
+ statement_parts << ('type: ' + index.type.inspect) if index.type
+
' ' + statement_parts.join(', ')
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 9746b1c3c2..0cf3d59985 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -1,3 +1,5 @@
+require 'active_support/per_thread_registry'
+
module ActiveRecord
module Scoping
extend ActiveSupport::Concern
@@ -9,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- Thread.current["#{self}_current_scope"]
+ ScopeRegistry.value_for(:current_scope, base_class.to_s)
end
def current_scope=(scope) #:nodoc:
- Thread.current["#{self}_current_scope"] = scope
+ ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
end
end
@@ -24,5 +26,57 @@ module ActiveRecord
send("#{att}=", value) if respond_to?("#{att}=")
end
end
+
+ # This class stores the +:current_scope+ and +:ignore_default_scope+ values
+ # for different classes. The registry is stored as a thread local, which is
+ # accessed through +ScopeRegistry.current+.
+ #
+ # This class allows you to store and get the scope values on different
+ # classes and different types of scopes. For example, if you are attempting
+ # to get the current_scope for the +Board+ model, then you would use the
+ # following code:
+ #
+ # registry = ActiveRecord::Scoping::ScopeRegistry
+ # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ #
+ # Now when you run:
+ #
+ # registry.value_for(:current_scope, "Board")
+ #
+ # You will obtain whatever was defined in +some_new_scope+. The +value_for+
+ # and +set_value_for+ methods are delegated to the current +ScopeRegistry+
+ # object, so the above example code can also be called as:
+ #
+ # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
+ # "Board", some_new_scope)
+ class ScopeRegistry # :nodoc:
+ extend ActiveSupport::PerThreadRegistry
+
+ VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope]
+
+ def initialize
+ @registry = Hash.new { |hash, key| hash[key] = {} }
+ end
+
+ # Obtains the value for a given +scope_name+ and +variable_name+.
+ def value_for(scope_type, variable_name)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name]
+ end
+
+ # Sets the +value+ for a given +scope_type+ and +variable_name+.
+ def set_value_for(scope_type, variable_name, value)
+ raise_invalid_scope_type!(scope_type)
+ @registry[scope_type][variable_name] = value
+ end
+
+ private
+
+ def raise_invalid_scope_type!(scope_type)
+ if !VALID_SCOPE_TYPES.include?(scope_type)
+ raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5bd481082e..d37d33d552 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -5,8 +5,17 @@ module ActiveRecord
included do
# Stores the default scope for the class.
- class_attribute :default_scopes, instance_writer: false
+ class_attribute :default_scopes, instance_writer: false, instance_predicate: false
+
self.default_scopes = []
+
+ def self.default_scopes?
+ ActiveSupport::Deprecation.warn(
+ "#default_scopes? is deprecated. Do something like #default_scopes.empty? instead."
+ )
+
+ !!self.default_scopes
+ end
end
module ClassMethods
@@ -27,14 +36,6 @@ module ActiveRecord
# Post.unscoped {
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
- #
- # It is recommended that you use the block form of unscoped because
- # chaining unscoped with +scope+ does not work. Assuming that
- # +published+ is a +scope+, the following two statements
- # are equal: the +default_scope+ is applied on both.
- #
- # Post.unscoped.published
- # Post.published
def unscoped
block_given? ? relation.scoping { yield } : relation
end
@@ -119,11 +120,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- Thread.current["#{self}_ignore_default_scope"]
+ ScopeRegistry.value_for(:ignore_default_scope, self)
end
def ignore_default_scope=(ignore) # :nodoc:
- Thread.current["#{self}_ignore_default_scope"] = ignore
+ ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 12317601b6..da73bead32 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -160,13 +160,8 @@ module ActiveRecord
singleton_class.send(:define_method, name) do |*args|
if body.respond_to?(:call)
- scope = extension ? body.call(*args).extending(extension) : body.call(*args)
-
- if scope
- default_scoped = scope.default_scoped
- scope = relation.merge(scope)
- scope.default_scoped = default_scoped
- end
+ scope = all.scoping { body.call(*args) }
+ scope = scope.extending(extension) if extension
else
scope = body
end
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
new file mode 100644
index 0000000000..dd4ee0c4a0
--- /dev/null
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -0,0 +1,26 @@
+module ActiveRecord
+
+ # Statement cache is used to cache a single statement in order to avoid creating the AST again.
+ # Initializing the cache is done by passing the statement in the initialization block:
+ #
+ # cache = ActiveRecord::StatementCache.new do
+ # Book.where(name: "my book").limit(100)
+ # end
+ #
+ # The cached statement is executed by using the +execute+ method:
+ #
+ # cache.execute
+ #
+ # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
+ # Database is queried when +to_a+ is called on the relation.
+ class StatementCache
+ def initialize
+ @relation = yield
+ raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
+ end
+
+ def execute
+ @relation.dup.to_a
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4fa7cf8a7d..3e8b79c7a0 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -3,10 +3,41 @@ module ActiveRecord
class DatabaseAlreadyExists < StandardError; end # :nodoc:
class DatabaseNotSupported < StandardError; end # :nodoc:
- module DatabaseTasks # :nodoc:
+ # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
+ # logic behind common tasks used to manage database and migrations.
+ #
+ # The tasks defined here are used in rake tasks provided by Active Record.
+ #
+ # In order to use DatabaseTasks, a few config values need to be set. All the needed
+ # config values are set by Rails already, so it's necessary to do it only if you
+ # want to change the defaults or when you want to use Active Record outside of Rails
+ # (in such case after configuring the database tasks, you can also use the rake tasks
+ # defined in Active Record).
+ #
+ #
+ # The possible config values are:
+ #
+ # * +env+: current environment (like Rails.env).
+ # * +database_configuration+: configuration of your databases (as in +config/database.yml+).
+ # * +db_dir+: your +db+ directory.
+ # * +fixtures_path+: a path to fixtures directory.
+ # * +migrations_paths+: a list of paths to directories with migrations.
+ # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
+ #
+ # Example usage of +DatabaseTasks+ outside Rails could look as such:
+ #
+ # include ActiveRecord::Tasks
+ # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.db_dir = 'db'
+ # # other settings...
+ #
+ # DatabaseTasks.create_current('production')
+ module DatabaseTasks
extend self
attr_writer :current_config
+ attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
+ :fixtures_path, :env
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
@@ -15,12 +46,16 @@ module ActiveRecord
@tasks[pattern] = task
end
- register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
- register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
- register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks)
+ register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
+ register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+
+ register_task(/firebird/, ActiveRecord::Tasks::FirebirdDatabaseTasks)
+ register_task(/sqlserver/, ActiveRecord::Tasks::SqlserverDatabaseTasks)
+ register_task(/(oci|oracle)/, ActiveRecord::Tasks::OracleDatabaseTasks)
def current_config(options = {})
- options.reverse_merge! :env => Rails.env
+ options.reverse_merge! :env => env
if options.has_key?(:config)
@current_config = options[:config]
else
@@ -46,7 +81,7 @@ module ActiveRecord
each_local_configuration { |configuration| create configuration }
end
- def create_current(environment = Rails.env)
+ def create_current(environment = env)
each_current_configuration(environment) { |configuration|
create configuration
}
@@ -69,7 +104,7 @@ module ActiveRecord
each_local_configuration { |configuration| drop configuration }
end
- def drop_current(environment = Rails.env)
+ def drop_current(environment = env)
each_current_configuration(environment) { |configuration|
drop configuration
}
@@ -79,7 +114,7 @@ module ActiveRecord
drop database_url_config
end
- def charset_current(environment = Rails.env)
+ def charset_current(environment = env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -88,7 +123,7 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).charset
end
- def collation_current(environment = Rails.env)
+ def collation_current(environment = env)
collation ActiveRecord::Base.configurations[environment]
end
@@ -113,6 +148,24 @@ module ActiveRecord
class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
end
+ def check_schema_file(filename)
+ unless File.exists?(filename)
+ message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}
+ message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails)
+ Kernel.abort message
+ end
+ end
+
+ def load_seed
+ if seed_loader
+ seed_loader.load_seed
+ else
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
+ "Seed loader should respond to load_seed method"
+ end
+ end
+
private
def database_url_config
@@ -130,7 +183,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- environments << 'test' if environment.development?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/firebird_database_tasks.rb b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
new file mode 100644
index 0000000000..98014a38ea
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/firebird_database_tasks.rb
@@ -0,0 +1,56 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class FirebirdDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.recreate_database!
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -a #{db_string} > #{filename}"
+ end
+
+ def structure_load(filename)
+ set_firebird_env(configuration)
+ db_string = firebird_db_string(configuration)
+ Kernel.system "isql -i #{filename} #{db_string}"
+ end
+
+ private
+
+ def set_firebird_env(config)
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
+ end
+
+ def firebird_db_string(config)
+ FireRuby::Database.db_string_for(config.symbolize_keys)
+ end
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/oracle_database_tasks.rb b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
new file mode 100644
index 0000000000..de3aa50e5e
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/oracle_database_tasks.rb
@@ -0,0 +1,45 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class OracleDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ establish_connection(:test)
+ connection.structure_drop.split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ establish_connection(configuration)
+ File.open(filename, "w:utf-8") { |f| f << connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ IO.read(filename).split(";\n\n").each { |ddl| connection.execute(ddl) }
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
index 0b1b030516..4413330fab 100644
--- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -59,7 +59,7 @@ module ActiveRecord
def structure_load(filename)
set_psql_env
- Kernel.system("psql -f #{filename} #{configuration['database']}")
+ Kernel.system("psql -q -f #{filename} #{configuration['database']}")
end
private
diff --git a/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
new file mode 100644
index 0000000000..c718ee03a8
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlserver_database_tasks.rb
@@ -0,0 +1,48 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SqlserverDatabaseTasks # :nodoc:
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ ActiveSupport::Deprecation.warn "This database tasks were deprecated, because this tasks should be served by the 3rd party adapter."
+ @configuration = configuration
+ end
+
+ def create
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def drop
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def purge
+ test = configuration.deep_dup
+ test_database = test['database']
+ test['database'] = 'master'
+ establish_connection(test)
+ connection.recreate_database!(test_database)
+ end
+
+ def charset
+ $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
+ end
+
+ def structure_dump(filename)
+ Kernel.system("smoscript -s #{configuration['host']} -d #{configuration['database']} -u #{configuration['username']} -p #{configuration['password']} -f #{filename} -A -U")
+ end
+
+ def structure_load(filename)
+ Kernel.system("sqlcmd -S #{configuration['host']} -d #{configuration['database']} -U #{configuration['username']} -P #{configuration['password']} -i #{filename}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 4dbd668fcf..a5955ccba4 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -339,8 +339,12 @@ module ActiveRecord
# Save the new record state and id of a record so it can be restored later if a transaction fails.
def remember_transaction_record_state #:nodoc:
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- @_start_transaction_state[:new_record] = @new_record
- @_start_transaction_state[:destroyed] = @destroyed
+ unless @_start_transaction_state.include?(:new_record)
+ @_start_transaction_state[:new_record] = @new_record
+ end
+ unless @_start_transaction_state.include?(:destroyed)
+ @_start_transaction_state[:destroyed] = @destroyed
+ end
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
@_start_transaction_state[:frozen?] = @attributes.frozen?
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3706885881..26dca415ff 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -74,8 +74,7 @@ module ActiveRecord
protected
def perform_validations(options={}) # :nodoc:
- perform_validation = options[:validate] != false
- perform_validation ? valid?(options[:context]) : true
+ options[:validate] == false || valid?(options[:context])
end
end
end
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index c0471bb506..de5fd05468 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,10 +1,11 @@
module ActiveRecord
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta1"
+ # Returns the version of the currently loaded ActiveRecord as a Gem::Version
+ def self.version
+ Gem::Version.new "4.1.0.beta"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveRecord.version.segments
+ STRING = ActiveRecord.version.to_s
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 0af7cbf74f..e28bb7b6ca 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -114,7 +114,7 @@ module ActiveRecord
end
end
- # test resetting sequences in odd tables in postgreSQL
+ # test resetting sequences in odd tables in PostgreSQL
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
require 'models/movie'
require 'models/subscriber'
@@ -167,7 +167,7 @@ module ActiveRecord
else
@connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
end
- # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block
+ # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block
# and will fail (at least on Oracle)
@connection.execute "DELETE FROM fk_test_has_fk"
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 5d71effb1f..e6d0183b11 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -21,20 +21,44 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :type => type)
+ end
+
+ %w(btree hash).each do |using|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => using)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index b965983fec..1844a2e0dc 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -17,7 +17,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
def test_connect_with_url
- run_without_connection do |orig|
+ run_without_connection do
ar_config = ARTest.connection_config['arunit']
skip "This test doesn't work with custom socket location" if ar_config['socket']
diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb
index 40af317ad1..f4e7a3ef0a 100644
--- a/activerecord/test/cases/adapters/mysql/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql/enum_test.rb
@@ -5,6 +5,6 @@ class MysqlEnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
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 0eb1cc511e..a75883cd3a 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -16,6 +16,15 @@ module ActiveRecord
eosql
end
+ def test_valid_column
+ column = @conn.columns('ex').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @conn.valid_type?(:foobar)
+ end
+
def test_client_encoding
assert_equal Encoding::UTF_8, @conn.client_encoding
end
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index d94bb629a7..807a7a155e 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -35,6 +35,28 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
+
+ def test_dump_indexes
+ index_a_name = 'index_key_tests_on_snack'
+ index_b_name = 'index_key_tests_on_pizza'
+ index_c_name = 'index_key_tests_on_awesome'
+
+ table = 'key_tests'
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 3,indexes.size
+
+ index_a = indexes.select{|i| i.name == index_a_name}[0]
+ index_b = indexes.select{|i| i.name == index_b_name}[0]
+ index_c = indexes.select{|i| i.name == index_c_name}[0]
+ assert_equal :btree, index_a.using
+ assert_nil index_a.type
+ assert_equal :btree, index_b.using
+ assert_nil index_b.type
+
+ assert_nil index_c.using
+ assert_equal :fulltext, index_c.type
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index a83399d0cd..8a2a7ef269 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -21,20 +21,44 @@ class ActiveSchemaTest < ActiveRecord::TestCase
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+
+ %w(SPATIAL FULLTEXT UNIQUE).each do |type|
+ expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :type => type)
+ end
+
+ %w(btree hash).each do |using|
+ expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
+ assert_equal expected, add_index(:people, :last_name, :using => using)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
+ assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
+
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :coyp)
+ end
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) "
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
end
@@ -70,8 +94,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
def test_add_timestamps
with_real_execute do
begin
- ActiveRecord::Base.connection.create_table :delete_me do |t|
- end
+ ActiveRecord::Base.connection.create_table :delete_me
ActiveRecord::Base.connection.add_timestamps :delete_me
assert column_present?('delete_me', 'updated_at', 'datetime')
assert column_present?('delete_me', 'created_at', 'datetime')
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index f3a05e48ad..6dd9a5ec87 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -5,6 +5,6 @@ class Mysql2EnumTest < ActiveRecord::TestCase
end
def test_enum_limit
- assert_equal 5, EnumTest.columns.first.limit
+ assert_equal 6, EnumTest.columns.first.limit
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 94429e772f..5db60ff8a0 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -44,6 +44,27 @@ module ActiveRecord
assert_match(/database 'foo-bar'/, e.inspect)
end
+ def test_dump_indexes
+ index_a_name = 'index_key_tests_on_snack'
+ index_b_name = 'index_key_tests_on_pizza'
+ index_c_name = 'index_key_tests_on_awesome'
+
+ table = 'key_tests'
+
+ indexes = @connection.indexes(table).sort_by {|i| i.name}
+ assert_equal 3,indexes.size
+
+ index_a = indexes.select{|i| i.name == index_a_name}[0]
+ index_b = indexes.select{|i| i.name == index_b_name}[0]
+ index_c = indexes.select{|i| i.name == index_c_name}[0]
+ assert_equal :btree, index_a.using
+ assert_nil index_a.type
+ assert_equal :btree, index_b.using
+ assert_nil index_b.type
+
+ assert_nil index_c.using
+ assert_equal :fulltext, index_c.type
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 01c3e6b49b..16329689c0 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -29,9 +29,29 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
false
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
+
+ %w(gin gist hash btree).each do |type|
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type)
+
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
+ end
+
+ assert_raise ArgumentError do
+ add_index(:people, :last_name, algorithm: :copy)
+ end
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 8774bf626f..61a3a2ba0f 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -81,6 +81,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase
assert_cycle(['1',nil,nil])
end
+ def test_insert_fixture
+ tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
+ @connection.insert_fixture({"tags" => tag_values}, "pg_arrays" )
+ assert_equal(PgArray.last.tags, tag_values)
+ end
+
private
def assert_cycle array
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
new file mode 100644
index 0000000000..d7d77f96e2
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlByteaTest < ActiveRecord::TestCase
+ class ByteaDataType < ActiveRecord::Base
+ self.table_name = 'bytea_data_type'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.transaction do
+ @connection.create_table('bytea_data_type') do |t|
+ t.binary 'payload'
+ end
+ end
+ end
+ @column = ByteaDataType.columns.find { |c| c.name == 'payload' }
+ assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists bytea_data_type'
+ end
+
+ def test_column
+ assert_equal :binary, @column.type
+ end
+
+ def test_type_cast_binary_converts_the_encoding
+ assert @column
+
+ data = "\u001F\x8B"
+ assert_equal('UTF-8', data.encoding.name)
+ assert_equal('ASCII-8BIT', @column.type_cast(data).encoding.name)
+ end
+
+ def test_type_cast_binary_value
+ data = "\u001F\x8B".force_encoding("BINARY")
+ assert_equal(data, @column.type_cast(data))
+ end
+
+ def test_type_case_nil
+ assert_equal(nil, @column.type_cast(nil))
+ end
+
+ def test_read_value
+ data = "\u001F"
+ @connection.execute "insert into bytea_data_type (payload) VALUES ('#{data}')"
+ record = ByteaDataType.first
+ assert_equal(data, record.payload)
+ record.delete
+ end
+
+ def test_read_nil_value
+ @connection.execute "insert into bytea_data_type (payload) VALUES (null)"
+ record = ByteaDataType.first
+ assert_equal(nil, record.payload)
+ record.delete
+ end
+
+ def test_write_value
+ data = "\u001F"
+ record = ByteaDataType.create(payload: data)
+ refute record.new_record?
+ assert_equal(data, record.payload)
+ end
+
+ def test_write_binary
+ data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log'))
+ assert(data.size > 1)
+ record = ByteaDataType.create(payload: data)
+ refute record.new_record?
+ assert_equal(data, record.payload)
+ assert_equal(data, ByteaDataType.where(id: record.id).first.payload)
+ end
+
+ def test_write_nil
+ record = ByteaDataType.create(payload: nil)
+ refute record.new_record?
+ assert_equal(nil, record.payload)
+ assert_equal(nil, ByteaDataType.where(id: record.id).first.payload)
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index c03660957e..6b726ce875 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -81,42 +81,6 @@ module ActiveRecord
assert_equal 'SCHEMA', @connection.logged[0][1]
end
- def test_reconnection_after_simulated_disconnection_with_verify
- assert @connection.active?
- original_connection_pid = @connection.query('select pg_backend_pid()')
-
- # Fail with bad connection on next query attempt.
- raw_connection = @connection.raw_connection
- raw_connection_class = class << raw_connection ; self ; end
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def query_fake(*args)
- if !( @called ||= false )
- self.stubs(:status).returns(PGconn::CONNECTION_BAD)
- @called = true
- raise PGError
- else
- self.unstub(:status)
- query_unfake(*args)
- end
- end
-
- alias query_unfake query
- alias query query_fake
- CODE
-
- begin
- @connection.verify!
- new_connection_pid = @connection.query('select pg_backend_pid()')
- ensure
- raw_connection_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- alias query query_unfake
- undef query_fake
- CODE
- end
-
- assert_not_equal original_connection_pid, new_connection_pid, "Should have a new underlying connection pid"
- end
-
# Must have with_manual_interventions set to true for this
# test to run.
# When prompted, restart the PostgreSQL server with the
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 1e6ae85a25..8c17372286 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -545,13 +545,19 @@ _SQL
def test_update_bit_string
new_bit_string = '11111111'
- new_bit_string_varying = '11111110'
+ new_bit_string_varying = '0xFF'
assert @first_bit_string.bit_string = new_bit_string
assert @first_bit_string.bit_string_varying = new_bit_string_varying
assert @first_bit_string.save
assert @first_bit_string.reload
- assert_equal new_bit_string, @first_bit_string.bit_string
- assert_equal new_bit_string_varying, @first_bit_string.bit_string_varying
+ assert_equal @first_bit_string.bit_string, new_bit_string
+ assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
+ end
+
+ def test_invalid_hex_string
+ new_bit_string = 'FF'
+ @first_bit_string.bit_string = new_bit_string
+ assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save }
end
def test_update_oid
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 619d581d5f..0b61f61572 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -22,13 +22,6 @@ module ActiveRecord
assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
assert_match %(Seq Scan on audit_logs), explain
end
-
- def test_dont_explain_for_set_search_path
- queries = Thread.current[:available_queries_for_explain] = []
- ActiveRecord::Base.connection.schema_search_path = "public"
- assert queries.empty?
- end
-
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index ad98d7c8ce..e434b4861c 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -40,25 +40,15 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert @connection.extensions.include?('hstore'), "extension list should include hstore"
end
- def test_hstore_enabled
+ def test_disable_enable_hstore
assert @connection.extension_enabled?('hstore')
- end
-
- def test_disable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- assert_not @connection.extension_enabled?('hstore')
- end
- end
-
- def test_enable_hstore
- if @connection.extension_enabled?('hstore')
- @connection.disable_extension 'hstore'
- end
-
+ @connection.disable_extension 'hstore'
assert_not @connection.extension_enabled?('hstore')
@connection.enable_extension 'hstore'
assert @connection.extension_enabled?('hstore')
+ ensure
+ # Restore column(s) dropped by `drop extension hstore cascade;`
+ load_schema
end
def test_column
@@ -189,6 +179,10 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase
assert_cycle('ca' => 'cà', 'ac' => 'àc')
end
+ def test_multiline
+ assert_cycle("a\nb" => "c\nd")
+ end
+
private
def assert_cycle hash
# test creation
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 05e0f0e192..17d77c5454 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -10,6 +10,15 @@ module ActiveRecord
@connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))')
end
+ def test_valid_column
+ column = @connection.columns('ex').find { |col| col.name == 'id' }
+ assert @connection.valid_type?(column.type)
+ end
+
+ def test_invalid_column
+ assert_not @connection.valid_type?(:foobar)
+ end
+
def test_primary_key
assert_equal 'id', @connection.primary_key('ex')
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index cd31900d4e..e8dd188ec8 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -11,16 +11,19 @@ class SchemaTest < ActiveRecord::TestCase
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
INDEX_C_NAME = 'c_index_full_text_search'
INDEX_D_NAME = 'd_index_things_on_description_desc'
+ INDEX_E_NAME = 'e_index_things_on_name_vector'
INDEX_A_COLUMN = 'name'
INDEX_B_COLUMN_S1 = 'email'
INDEX_B_COLUMN_S2 = 'moment'
INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
INDEX_D_COLUMN = 'description'
+ INDEX_E_COLUMN = 'name_vector'
COLUMNS = [
'id integer',
'name character varying(50)',
'email character varying(50)',
'description character varying(100)',
+ 'name_vector tsvector',
'moment timestamp without time zone default now()'
]
PK_TABLE_NAME = 'table_with_pk'
@@ -61,6 +64,8 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
@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))"
@@ -236,15 +241,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_dump_indexes_for_schema_one
- do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_two
- do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_dump_indexes_for_schema_multiple_schemas_in_search_path
- do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
+ do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
end
def test_with_uppercase_index_name
@@ -344,15 +349,20 @@ class SchemaTest < ActiveRecord::TestCase
@connection.schema_search_path = "'$user', public"
end
- def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
+ def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
with_schema_search_path(this_schema_name) do
indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
- assert_equal 3,indexes.size
+ assert_equal 4,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
+ do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
+ indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
+ assert_equal :btree, index.using
+ end
+ assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 53002c5265..c0c0e8898c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -35,6 +35,16 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
@connection.execute 'drop table if exists pg_uuids'
end
+ def test_id_is_uuid
+ assert_equal :uuid, UUID.columns_hash['id'].type
+ assert UUID.primary_key
+ end
+
+ def test_id_has_a_default
+ u = UUID.create
+ assert_not_nil u.id
+ end
+
def test_auto_create_uuid
u = UUID.create
u.reload
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index a5a22bc30b..e78cb88562 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -54,7 +54,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_id_col_that_is_not_primary_key
- test_copy_table('goofy_string_id', 'goofy_string_id2') do |from, to, options|
+ test_copy_table('goofy_string_id', 'goofy_string_id2') do
original_id = @connection.columns('goofy_string_id').detect{|col| col.name == 'id' }
copied_id = @connection.columns('goofy_string_id2').detect{|col| col.name == 'id' }
assert_equal original_id.type, copied_id.type
@@ -65,7 +65,7 @@ class CopyTableTest < ActiveRecord::TestCase
end
def test_copy_table_with_unconventional_primary_key
- test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ test_copy_table('owners', 'owners_unconventional') do
original_pk = @connection.primary_key('owners')
copied_pk = @connection.primary_key('owners_unconventional')
assert_equal original_pk, copied_pk
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 003052bac4..d51d425c3c 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -25,6 +25,19 @@ module ActiveRecord
@conn.intercepted = true
end
+ def test_valid_column
+ column = @conn.columns('items').find { |col| col.name == 'id' }
+ assert @conn.valid_type?(column.type)
+ end
+
+ # sqlite databses should be able to support any type and not
+ # just the ones mentioned in the native_database_types.
+ # Therefore test_invalid column should always return true
+ # even if the type is not valid.
+ def test_invalid_column
+ assert @conn.valid_type?(:foobar)
+ end
+
def teardown
@conn.intercepted = false
@conn.logged = []
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 10195e3ae4..5536702f58 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -141,7 +141,6 @@ class AggregationsTest < ActiveRecord::TestCase
end
class OverridingAggregationsTest < ActiveRecord::TestCase
- class Name; end
class DifferentName; end
class Person < ActiveRecord::Base
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 3a5dea6f13..4aa6567d85 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -302,7 +302,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_foreign_keys
pets = Pet.all.merge!(:includes => :owner).to_a
- assert_equal 3, pets.length
+ assert_equal 4, pets.length
end
def test_eager_association_loading_with_belongs_to
@@ -1174,6 +1174,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" }
end
+ test "preloading a through association twice does not reset it" do
+ members = Member.includes(current_membership: :club).includes(:club).to_a
+ assert_no_queries {
+ assert_equal 3, members.map(&:current_membership).map(&:club).size
+ }
+ end
+
test "works in combination with order(:symbol)" do
author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first
assert_equal authors(:bob), author
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 781b87741d..d85570236f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -755,6 +755,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal topic.replies.to_a.size, topic.replies_count
end
+ def test_pushing_association_updates_counter_cache
+ topic = Topic.order("id ASC").first
+ reply = Reply.create!
+
+ assert_difference "topic.reload.replies_count", 1 do
+ topic.replies << reply
+ end
+ end
+
def test_deleting_updates_counter_cache_without_dependent_option
post = posts(:welcome)
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 67d18f313a..70c6b489aa 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -583,7 +583,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
- assert_equal 1, owners(:blackbeard).toys.count
+ assert_equal 2, owners(:blackbeard).toys.count
end
def test_find_on_has_many_association_collection_with_include_and_conditions
@@ -882,6 +882,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [tags(:general)], post.reload.tags
end
+ def test_has_many_through_obeys_order_on_through_association
+ owner = owners(:blackbeard)
+ assert owner.toys.to_sql.include?("pets.name desc")
+ assert_equal ["parrot", "bulbul"], owner.toys.map { |r| r.pet.name }
+ end
+
test "has many through associations on new records use null relations" do
person = Person.new
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 4ed09a3bf7..0e48fbca9c 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -522,4 +522,20 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
account = Account.find(2)
assert_queries { company.account = account }
end
+
+ def test_has_one_assignment_triggers_save_on_change
+ pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?")
+ ship = pirate.build_ship(name: 'old name')
+ ship.save!
+
+ ship.name = 'new name'
+ assert ship.changed?
+ assert_queries(2) do
+ # One query for updating name and second query for updating pirate_id
+ pirate.ship = ship
+ end
+
+ assert_equal 'new name', pirate.ship.reload.name
+ end
+
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index c8cad84013..ec128acf28 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -235,6 +235,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
end
+ def test_parent_instance_should_be_shared_within_create_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
+ def test_parent_instance_should_be_shared_within_build_block_of_new_child
+ man = Man.first
+ interest = man.interests.build do |i|
+ assert i.man.equal?(man), "Man of child should be the same instance as a parent"
+ end
+ assert interest.man.equal?(man), "Man of the child should still be the same instance as a parent"
+ end
+
def test_parent_instance_should_be_shared_with_poked_in_child
m = men(:gordon)
i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
@@ -303,6 +319,22 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed"
end
+ def test_raise_record_not_found_error_when_invalid_ids_are_passed
+ man = Man.create!
+
+ invalid_id = 2394823094892348920348523452345
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_id) }
+
+ invalid_ids = [8432342, 2390102913, 2453245234523452]
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find(invalid_ids) }
+ end
+
+ def test_raise_record_not_found_error_when_no_ids_are_passed
+ man = Man.create!
+
+ assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() }
+ end
+
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index a06bacafca..95c571fd03 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -245,7 +245,6 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
class OverridingAssociationsTest < ActiveRecord::TestCase
- class Person < ActiveRecord::Base; end
class DifferentPerson < ActiveRecord::Base; end
class PeopleList < ActiveRecord::Base
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 648596b828..d9c032392d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -69,7 +69,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_boolean_attributes
- assert ! Topic.find(1).approved?
+ assert !Topic.find(1).approved?
assert Topic.find(2).approved?
end
@@ -141,13 +141,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_respond_to topic, :title
end
- # IRB inspects the return value of "MyModel.allocate"
- # by inspecting it.
+ # IRB inspects the return value of "MyModel.allocate".
def test_allocated_object_can_be_inspected
topic = Topic.allocate
- topic.instance_eval { @attributes = nil }
- assert_nothing_raised { topic.inspect }
- assert topic.inspect, "#<Topic not initialized>"
+ assert_equal "#<Topic not initialized>", topic.inspect
end
def test_array_content
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index e5cb4f8f7a..536ff4882c 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -439,7 +439,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_assign_ids_for_through_a_belongs_to
- post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
+ post = Post.new(:title => "Assigning IDs works!", :body => "You heard it here first, folks!")
post.person_ids = [people(:david).id, people(:michael).id]
post.save
post.reload
@@ -1335,7 +1335,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
assert !@pirate.valid?
end
- test "should not automatically asd validate associations without :validate => true" do
+ test "should not automatically add validate associations without :validate => true" do
assert @pirate.valid?
@pirate.non_validated_ship.name = ''
assert @pirate.valid?
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 7fb50e9617..bd568af06a 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -21,7 +21,6 @@ require 'models/parrot'
require 'models/person'
require 'models/edge'
require 'models/joke'
-require 'models/bulb'
require 'models/bird'
require 'models/car'
require 'models/bulb'
@@ -840,7 +839,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
@@ -869,7 +868,7 @@ class BasicsTest < ActiveRecord::TestCase
# Reload and check that we have all the geometric attributes.
h = Geometric.find(g.id)
- assert_equal '(5,6.1)', h.a_point
+ assert_equal [5.0, 6.1], h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
@@ -880,6 +879,29 @@ class BasicsTest < ActiveRecord::TestCase
objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
assert_equal true, objs[0].isclosed
+
+ # test native ruby formats when defining the geometric types
+ g = Geometric.new(
+ :a_point => [5.0, 6.1],
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ assert g.save
+
+ # Reload and check that we have all the geometric attributes.
+ h = Geometric.find(g.id)
+
+ assert_equal [5.0, 6.1], h.a_point
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
end
end
@@ -1024,7 +1046,7 @@ class BasicsTest < ActiveRecord::TestCase
Joke.reset_sequence_name
end
- def test_dont_clear_inheritnce_column_when_setting_explicitly
+ def test_dont_clear_inheritance_column_when_setting_explicitly
Joke.inheritance_column = "my_type"
before_inherit = Joke.inheritance_column
@@ -1355,9 +1377,9 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil Thread.current[:UnloadablePost_current_scope]
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil Thread.current[:UnloadablePost_current_scope]
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
@@ -1555,4 +1577,61 @@ class BasicsTest < ActiveRecord::TestCase
klass = Class.new(ActiveRecord::Base)
assert_equal ['foo'], klass.all.merge!(select: 'foo').select_values
end
+
+ test "connection_handler can be overridden" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ thread_connection_handler = nil
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ thread_connection_handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal thread_connection_handler, new_handler
+ end
+
+ test "new threads get default the default connection handler" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ handler = nil
+
+ t = Thread.new do
+ handler = klass.connection_handler
+ end
+ t.join
+
+ assert_equal handler, orig_handler
+ assert_equal klass.connection_handler, orig_handler
+ assert_equal klass.default_connection_handler, orig_handler
+ end
+
+ test "changing a connection handler in a main thread does not poison the other threads" do
+ klass = Class.new(ActiveRecord::Base)
+ orig_handler = klass.connection_handler
+ new_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
+ after_handler = nil
+ is_set = false
+
+ t = Thread.new do
+ klass.connection_handler = new_handler
+ is_set = true
+ Thread.stop
+ after_handler = klass.connection_handler
+ end
+
+ while(!is_set)
+ Thread.pass
+ end
+
+ klass.connection_handler = orig_handler
+ t.wakeup
+ t.join
+
+ assert_equal after_handler, new_handler
+ assert_equal orig_handler, klass.connection_handler
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 87559925e7..ba6b0b1362 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -19,7 +19,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_each_should_not_return_query_chain_and_execcute_only_one_query
+ def test_each_should_not_return_query_chain_and_execute_only_one_query
assert_queries(1) do
result = Post.find_each(:batch_size => 100000){ }
assert_nil result
@@ -68,7 +68,7 @@ class EachTest < ActiveRecord::TestCase
end
end
- def test_find_in_batches_shouldnt_excute_query_unless_needed
+ def test_find_in_batches_shouldnt_execute_query_unless_needed
post_count = Post.count
assert_queries(2) do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index c645523905..b0b647cbf7 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -96,25 +96,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_order_by_grouped_field
- c = Account.all.merge!(:group => :firm_id, :order => "firm_id").sum(:credit_limit)
+ c = Account.group(:firm_id).order("firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
end
def test_should_order_by_calculation
- c = Account.all.merge!(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit)
+ c = Account.group(:firm_id).order("sum_credit_limit desc, firm_id").sum(:credit_limit)
assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
assert_equal [6, 2, 9, 1], c.keys.compact
end
def test_should_limit_calculation
- c = Account.all.merge!(:where => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit)
+ c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").limit(2).sum(:credit_limit)
assert_equal [1, 2], c.keys.compact
end
def test_should_limit_calculation_with_offset
- c = Account.all.merge!(:where => "firm_id IS NOT NULL", :group => :firm_id,
- :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit)
+ c = Account.where("firm_id IS NOT NULL").group(:firm_id).order("firm_id").
+ limit(2).offset(1).sum(:credit_limit)
assert_equal [2, 6], c.keys.compact
end
@@ -164,8 +163,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition
- c = Account.all.merge!(:group => :firm_id,
- :having => 'sum(credit_limit) > 50').sum(:credit_limit)
+ c = Account.group(:firm_id).having('sum(credit_limit) > 50').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -200,17 +198,15 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_with_conditions
- c = Account.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id).sum(:credit_limit)
+ c = Account.where('firm_id > 1').group(:firm_id).sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_group_by_summed_field_with_conditions_and_having
- c = Account.all.merge!(:where => 'firm_id > 1',
- :group => :firm_id,
- :having => 'sum(credit_limit) > 60').sum(:credit_limit)
+ c = Account.where('firm_id > 1').group(:firm_id).
+ having('sum(credit_limit) > 60').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_nil c[2]
@@ -322,7 +318,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select
Account.update_all("credit_limit = NULL")
- assert_equal 0, Account.all.merge!(:select => "credit_limit").count
+ assert_equal 0, Account.select("credit_limit").count
end
def test_should_count_scoped_select_with_options
@@ -330,11 +326,11 @@ class CalculationsTest < ActiveRecord::TestCase
Account.last.update_columns('credit_limit' => 49)
Account.first.update_columns('credit_limit' => 51)
- assert_equal 1, Account.all.merge!(:select => "credit_limit").where('credit_limit >= 50').count
+ assert_equal 1, Account.select("credit_limit").where('credit_limit >= 50').count
end
def test_should_count_manual_select_with_include
- assert_equal 6, Account.all.merge!(:select => "DISTINCT accounts.id", :includes => :firm).count
+ assert_equal 6, Account.select("DISTINCT accounts.id").includes(:firm).count
end
def test_count_with_column_parameter
@@ -366,7 +362,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_count_field_in_joined_table_with_group_by
- c = Account.all.merge!(:group => 'accounts.firm_id', :joins => :firm).count('companies.id')
+ c = Account.group('accounts.firm_id').joins(:firm).count('companies.id')
[1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
end
diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb
index d91646efca..5e43082c33 100644
--- a/activerecord/test/cases/clone_test.rb
+++ b/activerecord/test/cases/clone_test.rb
@@ -29,5 +29,12 @@ module ActiveRecord
topic.author_name = 'Aaron'
assert_equal 'Aaron', cloned.author_name
end
+
+ def test_freezing_a_cloned_model_does_not_freeze_clone
+ cloned = Topic.new
+ clone = cloned.clone
+ cloned.freeze
+ assert_not clone.frozen?
+ end
end
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index b874adc081..b72c54f97b 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -43,10 +43,20 @@ module ActiveRecord
assert_equal [], coder.load([])
end
- def test_load_swallows_yaml_exceptions
+ def test_load_doesnt_swallow_yaml_exceptions
coder = YAMLColumn.new
bad_yaml = '--- {'
- assert_equal bad_yaml, coder.load(bad_yaml)
+ assert_raises(Psych::SyntaxError) do
+ coder.load(bad_yaml)
+ end
+ end
+
+ def test_load_doesnt_handle_undefined_class_or_module
+ coder = YAMLColumn.new
+ missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'
+ assert_raises(ArgumentError) do
+ coder.load(missing_class_yaml)
+ end
end
end
end
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index bd2fbaa7db..dbb2f223cd 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def @adapter.native_database_types
{:string => "varchar"}
end
+ @viz = @adapter.schema_creation
end
def test_can_set_coder
@@ -35,25 +36,25 @@ module ActiveRecord
def test_should_not_include_default_clause_when_default_is_null
column = Column.new("title", nil, "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal "title varchar(20)", column_def.to_sql
+ assert_equal "title varchar(20)", @viz.accept(column_def)
end
def test_should_include_default_clause_when_default_is_present
column = Column.new("title", "Hello", "varchar(20)")
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello'}, @viz.accept(column_def)
end
def test_should_specify_not_null_if_null_option_is_false
column = Column.new("title", "Hello", "varchar(20)", false)
column_def = ColumnDefinition.new(
- @adapter, column.name, "string",
+ column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null)
- assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, @viz.accept(column_def)
end
if current_adapter?(:MysqlAdapter)
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index adbe51f430..3a4f414ae8 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -6,6 +6,9 @@ module ActiveRecord
class ColumnTest < ActiveRecord::TestCase
def test_type_cast_boolean
column = Column.new("field", nil, "boolean")
+ assert column.type_cast('').nil?
+ assert column.type_cast(nil).nil?
+
assert column.type_cast(true)
assert column.type_cast(1)
assert column.type_cast('1')
@@ -15,15 +18,21 @@ module ActiveRecord
assert column.type_cast('TRUE')
assert column.type_cast('on')
assert column.type_cast('ON')
- assert !column.type_cast(false)
- assert !column.type_cast(0)
- assert !column.type_cast('0')
- assert !column.type_cast('f')
- assert !column.type_cast('F')
- assert !column.type_cast('false')
- assert !column.type_cast('FALSE')
- assert !column.type_cast('off')
- assert !column.type_cast('OFF')
+
+ # explicitly check for false vs nil
+ assert_equal false, column.type_cast(false)
+ assert_equal false, column.type_cast(0)
+ assert_equal false, column.type_cast('0')
+ assert_equal false, column.type_cast('f')
+ assert_equal false, column.type_cast('F')
+ assert_equal false, column.type_cast('false')
+ assert_equal false, column.type_cast('FALSE')
+ assert_equal false, column.type_cast('off')
+ assert_equal false, column.type_cast('OFF')
+ assert_equal false, column.type_cast(' ')
+ assert_equal false, column.type_cast("\u3000\r\n")
+ assert_equal false, column.type_cast("\u0000")
+ assert_equal false, column.type_cast('SOMETHING RANDOM')
end
def test_type_cast_integer
@@ -65,8 +74,9 @@ module ActiveRecord
def test_type_cast_time
column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
time_string = Time.now.utc.strftime("%T")
assert_equal time_string, column.type_cast(time_string).strftime("%T")
@@ -74,8 +84,10 @@ module ActiveRecord
def test_type_cast_datetime_and_timestamp
[Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
datetime_string = Time.now.utc.strftime("%FT%T")
assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
@@ -84,8 +96,10 @@ module ActiveRecord
def test_type_cast_date
column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('')
- assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast(' ')
+ assert_equal nil, column.type_cast('ABC')
date_string = Time.now.utc.strftime("%F")
assert_equal date_string, column.type_cast(date_string).strftime("%F")
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
index 1fd64dd0af..eb2fe5639b 100644
--- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -2,6 +2,15 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
+ class ConnectionPool
+ def insert_connection_for_test!(c)
+ synchronize do
+ @connections << c
+ @available.add c
+ end
+ end
+ end
+
class AbstractAdapterTest < ActiveRecord::TestCase
attr_reader :adapter
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 5379a70034..ac093251a5 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -117,6 +117,7 @@ class CounterCacheTest < ActiveRecord::TestCase
test "update other counters on parent destroy" do
david, joanna = dog_lovers(:david, :joanna)
+ joanna = joanna # squelch a warning
assert_difference 'joanna.reload.dogs_count', -1 do
david.destroy
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 7b2034dadf..36b87033ae 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -213,9 +213,11 @@ class DirtyTest < ActiveRecord::TestCase
topic = target.create
assert_nil topic.written_on
- topic.written_on = ""
- assert_nil topic.written_on
- assert !topic.written_on_changed?
+ ["", nil].each do |value|
+ topic.written_on = value
+ assert_nil topic.written_on
+ assert !topic.written_on_changed?
+ end
end
end
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index dbf6005a67..f73e449610 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -128,7 +128,7 @@ module ActiveRecord
prev_default_scopes = Topic.default_scopes
Topic.default_scopes = [Topic.where(:approved => true)]
topic = Topic.new(:approved => false)
- assert !topic.dup.approved?, "should not be overriden by default scopes"
+ assert !topic.dup.approved?, "should not be overridden by default scopes"
ensure
Topic.default_scopes = prev_default_scopes
end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index b425967678..fb53a92c89 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -1,55 +1,54 @@
require 'cases/helper'
+require 'active_record/explain_subscriber'
+require 'active_record/explain_registry'
if ActiveRecord::Base.connection.supports_explain?
class ExplainSubscriberTest < ActiveRecord::TestCase
SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
- def test_collects_nothing_if_available_queries_for_explain_is_nil
- with_queries(nil) do
- SUBSCRIBER.finish(nil, nil, {})
- assert_nil Thread.current[:available_queries_for_explain]
- end
+ def setup
+ ActiveRecord::ExplainRegistry.reset
+ ActiveRecord::ExplainRegistry.collect = true
end
def test_collects_nothing_if_the_payload_has_an_exception
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :exception => Exception.new)
- assert queries.empty?
- end
+ SUBSCRIBER.finish(nil, nil, exception: Exception.new)
+ assert queries.empty?
end
def test_collects_nothing_for_ignored_payloads
- with_queries([]) do |queries|
- ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
- SUBSCRIBER.finish(nil, nil, :name => ip)
- end
- assert queries.empty?
+ ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
+ SUBSCRIBER.finish(nil, nil, name: ip)
end
+ assert queries.empty?
+ end
+
+ def test_collects_nothing_if_collect_is_false
+ ActiveRecord::ExplainRegistry.collect = false
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2])
+ assert queries.empty?
end
def test_collects_pairs_of_queries_and_binds
sql = 'select 1 from users'
binds = [1, 2]
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds)
- assert_equal 1, queries.size
- assert_equal sql, queries[0][0]
- assert_equal binds, queries[0][1]
- end
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds)
+ assert_equal 1, queries.size
+ assert_equal sql, queries[0][0]
+ assert_equal binds, queries[0][1]
end
- def test_collects_nothing_if_unexplained_sqls
- with_queries([]) do |queries|
- SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length')
- assert queries.empty?
- end
+ def test_collects_nothing_if_the_statement_is_not_whitelisted
+ SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length')
+ assert queries.empty?
+ end
+
+ def teardown
+ ActiveRecord::ExplainRegistry.reset
end
- def with_queries(queries)
- Thread.current[:available_queries_for_explain] = queries
- yield queries
- ensure
- Thread.current[:available_queries_for_explain] = nil
+ def queries
+ ActiveRecord::ExplainRegistry.queries
end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index e505fe9f18..557cc7e7a0 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -31,6 +31,13 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ def test_symbols_table_ref
+ Post.first # warm up
+ x = Symbol.all_symbols.count
+ Post.where("title" => {"xxxqqqq" => "bar"})
+ assert_equal x, Symbol.all_symbols.count
+ end
+
# find should handle strings that come from URLs
# (example: Category.find(params[:id]))
def test_find_with_string
@@ -833,6 +840,8 @@ class FinderTest < ActiveRecord::TestCase
rescue ActiveRecord::RecordNotFound => e
assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message
end
+ ensure
+ Toy.reset_primary_key
end
def test_finder_with_offset_string
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8ad40ec3f4..f6cfee0cb8 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -576,6 +576,15 @@ class LoadAllFixturesTest < ActiveRecord::TestCase
end
end
+class LoadAllFixturesWithPathnameTest < ActiveRecord::TestCase
+ self.fixture_path = Pathname.new(FIXTURES_ROOT).join('all')
+ fixtures :all
+
+ def test_all_there
+ assert_equal %w(developers people tasks), fixture_table_names.sort
+ end
+end
+
class FasterFixturesTest < ActiveRecord::TestCase
fixtures :categories, :authors
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 99d54e7526..a9be132801 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -194,6 +194,10 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(:type => 'Account') }
end
+ def test_new_with_complex_inheritance
+ assert_nothing_raised { Client.new(type: 'VerySpecialClient') }
+ end
+
def test_new_with_autoload_paths
path = File.expand_path('../../models/autoloadable', __FILE__)
ActiveSupport::Dependencies.autoload_paths << path
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 0c896beb1d..77891b9156 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -193,11 +193,19 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_lock_without_default_sets_version_to_zero
t1 = LockWithoutDefault.new
assert_equal 0, t1.lock_version
+
+ t1.save
+ t1 = LockWithoutDefault.find(t1.id)
+ assert_equal 0, t1.lock_version
end
def test_lock_with_custom_column_without_default_sets_version_to_zero
t1 = LockWithCustomColumnWithoutDefault.new
assert_equal 0, t1.custom_lock_version
+
+ t1.save
+ t1 = LockWithCustomColumnWithoutDefault.find(t1.id)
+ assert_equal 0, t1.custom_lock_version
end
def test_readonly_attributes
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index e52809f0f8..2d7a7ec73a 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -55,13 +55,20 @@ module ActiveRecord
default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
assert_equal 70000, default_before
- rename_column "test_models", "salary", "anual_salary"
+ rename_column "test_models", "salary", "annual_salary"
- assert TestModel.column_names.include?("anual_salary")
- default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default
+ assert TestModel.column_names.include?("annual_salary")
+ default_after = connection.columns("test_models").find { |c| c.name == "annual_salary" }.default
assert_equal 70000, default_after
end
+ if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ def test_mysql_rename_column_preserves_auto_increment
+ rename_column "test_models", "id", "id_test"
+ assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra
+ end
+ end
+
def test_rename_nonexistent_column
exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
ActiveRecord::StatementInvalid
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f8afb7c591..193ffb26e3 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -258,6 +258,32 @@ class MigrationTest < ActiveRecord::TestCase
"On error, the Migrator should revert schema changes but it did not."
end
+ def test_migrator_one_up_with_exception_and_rollback_using_run
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
+ end
+
+ assert_not Person.column_methods_hash.include?(:last_name)
+
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
+
+ e = assert_raise(StandardError) { migrator.run }
+
+ assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message
+
+ Person.reset_column_information
+ assert_not Person.column_methods_hash.include?(:last_name),
+ "On error, the Migrator should revert schema changes but it did not."
+ end
+
def test_migration_without_transaction
unless ActiveRecord::Base.connection.supports_ddl_transactions?
skip "not supported on #{ActiveRecord::Base.connection.class}"
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 7042d7f4b6..b6e140b912 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -806,7 +806,7 @@ module NestedAttributesOnACollectionAssociationTests
assert_nothing_raised(NoMethodError) { @pirate.save! }
end
- def test_numeric_colum_changes_from_zero_to_no_empty_string
+ def test_numeric_column_changes_from_zero_to_no_empty_string
Man.accepts_nested_attributes_for(:interests)
repair_validations(Interest) do
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index b936cca875..db3bb56f1f 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -12,13 +12,13 @@ require 'models/minimalistic'
require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
+require 'models/owner'
require 'models/person'
require 'models/pet'
require 'models/toy'
require 'rexml/document'
class PersistencesTest < ActiveRecord::TestCase
-
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
@@ -247,15 +247,15 @@ class PersistencesTest < ActiveRecord::TestCase
topic.title = "Another New Topic"
topic.written_on = "2003-12-12 23:23:00"
topic.save
- topicReloaded = Topic.find(topic.id)
- assert_equal("Another New Topic", topicReloaded.title)
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topic_reloaded.title)
- topicReloaded.title = "Updated topic"
- topicReloaded.save
+ topic_reloaded.title = "Updated topic"
+ topic_reloaded.save
- topicReloadedAgain = Topic.find(topic.id)
+ topic_reloaded_again = Topic.find(topic.id)
- assert_equal("Updated topic", topicReloadedAgain.title)
+ assert_equal("Updated topic", topic_reloaded_again.title)
end
def test_update_columns_not_equal_attributes
@@ -263,12 +263,12 @@ class PersistencesTest < ActiveRecord::TestCase
topic.title = "Still another topic"
topic.save
- topicReloaded = Topic.allocate
- topicReloaded.init_with(
+ topic_reloaded = Topic.allocate
+ topic_reloaded.init_with(
'attributes' => topic.attributes.merge('does_not_exist' => 'test')
)
- topicReloaded.title = 'A New Topic'
- assert_nothing_raised { topicReloaded.save }
+ topic_reloaded.title = 'A New Topic'
+ assert_nothing_raised { topic_reloaded.save }
end
def test_update_for_record_with_only_primary_key
@@ -296,6 +296,22 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal "Reply", topic.type
end
+ def test_update_after_create
+ klass = Class.new(Topic) do
+ def self.name; 'Topic'; end
+ after_create do
+ update_attribute("author_name", "David")
+ end
+ end
+ topic = klass.new
+ topic.title = "Another New Topic"
+ topic.save
+
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topic_reloaded.title)
+ assert_equal("David", topic_reloaded.author_name)
+ end
+
def test_delete
topic = Topic.find(1)
assert_equal topic, topic.delete, 'topic.delete did not return self'
@@ -661,6 +677,15 @@ class PersistencesTest < ActiveRecord::TestCase
topic.reload
assert !topic.approved?
assert_equal "The First Topic", topic.title
+
+ assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
+ topic.update_attributes(id: 3, title: "Hm is it possible?")
+ end
+ assert_not_equal "Hm is it possible?", Topic.find(3).title
+
+ topic.update_attributes(id: 1234)
+ assert_nothing_raised { topic.reload }
+ assert_equal topic.title, Topic.find(1234).title
end
def test_update!
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 9ca980fdf6..482c1b3d48 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -1,10 +1,12 @@
require "cases/helper"
require 'models/post'
require 'models/comment'
+require 'models/author'
+require 'models/rating'
module ActiveRecord
class RelationTest < ActiveRecord::TestCase
- fixtures :posts, :comments
+ fixtures :posts, :comments, :authors
class FakeKlass < Struct.new(:table_name, :name)
end
@@ -176,6 +178,21 @@ module ActiveRecord
relation.merge!(where: ['foo = ?', 'bar'])
assert_equal ['foo = bar'], relation.where_values
end
+
+ def test_relation_merging_with_merged_joins
+ special_comments_with_ratings = SpecialComment.joins(:ratings)
+ posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
+ assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ end
+
+ def test_respond_to_for_non_selected_element
+ post = Post.select(:title).first
+ assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
+
+ post = Post.select("'title' as post_title").first
+ assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
+ end
+
end
class RelationMutationTest < ActiveSupport::TestCase
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9008c2785e..cf6af4e8f4 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1299,6 +1299,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
end
+ def test_doesnt_add_having_values_if_options_are_blank
+ scope = Post.having('')
+ assert_equal [], scope.having_values
+
+ scope = Post.having([])
+ assert_equal [], scope.having_values
+ end
+
def test_references_triggers_eager_loading
scope = Post.includes(:comments)
assert !scope.eager_loading?
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1147b9a09e..a48ae1036f 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,13 +177,19 @@ 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
- assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition
+ if current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) || current_adapter?(: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
+ end
end
def test_schema_dumps_partial_indices
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)"', index_definition
+ 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)
+ assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
else
assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition
end
@@ -219,6 +225,12 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
end
+
+ def test_schema_dumps_index_type
+ output = standard_dump
+ assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
+ assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ end
end
def test_schema_dump_includes_decimal_options
@@ -230,6 +242,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
if current_adapter?(:PostgreSQLAdapter)
+ def test_schema_dump_includes_bigint_default
+ output = standard_dump
+ assert_match %r{t.integer\s+"bigint_default",\s+limit: 8,\s+default: 0}, output
+ end
+
def test_schema_dump_includes_extensions
connection = ActiveRecord::Base.connection
skip unless connection.supports_extensions?
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 239004a223..0f69443839 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -1,334 +1,6 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/post'
-require 'models/author'
require 'models/developer'
-require 'models/project'
-require 'models/comment'
-require 'models/category'
-require 'models/person'
-require 'models/reference'
-
-class RelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
-
- def test_reverse_order
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
- end
-
- def test_reverse_order_with_arel_node
- assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
- end
-
- def test_reverse_order_with_multiple_arel_nodes
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_reverse_order_with_arel_nodes_and_strings
- assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
- end
-
- def test_double_reverse_order_produces_original_order
- assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
- end
-
- def test_scoped_find
- Developer.where("name = 'David'").scoping do
- assert_nothing_raised { Developer.find(1) }
- end
- end
-
- def test_scoped_find_first
- developer = Developer.find(10)
- Developer.where("salary = 100000").scoping do
- assert_equal developer, Developer.order("name").first
- end
- end
-
- def test_scoped_find_last
- highest_salary = Developer.order("salary DESC").first
-
- Developer.order("salary").scoping do
- assert_equal highest_salary, Developer.last
- end
- end
-
- def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.order("salary ASC").first
- highest_salary = Developer.order("salary DESC").first
-
- Developer.order("salary").scoping do
- assert_equal highest_salary, Developer.last
- assert_equal lowest_salary, Developer.first
- end
- end
-
- def test_scoped_find_combines_and_sanitizes_conditions
- Developer.where("salary = 9000").scoping do
- assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
- end
- end
-
- def test_scoped_find_all
- Developer.where("name = 'David'").scoping do
- assert_equal [developers(:david)], Developer.all
- end
- end
-
- def test_scoped_find_select
- Developer.select("id, name").scoping do
- developer = Developer.where("name = 'David'").first
- assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
- end
- end
-
- def test_scope_select_concatenates
- Developer.select("id, name").scoping do
- developer = Developer.select('salary').where("name = 'David'").first
- assert_equal 80000, developer.salary
- assert developer.has_attribute?(:id)
- assert developer.has_attribute?(:name)
- assert developer.has_attribute?(:salary)
- end
- end
-
- def test_scoped_count
- Developer.where("name = 'David'").scoping do
- assert_equal 1, Developer.count
- end
-
- Developer.where('salary = 100000').scoping do
- assert_equal 8, Developer.count
- assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
- end
- end
-
- def test_scoped_find_include
- # with the include, will retrieve only developers for the given project
- scoped_developers = Developer.includes(:projects).scoping do
- Developer.where('projects.id' => 2).to_a
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- end
-
- def test_scoped_find_joins
- scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
- Developer.where('developers_projects.project_id = 2').to_a
- end
-
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_create_with_where
- new_comment = VerySpecialComment.where(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with
- new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_create_with_has_higher_priority
- new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
- VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert_equal 1, new_comment.post_id
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- begin
- Developer.where("name = 'Jamis'").scoping do
- raise "an exception"
- end
- rescue
- end
-
- assert !Developer.all.where_values.include?("name = 'Jamis'")
- end
-
- def test_default_scope_filters_on_joins
- assert_equal 1, DeveloperFilteredOnJoins.all.count
- assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
- end
-
- def test_update_all_default_scope_filters_on_joins
- DeveloperFilteredOnJoins.update_all(:salary => 65000)
- assert_equal 65000, Developer.find(developers(:david).id).salary
-
- # has not changed jamis
- assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
- end
-
- def test_delete_all_default_scope_filters_on_joins
- assert_not_equal [], DeveloperFilteredOnJoins.all
-
- DeveloperFilteredOnJoins.delete_all()
-
- assert_equal [], DeveloperFilteredOnJoins.all
- assert_not_equal [], Developer.all
- end
-end
-
-class NestedRelationScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
-
- def test_merge_options
- 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
- end
- end
- end
-
- def test_merge_inner_scope_has_priority
- Developer.limit(5).scoping do
- Developer.limit(10).scoping do
- assert_equal 10, Developer.all.size
- end
- end
- end
-
- def test_replace_options
- Developer.where(:name => 'David').scoping do
- Developer.unscoped do
- assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
- end
-
- assert_equal 'David', Developer.first[:name]
- end
- end
-
- def test_three_level_nested_exclusive_scoped_find
- Developer.where("name = 'Jamis'").scoping do
- assert_equal 'Jamis', Developer.first.name
-
- Developer.unscoped.where("name = 'David'") do
- assert_equal 'David', Developer.first.name
-
- Developer.unscoped.where("name = 'Maiha'") do
- assert_equal nil, Developer.first
- end
-
- # ensure that scoping is restored
- assert_equal 'David', Developer.first.name
- end
-
- # ensure that scoping is restored
- assert_equal 'Jamis', Developer.first.name
- end
- end
-
- def test_nested_scoped_create
- comment = Comment.create_with(:post_id => 1).scoping do
- Comment.create_with(:post_id => 2).scoping do
- Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
- end
- end
-
- assert_equal 2, comment.post_id
- end
-
- def test_nested_exclusive_scope_for_create
- comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
- Comment.unscoped.create_with(:post_id => 1).scoping do
- assert Comment.new.body.blank?
- Comment.create :body => "Hey guys"
- end
- end
-
- assert_equal 1, comment.post_id
- assert_equal 'Hey guys', comment.body
- end
-end
-
-class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts, :people, :references
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- def test_forwarding_to_scoped
- assert_equal 4, Comment.search_by_type('Comment').size
- assert_equal 2, @welcome.comments.search_by_type('Comment').size
- end
-
- def test_nested_scope_finder
- Comment.where('1=0').scoping do
- assert_equal 0, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- Comment.where('1=1').scoping do
- assert_equal 2, @welcome.comments.count
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-
- def test_should_maintain_default_scope_on_associations
- magician = BadReference.find(1)
- assert_equal [magician], people(:michael).bad_references
- end
-
- def test_should_default_scope_on_associations_is_overriden_by_association_conditions
- reference = references(:michael_unicyclist).becomes(BadReference)
- assert_equal [reference], people(:michael).fixed_bad_references
- end
-
- def test_should_maintain_default_scope_on_eager_loaded_associations
- michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
- magician = BadReference.find(1)
- assert_equal [magician], michael.bad_references
- end
-end
-
-class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
- fixtures :posts, :categories, :categories_posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a category...', Category.what_are_you
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- def test_nested_scope_finder
- Category.where('1=0').scoping do
- assert_equal 0, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- Category.where('1=1').scoping do
- assert_equal 2, @welcome.categories.count
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
- end
-end
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
@@ -526,6 +198,16 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_unscope_and_scope
+ developer_klass = Class.new(Developer) do
+ scope :by_name, -> name { unscope(where: :name).where(name: name) }
+ end
+
+ expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] }
+ received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] }
+ assert_equal expected, received
+ end
+
def test_unscope_errors_with_invalid_value
assert_raises(ArgumentError) do
Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value)
@@ -634,7 +316,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
+
+ assert_equal 11, DeveloperCalledJamis.unscoped.length
+ assert_equal 1, DeveloperCalledJamis.poor.length
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
+ assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
def test_default_scope_select_ignored_by_aggregations
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index b593270352..afe32af1d1 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -6,7 +6,7 @@ require 'models/reply'
require 'models/author'
require 'models/developer'
-class NamedScopeTest < ActiveRecord::TestCase
+class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
@@ -271,6 +271,19 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal 'lifo', topic.author_name
end
+ # Method delegation for scope names which look like /\A[a-zA-Z_]\w*[!?]?\z/
+ # has been done by evaluating a string with a plain def statement. For scope
+ # names which contain spaces this approach doesn't work.
+ def test_spaces_in_scope_names
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ scope :"title containing space", -> { where("title LIKE '% %'") }
+ scope :approved, -> { where(:approved => true) }
+ end
+ assert_equal klass.send(:"title containing space"), klass.where("title LIKE '% %'")
+ assert_equal klass.approved.send(:"title containing space"), klass.approved.where("title LIKE '% %'")
+ end
+
def test_find_all_should_behave_like_select
assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved)
end
@@ -446,4 +459,9 @@ class NamedScopeTest < ActiveRecord::TestCase
end
assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
+
+ def test_subclass_merges_scopes_properly
+ assert_equal 1, SpecialComment.where(body: 'go crazy').created.count
+ end
+
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
new file mode 100644
index 0000000000..0018fc06f2
--- /dev/null
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -0,0 +1,331 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/category'
+require 'models/person'
+require 'models/reference'
+
+class RelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+
+ def test_reverse_order
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order
+ end
+
+ def test_reverse_order_with_arel_node
+ assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order
+ end
+
+ def test_reverse_order_with_multiple_arel_nodes
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_reverse_order_with_arel_nodes_and_strings
+ assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order
+ end
+
+ def test_double_reverse_order_produces_original_order
+ assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order
+ end
+
+ def test_scoped_find
+ Developer.where("name = 'David'").scoping do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ developer = Developer.find(10)
+ Developer.where("salary = 100000").scoping do
+ assert_equal developer, Developer.order("name").first
+ end
+ end
+
+ def test_scoped_find_last
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ lowest_salary = Developer.order("salary ASC").first
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.where("salary = 9000").scoping do
+ assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.where("name = 'David'").scoping do
+ assert_equal [developers(:david)], Developer.all
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.select("id, name").scoping do
+ developer = Developer.where("name = 'David'").first
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scope_select_concatenates
+ Developer.select("id, name").scoping do
+ developer = Developer.select('salary').where("name = 'David'").first
+ assert_equal 80000, developer.salary
+ assert developer.has_attribute?(:id)
+ assert developer.has_attribute?(:name)
+ assert developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scoped_count
+ Developer.where("name = 'David'").scoping do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.where('salary = 100000').scoping do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id' => 2).to_a
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ end
+
+ def test_scoped_find_joins
+ scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
+ Developer.where('developers_projects.project_id = 2').to_a
+ end
+
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
+ def test_scoped_create_with_where
+ new_comment = VerySpecialComment.where(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with
+ new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with_has_higher_priority
+ new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ begin
+ Developer.where("name = 'Jamis'").scoping do
+ raise "an exception"
+ end
+ rescue
+ end
+
+ assert !Developer.all.where_values.include?("name = 'Jamis'")
+ end
+
+ def test_default_scope_filters_on_joins
+ assert_equal 1, DeveloperFilteredOnJoins.all.count
+ assert_equal DeveloperFilteredOnJoins.all.first, developers(:david).becomes(DeveloperFilteredOnJoins)
+ end
+
+ def test_update_all_default_scope_filters_on_joins
+ DeveloperFilteredOnJoins.update_all(:salary => 65000)
+ assert_equal 65000, Developer.find(developers(:david).id).salary
+
+ # has not changed jamis
+ assert_not_equal 65000, Developer.find(developers(:jamis).id).salary
+ end
+
+ def test_delete_all_default_scope_filters_on_joins
+ assert_not_equal [], DeveloperFilteredOnJoins.all
+
+ DeveloperFilteredOnJoins.delete_all()
+
+ assert_equal [], DeveloperFilteredOnJoins.all
+ assert_not_equal [], Developer.all
+ end
+end
+
+class NestedRelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ 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
+ end
+ end
+ end
+
+ def test_merge_inner_scope_has_priority
+ Developer.limit(5).scoping do
+ Developer.limit(10).scoping do
+ assert_equal 10, Developer.all.size
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.where(:name => 'David').scoping do
+ Developer.unscoped do
+ assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
+ end
+
+ assert_equal 'David', Developer.first[:name]
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.where("name = 'Jamis'").scoping do
+ assert_equal 'Jamis', Developer.first.name
+
+ Developer.unscoped.where("name = 'David'") do
+ assert_equal 'David', Developer.first.name
+
+ Developer.unscoped.where("name = 'Maiha'") do
+ assert_equal nil, Developer.first
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'David', Developer.first.name
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'Jamis', Developer.first.name
+ end
+ end
+
+ def test_nested_scoped_create
+ comment = Comment.create_with(:post_id => 1).scoping do
+ Comment.create_with(:post_id => 2).scoping do
+ Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
+ end
+ end
+
+ assert_equal 2, comment.post_id
+ end
+
+ def test_nested_exclusive_scope_for_create
+ comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
+ Comment.unscoped.create_with(:post_id => 1).scoping do
+ assert Comment.new.body.blank?
+ Comment.create :body => "Hey guys"
+ end
+ end
+
+ assert_equal 1, comment.post_id
+ assert_equal 'Hey guys', comment.body
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts, :people, :references
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_nested_scope_finder
+ Comment.where('1=0').scoping do
+ assert_equal 0, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ Comment.where('1=1').scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+
+ def test_should_maintain_default_scope_on_associations
+ magician = BadReference.find(1)
+ assert_equal [magician], people(:michael).bad_references
+ end
+
+ def test_should_default_scope_on_associations_is_overridden_by_association_conditions
+ reference = references(:michael_unicyclist).becomes(BadReference)
+ assert_equal [reference], people(:michael).fixed_bad_references
+ end
+
+ def test_should_maintain_default_scope_on_eager_loaded_associations
+ michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
+ magician = BadReference.find(1)
+ assert_equal [magician], michael.bad_references
+ end
+end
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_nested_scope_finder
+ Category.where('1=0').scoping do
+ assert_equal 0, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ Category.where('1=1').scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+ end
+end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 726338db14..765e92ccca 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,5 +1,6 @@
require 'cases/helper'
require 'models/topic'
+require 'models/reply'
require 'models/person'
require 'models/traffic_light'
require 'bcrypt'
@@ -241,4 +242,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal [], light.state
assert_equal [], light.long_state
end
+
+ def test_serialized_column_should_not_be_wrapped_twice
+ Topic.serialize(:content, MyObject)
+
+ myobj = MyObject.new('value1', 'value2')
+ Topic.create(content: myobj)
+ Topic.create(content: myobj)
+
+ Topic.all.each do |topic|
+ type = topic.instance_variable_get("@columns_hash")["content"]
+ assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type)
+ end
+ end
end
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
new file mode 100644
index 0000000000..76da49707f
--- /dev/null
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -0,0 +1,64 @@
+require 'cases/helper'
+require 'models/book'
+require 'models/liquid'
+require 'models/molecule'
+require 'models/electron'
+
+module ActiveRecord
+ class StatementCacheTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_statement_cache_with_simple_statement
+ cache = ActiveRecord::StatementCache.new do
+ Book.where(name: "my book").where("author_id > 3")
+ end
+
+ Book.create(name: "my book", author_id: 4)
+
+ books = cache.execute
+ assert_equal "my book", books[0].name
+ end
+
+ def test_statement_cache_with_nil_statement_raises_error
+ assert_raise(ArgumentError) do
+ ActiveRecord::StatementCache.new do
+ nil
+ end
+ end
+ end
+
+ def test_statement_cache_with_complex_statement
+ cache = ActiveRecord::StatementCache.new do
+ Liquid.joins(:molecules => :electrons).where('molecules.name' => 'dioxane', 'electrons.name' => 'lepton')
+ end
+
+ salty = Liquid.create(name: 'salty')
+ molecule = salty.molecules.create(name: 'dioxane')
+ molecule.electrons.create(name: 'lepton')
+
+ liquids = cache.execute
+ assert_equal "salty", liquids[0].name
+ end
+
+ def test_statement_cache_values_differ
+ cache = ActiveRecord::StatementCache.new do
+ Book.where(name: "my book")
+ end
+
+ 3.times do
+ Book.create(name: "my book")
+ end
+
+ first_books = cache.execute
+
+ 3.times do
+ Book.create(name: "my book")
+ end
+
+ additional_books = cache.execute
+ assert first_books != additional_books
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 3bfbc92afd..e9000fef25 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -305,4 +305,11 @@ module ActiveRecord
end
end
end
+
+ class DatabaseTasksCheckSchemaFileTest < ActiveRecord::TestCase
+ def test_check_schema_file
+ Kernel.expects(:abort).with(regexp_matches(/awesome-file.sql/))
+ ActiveRecord::Tasks::DatabaseTasks.check_schema_file("awesome-file.sql")
+ end
+ end
end
diff --git a/activerecord/test/cases/tasks/firebird_rake_test.rb b/activerecord/test/cases/tasks/firebird_rake_test.rb
new file mode 100644
index 0000000000..c54989ae34
--- /dev/null
+++ b/activerecord/test/cases/tasks/firebird_rake_test.rb
@@ -0,0 +1,100 @@
+require 'cases/helper'
+
+unless defined?(FireRuby::Database)
+module FireRuby
+ module Database; end
+end
+end
+
+module ActiveRecord
+ module FirebirdSetupper
+ def setup
+ @database = 'db.firebird'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'firebird',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::FirebirdDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::FirebirdAdapter
+ end
+ end
+
+ class FirebirdDBCreateTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBDropTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class FirebirdStructureDumpTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_dump
+ filename = "filebird.sql"
+ Kernel.expects(:system).with("isql -a #{@database} > #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class FirebirdStructureLoadTest < ActiveRecord::TestCase
+ include FirebirdSetupper
+
+ def setup
+ super
+ FireRuby::Database.stubs(:db_string_for).returns(@database)
+ end
+
+ def test_structure_load
+ filename = "firebird.sql"
+ Kernel.expects(:system).with("isql -i #{filename} #{@database}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/oracle_rake_test.rb b/activerecord/test/cases/tasks/oracle_rake_test.rb
new file mode 100644
index 0000000000..5f840febbc
--- /dev/null
+++ b/activerecord/test/cases/tasks/oracle_rake_test.rb
@@ -0,0 +1,93 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module OracleSetupper
+ def setup
+ @database = 'db.oracle'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'oracle',
+ 'database' => @database
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::OracleDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::OracleAdapter
+ end
+ end
+
+ class OracleDBCreateTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBDropTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class OracleStructureDumpTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def setup
+ super
+ @connection.stubs(:structure_dump).returns("select sysdate from dual;")
+ end
+
+ def test_structure_dump
+ filename = "oracle.sql"
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ assert File.exists?(filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+
+ class OracleStructureLoadTest < ActiveRecord::TestCase
+ include OracleSetupper
+
+ def test_structure_load
+ filename = "oracle.sql"
+
+ open(filename, 'w') { |f| f.puts("select sysdate from dual;") }
+ @connection.stubs(:execute).with("select sysdate from dual;\n")
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ ensure
+ FileUtils.rm_f(filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 3006a87589..f31896bc7f 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -225,9 +225,9 @@ module ActiveRecord
Kernel.stubs(:system)
end
- def test_structure_dump
+ def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with("psql -f #{filename} my-app-db")
+ Kernel.expects(:system).with("psql -q -f #{filename} my-app-db")
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/cases/tasks/sqlserver_rake_test.rb b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
new file mode 100644
index 0000000000..0f1264b8ce
--- /dev/null
+++ b/activerecord/test/cases/tasks/sqlserver_rake_test.rb
@@ -0,0 +1,87 @@
+require 'cases/helper'
+
+module ActiveRecord
+ module SqlserverSetupper
+ def setup
+ @database = 'db.sqlserver'
+ @connection = stub :connection
+ @configuration = {
+ 'adapter' => 'sqlserver',
+ 'database' => @database,
+ 'host' => 'localhost',
+ 'username' => 'username',
+ 'password' => 'password',
+ }
+ ActiveRecord::Base.stubs(:connection).returns(@connection)
+ ActiveRecord::Base.stubs(:establish_connection).returns(true)
+
+ @tasks = Class.new(ActiveRecord::Tasks::SqlserverDatabaseTasks) do
+ def initialize(configuration)
+ ActiveSupport::Deprecation.silence { super }
+ end
+ end
+ ActiveRecord::Tasks::DatabaseTasks.stubs(:class_for_adapter).returns(@tasks) unless defined? ActiveRecord::ConnectionAdapters::SQLServerAdapter
+ end
+ end
+
+ class SqlserverDBCreateTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_create
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.create @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBDropTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_drop
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.drop @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverDBCharsetAndCollationTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_db_retrieves_collation
+ assert_raise NoMethodError do
+ ActiveRecord::Tasks::DatabaseTasks.collation @configuration
+ end
+ end
+
+ def test_db_retrieves_charset
+ message = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.charset @configuration
+ end
+ assert_match(/not supported/, message)
+ end
+ end
+
+ class SqlserverStructureDumpTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_dump
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("smoscript -s localhost -d #{@database} -u username -p password -f #{filename} -A -U")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+ end
+
+ class SqlserverStructureLoadTest < ActiveRecord::TestCase
+ include SqlserverSetupper
+
+ def test_structure_load
+ filename = "sqlserver.sql"
+ Kernel.expects(:system).with("sqlcmd -S localhost -d #{@database} -U username -P password -i #{filename}")
+
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
+ end
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 777a2b70dd..ff1b01556d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -176,6 +176,106 @@ class TimestampTest < ActiveRecord::TestCase
assert_not_equal time, owner.updated_at
end
+ def test_touching_a_record_touches_polymorphic_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ toy = klass.first
+ time = 3.days.ago
+ toy.update_columns(updated_at: time)
+
+ wheel = wheel_klass.new
+ wheel.wheelable = toy
+ wheel.save
+ wheel.touch
+
+ assert_not_equal time, toy.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy1 = klass.find(1)
+ old_pet = toy1.pet
+
+ toy2 = klass.find(2)
+ new_pet = toy2.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ old_pet.update_columns(updated_at: time)
+ new_pet.update_columns(updated_at: time)
+
+ toy1.pet = new_pet
+ toy1.save!
+
+ old_pet.reload
+ new_pet.reload
+
+ assert_not_equal time, new_pet.updated_at
+ assert_not_equal time, old_pet.updated_at
+ end
+
+ def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ end
+
+ wheel_klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Wheel'; end
+ belongs_to :wheelable, :polymorphic => true, :touch => true
+ end
+
+ toy1 = klass.find(1)
+ toy2 = klass.find(2)
+
+ wheel = wheel_klass.new
+ wheel.wheelable = toy1
+ wheel.save!
+
+ time = 3.days.ago.at_beginning_of_hour
+
+ toy1.update_columns(updated_at: time)
+ toy2.update_columns(updated_at: time)
+
+ wheel.wheelable = toy2
+ wheel.save!
+
+ toy1.reload
+ toy2.reload
+
+ assert_not_equal time, toy1.updated_at
+ assert_not_equal time, toy2.updated_at
+ end
+
+ def test_clearing_association_touches_the_old_record
+ klass = Class.new(ActiveRecord::Base) do
+ def self.name; 'Toy'; end
+ belongs_to :pet, touch: true
+ end
+
+ toy = klass.find(1)
+ pet = toy.pet
+ time = 3.days.ago.at_beginning_of_hour
+
+ pet.update_columns(updated_at: time)
+
+ toy.pet = nil
+ toy.save!
+
+ pet.reload
+
+ assert_not_equal time, pet.updated_at
+ end
+
def test_timestamp_attributes_for_create
toy = Toy.first
assert_equal toy.send(:timestamp_attributes_for_create), [:created_at, :created_on]
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 766a5c0c90..9485de88a6 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -281,38 +281,6 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
end
end
-
-class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- class TopicWithSaveInCallback < ActiveRecord::Base
- self.table_name = :topics
- after_commit :cache_topic, :on => :create
- after_commit :call_update, :on => :update
- attr_accessor :cached, :record_updated
-
- def call_update
- self.record_updated = true
- end
-
- def cache_topic
- unless cached
- self.cached = true
- self.save
- else
- self.cached = false
- end
- end
- end
-
- def test_after_commit_in_save
- topic = TopicWithSaveInCallback.new()
- topic.save
- assert_equal true, topic.cached
- assert_equal true, topic.record_updated
- end
-end
-
class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 29b45944aa..57457359b1 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -54,7 +54,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert !t2.save, "Shouldn't save t2 as unique"
assert_equal ["has already been taken"], t2.errors[:title]
- t2.title = "Now Im really also unique"
+ t2.title = "Now I am really also unique"
assert t2.save, "Should now save t2 as unique"
end
diff --git a/activerecord/test/fixtures/pets.yml b/activerecord/test/fixtures/pets.yml
index a1601a53f0..2ec4f53e6d 100644
--- a/activerecord/test/fixtures/pets.yml
+++ b/activerecord/test/fixtures/pets.yml
@@ -12,3 +12,8 @@ mochi:
pet_id: 3
name: mochi
owner_id: 2
+
+bulbul:
+ pet_id: 4
+ name: bulbul
+ owner_id: 1
diff --git a/activerecord/test/fixtures/toys.yml b/activerecord/test/fixtures/toys.yml
index 037e335e0a..ae9044ec62 100644
--- a/activerecord/test/fixtures/toys.yml
+++ b/activerecord/test/fixtures/toys.yml
@@ -2,3 +2,13 @@ bone:
toy_id: 1
name: Bone
pet_id: 1
+
+doll:
+ toy_id: 2
+ name: Doll
+ pet_id: 2
+
+bulbuli:
+ toy_id: 3
+ name: Bulbuli
+ pet_id: 4
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index fea55f4535..1c7ed4aa3e 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,5 +1,5 @@
class Owner < ActiveRecord::Base
self.primary_key = :owner_id
- has_many :pets
+ has_many :pets, -> { order 'pets.name desc' }
has_many :toys, :through => :pets
end
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index 3cd5bceed5..f7970d7aab 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -1,5 +1,4 @@
class Pet < ActiveRecord::Base
-
attr_accessor :current_user
self.primary_key = :pet_id
@@ -13,5 +12,4 @@ class Pet < ActiveRecord::Base
after_destroy do |record|
Pet.after_destroy_output = record.current_user
end
-
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index f25f72c481..a9a6514c9d 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -14,6 +14,16 @@ ActiveRecord::Schema.define do
add_index :binary_fields, :var_binary
+ create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ t.string :awesome
+ t.string :pizza
+ t.string :snacks
+ end
+
+ add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
+ add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
+ add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -42,7 +52,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index 5401c12ed5..f2cffca52c 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -14,6 +14,16 @@ ActiveRecord::Schema.define do
add_index :binary_fields, :var_binary
+ create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
+ t.string :awesome
+ t.string :pizza
+ t.string :snacks
+ end
+
+ add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
+ add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
+ add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'
+
ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
@@ -53,7 +63,7 @@ SQL
ActiveRecord::Base.connection.execute <<-SQL
CREATE TABLE enum_tests (
- enum_column ENUM('true','false')
+ enum_column ENUM('text','blob','tiny','medium','long')
)
SQL
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index d8271ac8d1..6b7012a172 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -32,6 +32,7 @@ ActiveRecord::Schema.define do
char3 text default 'a text field',
positive_integer integer default 1,
negative_integer integer default -1,
+ bigint_default bigint default 0::bigint,
decimal_number decimal(3,2) default 2.78,
multiline_default text DEFAULT '--- []
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 8fd4898ad6..8beb58f3fc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -183,6 +183,7 @@ ActiveRecord::Schema.define do
add_index :companies, [:firm_id, :type, :rating], :name => "company_index"
add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10"
+ add_index :companies, :name, :name => 'company_name_index', :using => :btree
create_table :vegetables, :force => true do |t|
t.string :name