diff options
author | Vijay Dev <vijaydev.cse@gmail.com> | 2014-11-30 10:28:16 +0000 |
---|---|---|
committer | Vijay Dev <vijaydev.cse@gmail.com> | 2014-11-30 10:28:16 +0000 |
commit | 079ac4a0584b24625266a0e9448503d1b96c2b7f (patch) | |
tree | 6a28edeb393cd095de252335f9e3c9cec7c78d13 /activerecord | |
parent | bd9ffa7e1fb433f359ee096a2afd924a652d1912 (diff) | |
parent | 6874133f9e5ca79f30f43632da4338d7b82ddeff (diff) | |
download | rails-079ac4a0584b24625266a0e9448503d1b96c2b7f.tar.gz rails-079ac4a0584b24625266a0e9448503d1b96c2b7f.tar.bz2 rails-079ac4a0584b24625266a0e9448503d1b96c2b7f.zip |
Merge branch 'master' of github.com:rails/rails
Diffstat (limited to 'activerecord')
163 files changed, 1114 insertions, 1883 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f7ca41908d..38c5583708 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,1273 +1 @@ -* Deprecate `sanitize_sql_hash_for_conditions` without replacement. Using a - `Relation` for performing queries and updates is the prefered API. - - *Sean Griffin* - -* Queries now properly type cast values that are part of a join statement, - even when using type decorators such as `serialize`. - - *Melanie Gilman & Sean Griffin* - -* MySQL enum type lookups, with values matching another type, no longer result - in an endless loop. - - Fixes #17402. - - *Yves Senn* - -* Raise `ArgumentError` when the body of a scope is not callable. - - *Mauro George* - -* Use type column first in multi-column indexes created with `add-reference`. - - *Derek Prior* - -* Fix `Relation.rewhere` to work with Range values. - - *Dan Olson* - -* `AR::UnknownAttributeError` now includes the class name of a record. - - User.new(name: "Yuki Nishijima", project_attributes: {name: "kaminari"}) - # => ActiveRecord::UnknownAttributeError: unknown attribute 'name' for User. - - *Yuki Nishijima* - -* Fix regression causing `after_create` callbacks to run before associated - records are autosaved. - - Fixes #17209. - - *Agis Anastasopoulos* - -* Honor overridden `rack.test` in Rack environment for the connection - management middleware. - - *Simon Eskildsen* - -* Add a truncate method to the connection. - - *Aaron Patterson* - -* Don't autosave unchanged has_one through records. - - *Alan Kennedy*, *Steve Parrington* - -* Do not dump foreign keys for ignored tables. - - *Yves Senn* - -* PostgreSQL adapter correctly dumps foreign keys targeting tables - outside the schema search path. - - Fixes #16907. - - *Matthew Draper*, *Yves Senn* - -* When a thread is killed, rollback the active transaction, instead of - committing it during the stack unwind. Previously, we could commit half- - completed work. This fix only works for Ruby 2.0+; on 1.9, we can't - distinguish a thread kill from an ordinary non-local (block) return, so must - default to committing. - - *Chris Hanks* - -* A `NullRelation` should represent nothing. This fixes a bug where - `Comment.where(post_id: Post.none)` returned a non-empty result. - - Fixes #15176. - - *Matthew Draper*, *Yves Senn* - -* Include default column limits in schema.rb. Allows defaults to be changed - in the future without affecting old migrations that assumed old defaults. - - *Jeremy Kemper* - -* MySQL: schema.rb now includes TEXT and BLOB column limits. - - *Jeremy Kemper* - -* MySQL: correct LONGTEXT and LONGBLOB limits from 2GB to their true 4GB. - - *Jeremy Kemper* - -* SQLite3Adapter now checks for views in `table_exists?`. Fixes #14041. - - *Girish Sonawane* - -* Introduce `connection.supports_views?` to check whether the current adapter - has support for SQL views. Connection adapters should define this method. - - *Yves Senn* - -* Allow included modules to override association methods. - - Fixes #16684. - - *Yves Senn* - -* Schema loading rake tasks (like `db:schema:load` and `db:setup`) maintain - the database connection to the current environment. - - Fixes #16757. - - *Joshua Cody*, *Yves Senn* - -* MySQL: set the connection collation along with the charset. - - Sets the connection collation to the database collation configured in - database.yml. Otherwise, `SET NAMES utf8mb4` will use the default - collation for that charset (utf8mb4_general_ci) when you may have chosen - a different collation, like utf8mb4_unicode_ci. - - This only applies to literal string comparisons, not column values, so it - is unlikely to affect you. - - *Jeremy Kemper* - -* `default_sequence_name` from the PostgreSQL adapter returns a `String`. - - *Yves Senn* - -* Fixed a regression where whitespaces were stripped from DISTINCT queries in - PostgreSQL. - - *Agis Anastasopoulos* - - Fixes #16623. - -* Fix has_many :through relation merging failing when dynamic conditions are - passed as a lambda with an arity of one. - - Fixes #16128. - - *Agis Anastasopoulos* - -* Fixed `Relation#exists?` to work with polymorphic associations. - - Fixes #15821. - - *Kassio Borges* - -* Currently, Active Record rescues any errors raised within - `after_rollback`/`after_create` callbacks and prints them to the logs. - Future versions of Rails will not rescue these errors anymore and - just bubble them up like the other callbacks. - - This commit adds an opt-in flag to enable not rescuing the errors. - - Example: - - # Do not swallow errors in after_commit/after_rollback callbacks. - config.active_record.raise_in_transactional_callbacks = true - - Fixes #13460. - - *arthurnn* - -* Fixed an issue where custom accessor methods (such as those generated by - `enum`) with the same name as a global method are incorrectly overridden - when subclassing. - - Fixes #16288. - - *Godfrey Chan* - -* `*_was` and `changes` now work correctly for in-place attribute changes as - well. - - *Sean Griffin* - -* Fix regression on `after_commit` that did not fire with nested transactions. - - Fixes #16425. - - *arthurnn* - -* Do not try to write timestamps when a table has no timestamps columns. - - Fixes #8813. - - *Sergey Potapov* - -* `index_exists?` with `:name` option does verify specified columns. - - Example: - - add_index :articles, :title, name: "idx_title" - - # Before: - index_exists? :articles, :title, name: "idx_title" # => `true` - index_exists? :articles, :body, name: "idx_title" # => `true` - - # After: - index_exists? :articles, :title, name: "idx_title" # => `true` - index_exists? :articles, :body, name: "idx_title" # => `false` - - *Yves Senn*, *Matthew Draper* - -* When calling `update_columns` on a record that is not persisted, the error - message now reflects whether that object is a new record or has been - destroyed. - - *Lachlan Sylvester* - -* Define `id_was` to get the previous value of the primary key. - - Currently when we call `id_was` and we have a custom primary key name, - Active Record will return the current value of the primary key. This - makes it impossible to correctly do an update operation if you change the - id. - - Fixes #16413. - - *Rafael Mendonça França* - -* Deprecate `DatabaseTasks.load_schema` to act on the current connection. - Use `.load_schema_current` instead. In the future `load_schema` will - require the `configuration` to act on as an argument. - - *Yves Senn* - -* Fixed automatic maintaining test schema to properly handle sql structure - schema format. - - Fixes #15394. - - *Wojciech Wnętrzak* - -* Fix type casting to Decimal from Float with large precision. - - *Tomohiro Hashidate* - -* Deprecate `Reflection#source_macro` - - `Reflection#source_macro` is no longer needed in Active Record - source so it has been deprecated. Code that used `source_macro` - was removed in #16353. - - *Eileen M. Uchtitelle*, *Aaron Patterson* - -* No verbose backtrace by `db:drop` when database does not exist. - - Fixes #16295. - - *Kenn Ejima* - -* Add support for PostgreSQL JSONB. - - Example: - - create_table :posts do |t| - t.jsonb :meta_data - end - - *Philippe Creux*, *Chris Teague* - -* `db:purge` with MySQL respects `Rails.env`. - - *Yves Senn* - -* `change_column_default :table, :column, nil` with PostgreSQL will issue a - `DROP DEFAULT` instead of a `DEFAULT NULL` query. - - Fixes #16261. - - *Matthew Draper*, *Yves Senn* - -* Allow to specify a type for the foreign key column in `references` - and `add_reference`. - - Example: - - change_table :vehicle do |t| - t.references :station, type: :uuid - end - - *Andrey Novikov*, *Łukasz Sarnacki* - -* `create_join_table` removes a common prefix when generating the join table. - This matches the existing behavior of HABTM associations. - - Fixes #13683. - - *Stefan Kanev* - -* Do not swallow errors on `compute_type` when having a bad `alias_method` on - a class. - - *arthurnn* - -* PostgreSQL invalid `uuid` are convert to nil. - - *Abdelkader Boudih* - -* Restore 4.0 behavior for using serialize attributes with `JSON` as coder. - - With 4.1.x, `serialize` started returning a string when `JSON` was passed as - the second attribute. It will now return a hash as per previous versions. - - Example: - - class Post < ActiveRecord::Base - serialize :comment, JSON - end - - class Comment - include ActiveModel::Model - attr_accessor :category, :text - end - - post = Post.create! - post.comment = Comment.new(category: "Animals", text: "This is a comment about squirrels.") - post.save! - - # 4.0 - post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."} - - # 4.1 before - post.comment # => "#<Comment:0x007f80ab48ff98>" - - # 4.1 after - post.comment # => {"category"=>"Animals", "text"=>"This is a comment about squirrels."} - - When using `JSON` as the coder in `serialize`, Active Record will use the - new `ActiveRecord::Coders::JSON` coder which delegates its `dump/load` to - `ActiveSupport::JSON.encode/decode`. This ensures special objects are dumped - correctly using the `#as_json` hook. - - To keep the previous behaviour, supply a custom coder instead - ([example](https://gist.github.com/jenncoop/8c4142bbe59da77daa63)). - - Fixes #15594. - - *Jenn Cooper* - -* Do not use `RENAME INDEX` syntax for MariaDB 10.0. - - Fixes #15931. - - *Jeff Browning* - -* Calling `#empty?` on a `has_many` association would use the value from the - counter cache if one exists. - - *David Verhasselt* - -* Fix the schema dump generated for tables without constraints and with - primary key with default value of custom PostgreSQL function result. - - Fixes #16111. - - *Andrey Novikov* - -* Fix the SQL generated when a `delete_all` is run on an association to not - produce an `IN` statements. - - Before: - - UPDATE "categorizations" SET "category_id" = NULL WHERE - "categorizations"."category_id" = 1 AND "categorizations"."id" IN (1, 2) - - After: - - UPDATE "categorizations" SET "category_id" = NULL WHERE - "categorizations"."category_id" = 1 - - *Eileen M. Uchitelle, Aaron Patterson* - -* Avoid type casting boolean and `ActiveSupport::Duration` values to numeric - values for string columns. Otherwise, in some database, the string column - values will be coerced to a numeric allowing false or 0.seconds match any - string starting with a non-digit. - - Example: - - App.where(apikey: false) # => SELECT * FROM users WHERE apikey = '0' - - *Dylan Thacker-Smith* - -* Add a `:required` option to singular associations, providing a nicer - API for presence validations on associations. - - *Sean Griffin* - -* Fixed error in `reset_counters` when associations have `select` scope. - (Call to `count` generates invalid SQL.) - - *Cade Truitt* - -* After a successful `reload`, `new_record?` is always false. - - Fixes #12101. - - *Matthew Draper* - -* PostgreSQL renaming table doesn't attempt to rename non existent sequences. - - *Abdelkader Boudih* - -* Move 'dependent: :destroy' handling for `belongs_to` - from `before_destroy` to `after_destroy` callback chain - - Fixes #12380. - - *Ivan Antropov* - -* Detect in-place modifications on String attributes. - - Before this change, an attribute modified in-place had to be marked as - changed in order for it to be persisted in the database. Now it is no longer - required. - - Before: - - user = User.first - user.name << ' Griffin' - user.name_will_change! - user.save - user.reload.name # => "Sean Griffin" - - After: - - user = User.first - user.name << ' Griffin' - user.save - user.reload.name # => "Sean Griffin" - - *Sean Griffin* - -* Add `ActiveRecord::Base#validate!` that raises `RecordInvalid` if the record - is invalid. - - *Bogdan Gusiev*, *Marc Schütz* - -* Support for adding and removing foreign keys. Foreign keys are now - a part of `schema.rb`. This is supported by Mysql2Adapter, MysqlAdapter - and PostgreSQLAdapter. - - Many thanks to *Matthew Higgins* for laying the foundation with his work on - [foreigner](https://github.com/matthuhiggins/foreigner). - - Example: - - # within your migrations: - add_foreign_key :articles, :authors - remove_foreign_key :articles, :authors - - *Yves Senn* - -* Fix subtle bugs regarding attribute assignment on models with no primary - key. `'id'` will no longer be part of the attributes hash. - - *Sean Griffin* - -* Deprecate automatic counter caches on `has_many :through`. The behavior was - broken and inconsistent. - - *Sean Griffin* - -* `preload` preserves readonly flag for associations. - - See #15853. - - *Yves Senn* - -* Assume numeric types have changed if they were assigned to a value that - would fail numericality validation, regardless of the old value. Previously - this would only occur if the old value was 0. - - Example: - - model = Model.create!(number: 5) - model.number = '5wibble' - model.number_changed? # => true - - Fixes #14731. - - *Sean Griffin* - -* `reload` no longer merges with the existing attributes. - The attribute hash is fully replaced. The record is put into the same state - as it would be with `Model.find(model.id)`. - - *Sean Griffin* - -* The object returned from `select_all` must respond to `column_types`. - If this is not the case a `NoMethodError` is raised. - - *Sean Griffin* - -* Detect in-place modifications of PG array types - - *Sean Griffin* - -* Add `bin/rake db:purge` task to empty the current database. - - *Yves Senn* - -* Deprecate `serialized_attributes` without replacement. - - *Sean Griffin* - -* Correctly extract IPv6 addresses from `DATABASE_URI`: the square brackets - are part of the URI structure, not the actual host. - - Fixes #15705. - - *Andy Bakun*, *Aaron Stone* - -* Ensure both parent IDs are set on join records when both sides of a - through association are new. - - *Sean Griffin* - -* `ActiveRecord::Dirty` now detects in-place changes to mutable values. - Serialized attributes on ActiveRecord models will no longer save when - unchanged. - - Fixes #8328. - - *Sean Griffin* - -* `Pluck` now works when selecting columns from different tables with the same - name. - - Fixes #15649. - - *Sean Griffin* - -* Remove `cache_attributes` and friends. All attributes are cached. - - *Sean Griffin* - -* Remove deprecated method `ActiveRecord::Base.quoted_locking_column`. - - *Akshay Vishnoi* - -* `ActiveRecord::FinderMethods.find` with block can handle proc parameter as - `Enumerable#find` does. - - Fixes #15382. - - *James Yang* - -* Make timezone aware attributes work with PostgreSQL array columns. - - Fixes #13402. - - *Kuldeep Aggarwal*, *Sean Griffin* - -* `ActiveRecord::SchemaMigration` has no primary key regardless of the - `primary_key_prefix_type` configuration. - - Fixes #15051. - - *JoseLuis Torres*, *Yves Senn* - -* `rake db:migrate:status` works with legacy migration numbers like `00018_xyz.rb`. - - Fixes #15538. - - *Yves Senn* - -* Baseclass becomes! subclass. - - Before this change, a record which changed its STI type, could not be - updated. - - Fixes #14785. - - *Matthew Draper*, *Earl St Sauver*, *Edo Balvers* - -* Remove deprecated `ActiveRecord::Migrator.proper_table_name`. Use the - `proper_table_name` instance method on `ActiveRecord::Migration` instead. - - *Akshay Vishnoi* - -* Fix regression on eager loading association based on SQL query rather than - existing column. - - Fixes #15480. - - *Lauro Caetano*, *Carlos Antonio da Silva* - -* Deprecate returning `nil` from `column_for_attribute` when no column exists. - It will return a null object in Rails 5.0 - - *Sean Griffin* - -* Implemented `ActiveRecord::Base#pretty_print` to work with PP. - - *Ethan* - -* Preserve type when dumping PostgreSQL point, bit, bit varying and money - columns. - - *Yves Senn* - -* New records remain new after YAML serialization. - - *Sean Griffin* - -* PostgreSQL support default values for enum types. Fixes #7814. - - *Yves Senn* - -* PostgreSQL `default_sequence_name` respects schema. Fixes #7516. - - *Yves Senn* - -* Fixed `columns_for_distinct` of postgresql adapter to work correctly - with orders without sort direction modifiers. - - *Nikolay Kondratyev* - -* PostgreSQL `reset_pk_sequence!` respects schemas. Fixes #14719. - - *Yves Senn* - -* Keep PostgreSQL `hstore` and `json` attributes as `Hash` in `@attributes`. - Fixes duplication in combination with `store_accessor`. - - Fixes #15369. - - *Yves Senn* - -* `rake railties:install:migrations` respects the order of railties. - - *Arun Agrawal* - -* Fix redefine a `has_and_belongs_to_many` inside inherited class - Fixing regression case, where redefining the same `has_and_belongs_to_many` - definition into a subclass would raise. - - Fixes #14983. - - *arthurnn* - -* Fix `has_and_belongs_to_many` public reflection. - When defining a `has_and_belongs_to_many`, internally we convert that to two has_many. - But as `reflections` is a public API, people expect to see the right macro. - - Fixes #14682. - - *arthurnn* - -* Fixed serialization for records with an attribute named `format`. - - Fixes #15188. - - *Godfrey Chan* - -* When a `group` is set, `sum`, `size`, `average`, `minimum` and `maximum` - on a NullRelation should return a Hash. - - *Kuldeep Aggarwal* - -* Fixed serialized fields returning serialized data after being updated with - `update_column`. - - *Simon Hørup Eskildsen* - -* Fixed polymorphic eager loading when using a String as foreign key. - - Fixes #14734. - - *Lauro Caetano* - -* Change belongs_to touch to be consistent with timestamp updates - - If a model is set up with a belongs_to: touch relationship the parent - record will only be touched if the record was modified. This makes it - consistent with timestamp updating on the record itself. - - *Brock Trappitt* - -* Fixed the inferred table name of a `has_and_belongs_to_many` auxiliar - table inside a schema. - - Fixes #14824. - - *Eric Chahin* - -* Remove unused `:timestamp` type. Transparently alias it to `:datetime` - in all cases. Fixes inconsistencies when column types are sent outside of - `ActiveRecord`, such as for XML Serialization. - - *Sean Griffin* - -* Fix bug that added `table_name_prefix` and `table_name_suffix` to - extension names in PostgreSQL when migrating. - - *Joao Carlos* - -* The `:index` option in migrations, which previously was only available for - `references`, now works with any column types. - - *Marc Schütz* - -* Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`. - - *jnormore* - -* Restrict deletion of record when using `delete_all` with `uniq`, `group`, `having` - or `offset`. - - In these cases the generated query ignored them and that caused unintended - records to be deleted. - - Fixes #11985. - - *Leandro Facchinetti* - -* Floats with limit >= 25 that get turned into doubles in MySQL no longer have - their limit dropped from the schema. - - Fixes #14135. - - *Aaron Nelson* - -* Fix how to calculate associated class name when using namespaced `has_and_belongs_to_many` - association. - - Fixes #14709. - - *Kassio Borges* - -* `ActiveRecord::Relation::Merger#filter_binds` now compares equivalent symbols and - strings in column names as equal. - - This fixes a rare case in which more bind values are passed than there are - placeholders for them in the generated SQL statement, which can make PostgreSQL - throw a `StatementInvalid` exception. - - *Nat Budin* - -* Fix `stored_attributes` to correctly merge the details of stored - attributes defined in parent classes. - - Fixes #14672. - - *Brad Bennett*, *Jessica Yao*, *Lakshmi Parthasarathy* - -* `change_column_default` allows `[]` as argument to `change_column_default`. - - Fixes #11586. - - *Yves Senn* - -* Handle `name` and `"char"` column types in the PostgreSQL adapter. - - `name` and `"char"` are special character types used internally by - PostgreSQL and are used by internal system catalogs. These field types - can sometimes show up in structure-sniffing queries that feature internal system - structures or with certain PostgreSQL extensions. - - *J Smith*, *Yves Senn* - -* Fix `PostgreSQLAdapter::OID::Float#type_cast` to convert Infinity and - NaN PostgreSQL values into a native Ruby `Float::INFINITY` and `Float::NAN` - - Before: - - Point.create(value: 1.0/0) - Point.last.value # => 0.0 - - After: - - Point.create(value: 1.0/0) - Point.last.value # => Infinity - - *Innokenty Mikhailov* - -* Allow the PostgreSQL adapter to handle bigserial primary key types again. - - Fixes #10410. - - *Patrick Robertson* - -* Deprecate joining, eager loading and preloading of instance dependent - associations without replacement. These operations happen before instances - are created. The current behavior is unexpected and can result in broken - behavior. - - Fixes #15024. - - *Yves Senn* - -* Fixed has_and_belongs_to_many's CollectionAssociation size calculation. - - `has_and_belongs_to_many` should fall back to using the normal CollectionAssociation's - size calculation if the collection is not cached or loaded. - - Fixes #14913, #14914. - - *Fred Wu* - -* Return a non zero status when running `rake db:migrate:status` and migration table does - not exist. - - *Paul B.* - -* Add support for module-level `table_name_suffix` in models. - - This makes `table_name_suffix` work the same way as `table_name_prefix` when - using namespaced models. - - *Jenner LaFave* - -* Revert the behaviour of `ActiveRecord::Relation#join` changed through 4.0 => 4.1 to 4.0. - - In 4.1.0 `Relation#join` is delegated to `Arel#SelectManager`. - In 4.0 series it is delegated to `Array#join`. - - *Bogdan Gusiev* - -* Log nil binary column values correctly. - - When an object with a binary column is updated with a nil value - in that column, the SQL logger would throw an exception when trying - to log that nil value. This only occurs when updating a record - that already has a non-nil value in that column since an initial nil - value isn't included in the SQL anyway (at least, when dirty checking - is enabled.) The column's new value will now be logged as `<NULL binary data>` - to parallel the existing `<N bytes of binary data>` for non-nil values. - - *James Coleman* - -* Rails will now pass a custom validation context through to autosave associations - in order to validate child associations with the same context. - - Fixes #13854. - - *Eric Chahin*, *Aaron Nelson*, *Kevin Casey* - -* Stringify all variables keys of MySQL connection configuration. - - When `sql_mode` variable for MySQL adapters set in configuration as `String` - was ignored and overwritten by strict mode option. - - Fixes #14895. - - *Paul Nikitochkin* - -* Ensure SQLite3 statements are closed on errors. - - Fixes #13631. - - *Timur Alperovich* - -* Give `ActiveRecord::PredicateBuilder` private methods the privacy they deserve. - - *Hector Satre* - -* When using a custom `join_table` name on a `habtm`, rails was not saving it - on Reflections. This causes a problem when rails loads fixtures, because it - uses the reflections to set database with fixtures. - - Fixes #14845. - - *Kassio Borges* - -* Reset the cache when modifying a Relation with cached Arel. - Additionally display a warning message to make the user aware. - - *Yves Senn* - -* PostgreSQL should internally use `:datetime` consistently for TimeStamp. Assures - different spellings of timestamps are treated the same. - - Example: - - mytimestamp.simplified_type('timestamp without time zone') - # => :datetime - mytimestamp.simplified_type('timestamp(6) without time zone') - # => also :datetime (previously would be :timestamp) - - See #14513. - - *Jefferson Lai* - -* `ActiveRecord::Base.no_touching` no longer triggers callbacks or start empty transactions. - - Fixes #14841. - - *Lucas Mazza* - -* Fix name collision with `Array#select!` with `Relation#select!`. - - Fixes #14752. - - *Earl St Sauver* - -* Fixed unexpected behavior for `has_many :through` associations going through a scoped `has_many`. - - If a `has_many` association is adjusted using a scope, and another `has_many :through` - uses this association, then the scope adjustment is unexpectedly neglected. - - Fixes #14537. - - *Jan Habermann* - -* `@destroyed` should always be set to `false` when an object is duped. - - *Kuldeep Aggarwal* - -* Fixed `has_many` association to make it support irregular inflections. - - Fixes #8928. - - *arthurnn*, *Javier Goizueta* - -* Fixed a problem where count used with a grouping was not returning a Hash. - - Fixes #14721. - - *Eric Chahin* - -* `sanitize_sql_like` helper method to escape a string for safe use in an SQL - LIKE statement. - - Example: - - class Article - def self.search(term) - where("title LIKE ?", sanitize_sql_like(term)) - end - end - - Article.search("20% _reduction_") - # => Query looks like "... title LIKE '20\% \_reduction\_' ..." - - *Rob Gilson*, *Yves Senn* - -* Do not quote uuid default value on `change_column`. - - Fixes #14604. - - *Eric Chahin* - -* The comparison between `Relation` and `CollectionProxy` should be consistent. - - Example: - - author.posts == Post.where(author_id: author.id) - # => true - Post.where(author_id: author.id) == author.posts - # => true - - Fixes #13506. - - *Lauro Caetano* - -* Calling `delete_all` on an unloaded `CollectionProxy` no longer - generates an SQL statement containing each id of the collection: - - Before: - - DELETE FROM `model` WHERE `model`.`parent_id` = 1 - AND `model`.`id` IN (1, 2, 3...) - - After: - - DELETE FROM `model` WHERE `model`.`parent_id` = 1 - - *Eileen M. Uchitelle*, *Aaron Patterson* - -* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select` - which created invalid SQL. - - Fixes #13648. - - *Simon Woker* - -* PostgreSQL adapter only warns once for every missing OID per connection. - - Fixes #14275. - - *Matthew Draper*, *Yves Senn* - -* PostgreSQL adapter automatically reloads it's type map when encountering - unknown OIDs. - - Fixes #14678. - - *Matthew Draper*, *Yves Senn* - -* Fix insertion of records via `has_many :through` association with scope. - - Fixes #3548. - - *Ivan Antropov* - -* Auto-generate stable fixture UUIDs on PostgreSQL. - - Fixes #11524. - - *Roderick van Domburg* - -* Fixed a problem where an enum would overwrite values of another enum - with the same name in an unrelated class. - - Fixes #14607. - - *Evan Whalen* - -* PostgreSQL and SQLite string columns no longer have a default limit of 255. - - Fixes #13435, #9153. - - *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn* - -* Make possible to have an association called `records`. - - Fixes #11645. - - *prathamesh-sonpatki* - -* `to_sql` on an association now matches the query that is actually executed, where it - could previously have incorrectly accrued additional conditions (e.g. as a result of - a previous query). `CollectionProxy` now always defers to the association scope's - `arel` method so the (incorrect) inherited one should be entirely concealed. - - Fixes #14003. - - *Jefferson Lai* - -* Block a few default Class methods as scope name. - - For instance, this will raise: - - scope :public, -> { where(status: 1) } - - *arthurnn* - -* Fixed error when using `with_options` with lambda. - - Fixes #9805. - - *Lauro Caetano* - -* Switch `sqlite3:///` URLs (which were temporarily - deprecated in 4.1) from relative to absolute. - - If you still want the previous interpretation, you should replace - `sqlite3:///my/path` with `sqlite3:my/path`. - - *Matthew Draper* - -* Treat blank UUID values as `nil`. - - Example: - - Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil> - - *Dmitry Lavrov* - -* Enable support for materialized views on PostgreSQL >= 9.3. - - *Dave Lee* - -* The PostgreSQL adapter supports custom domains. Fixes #14305. - - *Yves Senn* - -* PostgreSQL `Column#type` is now determined through the corresponding OID. - The column types stay the same except for enum columns. They no longer have - `nil` as type but `enum`. - - See #7814. - - *Yves Senn* - -* Fixed error when specifying a non-empty default value on a PostgreSQL array column. - - Fixes #10613. - - *Luke Steensen* - -* Fixed error where .persisted? throws SystemStackError for an unsaved model with a - custom primary key that didn't save due to validation error. - - Fixes #14393. - - *Chris Finne* - -* Introduce `validate` as an alias for `valid?`. - - This is more intuitive when you want to run validations but don't care about the return value. - - *Henrik Nyh* - -* Create indexes inline in CREATE TABLE for MySQL. - - This is important, because adding an index on a temporary table after it has been created - would commit the transaction. - - It also allows creating and dropping indexed tables with fewer queries and fewer permissions - required. - - Example: - - create_table :temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query" do |t| - t.index :zip - end - # => CREATE TEMPORARY TABLE temp (INDEX (zip)) AS SELECT id, name, zip FROM a_really_complicated_query - - *Cody Cutrer*, *Steve Rice*, *Rafael Mendonça Franca* - -* Use singular table name in generated migrations when - `ActiveRecord::Base.pluralize_table_names` is `false`. - - Fixes #13426. - - *Kuldeep Aggarwal* - -* `touch` accepts many attributes to be touched at once. - - Example: - - # touches :signed_at, :sealed_at, and :updated_at/on attributes. - Photo.last.touch(:signed_at, :sealed_at) - - *James Pinto* - -* `rake db:structure:dump` only dumps schema information if the schema - migration table exists. - - Fixes #14217. - - *Yves Senn* - -* Reap connections that were checked out by now-dead threads, instead - of waiting until they disconnect by themselves. Before this change, - a suitably constructed series of short-lived threads could starve - the connection pool, without ever having more than a couple alive at - the same time. - - *Matthew Draper* - -* `pk_and_sequence_for` now ensures that only the pg_depend entries - pointing to pg_class, and thus only sequence objects, are considered. - - *Josh Williams* - -* `where.not` adds `references` for `includes` like normal `where` calls do. - - Fixes #14406. - - *Yves Senn* - -* Extend fixture `$LABEL` replacement to allow string interpolation. - - Example: - - martin: - email: $LABEL@email.com - - users(:martin).email # => martin@email.com - - *Eric Steele* - -* Add support for `Relation` be passed as parameter on `QueryCache#select_all`. - - Fixes #14361. - - *arthurnn* - -* Passing an Active Record object to `find` or `exists?` is now deprecated. - Call `.id` on the object first. - - *Aaron Patterson* - -* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation. - - *Ryuta Kamizono* - -* Support for MySQL 5.6 fractional seconds. - - *arthurnn*, *Tatsuhiko Miyagawa* - -* Support for Postgres `citext` data type enabling case-insensitive where - values without needing to wrap in UPPER/LOWER sql functions. - - *Troy Kruthoff*, *Lachlan Sylvester* - -* Only save has_one associations if record has changes. - Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one - object did not get saved to the db. - - *Alan Kennedy* - -* Allow strings to specify the `#order` value. - - Example: - - Model.order(id: 'asc').to_sql == Model.order(id: :asc).to_sql - - *Marcelo Casiraghi*, *Robin Dupret* - -* Dynamically register PostgreSQL enum OIDs. This prevents "unknown OID" - warnings on enum columns. - - *Dieter Komendera* - -* `includes` is able to detect the right preloading strategy when string - joins are involved. - - Fixes #14109. - - *Aaron Patterson*, *Yves Senn* - -* Fixed error with validation with enum fields for records where the - value for any enum attribute is always evaluated as 0 during - uniqueness validation. - - Fixes #14172. - - *Vilius Luneckas* *Ahmed AbouElhamayed* - -* `before_add` callbacks are fired before the record is saved on - `has_and_belongs_to_many` associations *and* on `has_many :through` - associations. Before this change, `before_add` callbacks would be fired - before the record was saved on `has_and_belongs_to_many` associations, but - *not* on `has_many :through` associations. - - Fixes #14144. - -* Fixed STI classes not defining an attribute method if there is a - conflicting private method defined on its ancestors. - - Fixes #11569. - - *Godfrey Chan* - -* Coerce strings when reading attributes. Fixes #10485. - - Example: - - book = Book.new(title: 12345) - book.save! - book.title # => "12345" - - *Yves Senn* - -* Deprecate half-baked support for PostgreSQL range values with excluding beginnings. - We currently map PostgreSQL ranges to Ruby ranges. This conversion is not fully - possible because the Ruby range does not support excluded beginnings. - - The current solution of incrementing the beginning is not correct and is now - deprecated. For subtypes where we don't know how to increment (e.g. `#succ` - is not defined) it will raise an `ArgumentException` for ranges with excluding - beginnings. - - *Yves Senn* - -* Support for user created range types in PostgreSQL. - - *Yves Senn* - -Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activerecord/CHANGELOG.md) for previous changes. +Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index bc10e96244..471769a962 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'Object-relational mapper framework (part of Rails).' s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.1.0' s.license = 'MIT' @@ -24,5 +24,5 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '>= 6.0.0.beta2', '< 6.1' + s.add_dependency 'arel', '~> 6.0' end diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb index d3546ce948..a5a1f284a0 100644 --- a/activerecord/examples/performance.rb +++ b/activerecord/examples/performance.rb @@ -39,8 +39,8 @@ class Exhibit < ActiveRecord::Base where("notes IS NOT NULL") end - def self.look(exhibits) exhibits.each { |e| e.look } end - def self.feel(exhibits) exhibits.each { |e| e.feel } end + def self.look(exhibits) exhibits.each(&:look) end + def self.feel(exhibits) exhibits.each(&:feel) end end def progress_bar(int); print "." if (int%100).zero? ; end diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index e576ec4d40..1040e6e3bb 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -230,8 +230,8 @@ module ActiveRecord private def reader_method(name, class_name, mapping, allow_nil, constructor) define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? }) - attrs = mapping.collect {|key, _| read_attribute(key)} + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !_read_attribute(key).nil? }) + attrs = mapping.collect {|key, _| _read_attribute(key)} object = constructor.respond_to?(:call) ? constructor.call(*attrs) : class_name.constantize.send(constructor, *attrs) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8911506694..eb21712f96 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -57,7 +57,7 @@ module ActiveRecord through_reflection = reflection.through_reflection source_reflection_names = reflection.source_reflection_names source_associations = reflection.through_reflection.klass._reflections.keys - super("Could not find the source association(s) #{source_reflection_names.collect{ |a| a.inspect }.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?") end end @@ -1700,7 +1700,7 @@ module ActiveRecord hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name - [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table].each do |k| + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name].each do |k| hm_options[k] = options[k] if options.key? k end diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index a6a1947148..0c3234ed24 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -57,20 +57,10 @@ module ActiveRecord end def aliased_table_for(table_name, aliased_name) - table_alias = aliased_name_for(table_name, aliased_name) - - if table_alias == table_name - Arel::Table.new(table_name) - else - Arel::Table.new(table_name).alias(table_alias) - end - end - - def aliased_name_for(table_name, aliased_name) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 - table_name + Arel::Table.new(table_name) else # Otherwise, we need to use an alias aliased_name = connection.table_alias_for(aliased_name) @@ -78,11 +68,12 @@ module ActiveRecord # Update the count aliases[aliased_name] += 1 - if aliases[aliased_name] > 1 + table_alias = if aliases[aliased_name] > 1 "#{truncate(aliased_name)}_#{aliases[aliased_name]}" else aliased_name end + Arel::Table.new(table_name).alias(table_alias) end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index b965230e60..dcbd57e61d 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -11,8 +11,7 @@ module ActiveRecord end def bind_value(scope, column, value, alias_tracker) - substitute = alias_tracker.connection.substitute_at( - column, scope.bind_values.length) + substitute = alias_tracker.connection.substitute_at(column) scope.bind_values += [[column, @block.call(value)]] substitute end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 815e8eb97f..092b4ebd2f 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -98,7 +98,7 @@ module ActiveRecord::Associations::Builder def middle_options(join_model) middle_options = {} - middle_options[:class] = join_model + middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" middle_options[:source] = join_model.left_reflection.name if options.key? :foreign_key middle_options[:foreign_key] = options[:foreign_key] @@ -110,7 +110,7 @@ module ActiveRecord::Associations::Builder rhs_options = {} if options.key? :class_name - rhs_options[:foreign_key] = options[:class_name].foreign_key + rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key rhs_options[:class_name] = options[:class_name] end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index bdfd569be2..7b6aefe345 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -33,7 +33,13 @@ module ActiveRecord reload end - @proxy ||= CollectionProxy.create(klass, self) + if owner.new_record? + # Cache the proxy separately before the owner has an id + # or else a post-save proxy will still lack the id + @new_record_proxy ||= CollectionProxy.create(klass, self) + else + @proxy ||= CollectionProxy.create(klass, self) + end end # Implements the writer method, e.g. foo.items= for Foo.has_many :items @@ -56,9 +62,9 @@ module ActiveRecord # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items def ids_writer(ids) pk_type = reflection.primary_key_type - ids = Array(ids).reject { |id| id.blank? } + ids = Array(ids).reject(&:blank?) ids.map! { |i| pk_type.type_cast_from_user(i) } - replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids)) + replace(klass.find(ids).index_by(&:id).values_at(*ids)) end def reset @@ -169,8 +175,8 @@ module ActiveRecord end # Removes all records from the association without calling callbacks - # on the associated records. It honors the `:dependent` option. However - # if the `:dependent` value is `:destroy` then in that case the `:delete_all` + # on the associated records. It honors the +:dependent+ option. However + # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+ # deletion strategy for the association is applied. # # You can force a particular deletion strategy by passing a parameter. @@ -283,7 +289,7 @@ module ActiveRecord elsif !loaded? && !association_scope.group_values.empty? load_target.size elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array) - unsaved_records = target.select { |r| r.new_record? } + unsaved_records = target.select(&:new_record?) unsaved_records.size + count_records else count_records @@ -352,6 +358,7 @@ module ActiveRecord if owner.new_record? replace_records(other_array, original_target) else + replace_common_records_in_memory(other_array, original_target) if other_array != original_target transaction { replace_records(other_array, original_target) } end @@ -379,11 +386,18 @@ module ActiveRecord target end - def add_to_target(record, skip_callbacks = false) + def add_to_target(record, skip_callbacks = false, &block) + if association_scope.distinct_value + index = @target.index(record) + end + replace_on_target(record, index, skip_callbacks, &block) + end + + def replace_on_target(record, index, skip_callbacks) callback(:before_add, record) unless skip_callbacks yield(record) if block_given? - if association_scope.distinct_value && index = @target.index(record) + if index @target[index] = record else @target << record @@ -409,7 +423,8 @@ module ActiveRecord def get_records if reflection.scope_chain.any?(&:any?) || scope.eager_loading? || - klass.current_scope + klass.current_scope || + klass.default_scopes.any? return scope.to_a end @@ -491,7 +506,7 @@ module ActiveRecord def delete_or_destroy(records, method) records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } - existing_records = records.reject { |r| r.new_record? } + existing_records = records.reject(&:new_record?) if existing_records.empty? remove_records(existing_records, records, method) @@ -527,6 +542,14 @@ module ActiveRecord target end + def replace_common_records_in_memory(new_target, original_target) + common_records = new_target & original_target + common_records.each do |record| + skip_callbacks = true + replace_on_target(record, @target.index(record), skip_callbacks) + end + end + def concat_records(records, should_raise = false) result = true @@ -586,7 +609,7 @@ module ActiveRecord # specified, then #find scans the entire collection. def find_by_scan(*args) expects_array = args.first.kind_of?(Array) - ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq + ids = args.flatten.compact.map(&:to_s).uniq if ids.size == 1 id = ids.first diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 1413efaf7f..93084e0dcf 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -66,7 +66,7 @@ module ActiveRecord # the loaded flag is set to true as well. def count_records count = if has_cached_counter? - owner.read_attribute cached_counter_attribute_name + owner._read_attribute cached_counter_attribute_name else scope.count end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index bde23fc116..3f4d3bfc08 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -20,7 +20,7 @@ module ActiveRecord # SELECT query will be generated by using #length instead. def size if has_cached_counter? - owner.read_attribute cached_counter_attribute_name(reflection) + owner._read_attribute cached_counter_attribute_name(reflection) elsif loaded? target.size else @@ -161,13 +161,11 @@ module ActiveRecord if scope.klass.primary_key count = scope.destroy_all.length else - scope.each do |record| - record._run_destroy_callbacks - end + scope.each(&:_run_destroy_callbacks) arel = scope.arel - stmt = Arel::DeleteManager.new arel.engine + stmt = Arel::DeleteManager.new stmt.from scope.klass.arel_table stmt.wheres = arel.constraints diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index c5c4edd090..cf63430a97 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -20,7 +20,7 @@ module ActiveRecord end def columns - @tables.flat_map { |t| t.column_aliases } + @tables.flat_map(&:column_aliases) end # An array of [column_name, alias] pairs for the table @@ -94,7 +94,7 @@ module ActiveRecord # def initialize(base, associations, joins) @alias_tracker = AliasTracker.create(base.connection, joins) - @alias_tracker.aliased_name_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 + @alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1 tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } 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 e7d3c9ba40..5dede5527d 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -67,7 +67,7 @@ module ActiveRecord value = foreign_klass.base_class.name column = klass.columns_hash[reflection.type.to_s] - substitute = klass.connection.substitute_at(column, bind_values.length) + substitute = klass.connection.substitute_at(column) bind_values.push [column, value] constraint = constraint.and table[reflection.type].eq substitute end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 46bccbf15a..4358f3b581 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -178,6 +178,7 @@ module ActiveRecord class NullPreloader def self.new(klass, owners, reflection, preload_scope); self; end def self.run(preloader); end + def self.preloaded_records; []; end end def preloader_for(reflection, owners, rhs_klass) diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index c0639742be..7d6523dbc4 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -142,14 +142,8 @@ module ActiveRecord scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] - - if preload_values.key? :order - scope.order! preload_values[:order] - else - if values.key? :order - scope.order! values[:order] - end - end + scope.joins! preload_values[:joins] || values[:joins] + scope.order! preload_values[:order] || values[:order] if preload_values[:readonly] || values[:readonly] scope.readonly! @@ -159,6 +153,7 @@ module ActiveRecord scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) end + scope.unscope_values = Array(values[:unscope]) klass.default_scoped.merge(scope) 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 7b37b5942d..2029871f39 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb @@ -8,7 +8,7 @@ module ActiveRecord records_by_owner = super if reflection_scope.distinct_value - records_by_owner.each_value { |records| records.uniq! } + records_by_owner.each_value(&:uniq!) end records_by_owner diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index c360ef1b2c..c44242a0f0 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -41,7 +41,8 @@ module ActiveRecord def get_records if reflection.scope_chain.any?(&:any?) || scope.eager_loading? || - klass.current_scope + klass.current_scope || + klass.default_scopes.any? return scope.limit(1).to_a end diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 2887db3bf7..bf64830417 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -87,7 +87,7 @@ module ActiveRecord end end unless errors.empty? - error_descriptions = errors.map { |ex| ex.message }.join(",") + error_descriptions = errors.map(&:message).join(",") raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 21ddbbcb66..83fcefa64d 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -332,7 +332,7 @@ module ActiveRecord # task.attribute_present?(:title) # => true # task.attribute_present?(:is_done) # => true def attribute_present?(attribute) - value = read_attribute(attribute) + value = _read_attribute(attribute) !value.nil? && !(value.respond_to?(:empty?) && value.empty?) end @@ -433,7 +433,7 @@ module ActiveRecord end def typecasted_attribute_value(name) - read_attribute(name) + _read_attribute(name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 2f02738f6d..9ba46ec4c7 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -110,7 +110,7 @@ module ActiveRecord if attribute_changed?(attr) changed_attributes[attr] else - clone_attribute_value(:read_attribute, attr) + clone_attribute_value(:_read_attribute, attr) end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 9bd333bbac..c28374e4ab 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -17,7 +17,7 @@ module ActiveRecord def id if pk = self.class.primary_key sync_with_transaction_state - read_attribute(pk) + _read_attribute(pk) end end @@ -120,6 +120,7 @@ module ActiveRecord def primary_key=(value) @primary_key = value && value.to_s @quoted_primary_key = nil + @attributes_builder = nil end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index bf2a084a00..20f0936e52 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -27,7 +27,7 @@ module ActiveRecord <<-EOMETHOD def #{method_name} name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} - read_attribute(name) { |n| missing_attribute(n, caller) } + _read_attribute(name) { |n| missing_attribute(n, caller) } end EOMETHOD end @@ -64,7 +64,7 @@ module ActiveRecord generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def #{temp_method} name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - read_attribute(name) { |n| missing_attribute(n, caller) } + _read_attribute(name) { |n| missing_attribute(n, caller) } end STR @@ -76,19 +76,27 @@ module ActiveRecord end end + ID = 'id'.freeze + # Returns the value of the attribute identified by <tt>attr_name</tt> after # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name, &block) name = attr_name.to_s - name = self.class.primary_key if name == 'id' - @attributes.fetch_value(name, &block) + name = self.class.primary_key if name == ID + _read_attribute(name, &block) + end + + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the read_attribute API + def _read_attribute(attr_name) # :nodoc: + @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } end private def attribute(attribute_name) - read_attribute(attribute_name) + _read_attribute(attribute_name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index b7fe079ef5..33d9d2002c 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,7 +1,7 @@ module ActiveRecord module AttributeMethods module TimeZoneConversion - class TimeZoneConverter < SimpleDelegator # :nodoc: + class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc: include Type::Decorator def type_cast_from_database(value) diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 98ac63c7e1..6b1d7ea79e 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -2,8 +2,6 @@ require 'active_record/attribute_set/builder' module ActiveRecord class AttributeSet # :nodoc: - delegate :keys, to: :initialized_attributes - def initialize(attributes) @attributes = attributes end @@ -25,8 +23,12 @@ module ActiveRecord attributes.key?(name) && self[name].initialized? end - def fetch_value(name, &block) - self[name].value(&block) + def keys + attributes.initialized_keys + end + + def fetch_value(name) + self[name].value { |n| yield n if block_given? } end def write_from_database(name, value) @@ -43,7 +45,7 @@ module ActiveRecord end def initialize_dup(_) - @attributes = attributes.transform_values(&:dup) + @attributes = attributes.dup super end @@ -58,12 +60,6 @@ module ActiveRecord end end - def ensure_initialized(key) - unless self[key].initialized? - write_from_database(key, nil) - end - end - protected attr_reader :attributes diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index d4a787f2fe..05138ae36d 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,35 +1,84 @@ module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: - attr_reader :types + attr_reader :types, :always_initialized - def initialize(types) + def initialize(types, always_initialized = nil) @types = types + @always_initialized = always_initialized end def build_from_database(values = {}, additional_types = {}) - attributes = build_attributes_from_values(values, additional_types) - add_uninitialized_attributes(attributes) + if always_initialized && !values.key?(always_initialized) + values[always_initialized] = nil + end + + attributes = LazyAttributeHash.new(types, values, additional_types) AttributeSet.new(attributes) end + end + end - private + class LazyAttributeHash # :nodoc: + delegate :select, :transform_values, to: :materialize - def build_attributes_from_values(values, additional_types) - values.each_with_object({}) do |(name, value), hash| - type = additional_types.fetch(name, types[name]) - hash[name] = Attribute.from_database(name, value, type) - end + def initialize(types, values, additional_types) + @types = types + @values = values + @additional_types = additional_types + @materialized = false + @delegate_hash = {} + end + + def key?(key) + delegate_hash.key?(key) || values.key?(key) || types.key?(key) + end + + def [](key) + delegate_hash[key] || assign_default_value(key) + end + + def []=(key, value) + if frozen? + raise RuntimeError, "Can't modify frozen hash" + end + delegate_hash[key] = value + end + + def initialized_keys + delegate_hash.keys | values.keys + end + + def initialize_dup(_) + @delegate_hash = delegate_hash.transform_values(&:dup) + super + end + + protected + + attr_reader :types, :values, :additional_types, :delegate_hash + + private + + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + delegate_hash[name] = Attribute.uninitialized(name, type) end + end - def add_uninitialized_attributes(attributes) - types.each_key do |name| - next if attributes.key? name - type = types[name] - attributes[name] = - Attribute.uninitialized(name, type) - end + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + @materialized = true end + delegate_hash end end end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index a0d70435fa..c39b045a5e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -263,9 +263,9 @@ module ActiveRecord if new_record association && association.target elsif autosave - association.target.find_all { |record| record.changed_for_autosave? } + association.target.find_all(&:changed_for_autosave?) else - association.target.find_all { |record| record.new_record? } + association.target.find_all(&:new_record?) end end @@ -275,7 +275,7 @@ module ActiveRecord self.class._reflections.values.any? do |reflection| if reflection.options[:autosave] association = association_instance_get(reflection.name) - association && Array.wrap(association.target).any? { |a| a.changed_for_autosave? } + association && Array.wrap(association.target).any?(&:changed_for_autosave?) 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 46812b75bb..3a9db4a109 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -320,9 +320,7 @@ module ActiveRecord checkin conn conn.disconnect! if conn.requires_reloading? end - @connections.delete_if do |conn| - conn.requires_reloading? - end + @connections.delete_if(&:requires_reloading?) @available.clear @connections.each do |conn| @available.add conn diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 1a3ed28d66..12b16b2473 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -287,10 +287,6 @@ module ActiveRecord "DEFAULT VALUES" end - def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) - "WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{quoted_table_name} #{where_sql})" - end - # Sanitizes the given LIMIT parameter in order to prevent SQL injection. # # The +limit+ may be anything that can evaluate to a string via #to_s. It diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 6bab260f5a..5c95b95184 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -15,9 +15,7 @@ module ActiveRecord end def visit_AddColumn(o) - sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) - sql = "ADD #{quote_column_name(o.name)} #{sql_type}" - add_column_options!(sql, column_options(o)) + "ADD #{accept(o)}" end private @@ -83,7 +81,7 @@ module ActiveRecord end def add_column_options!(sql, options) - sql << " DEFAULT #{quote_value(options[:default], options[:column])}" if options_include_default?(options) + sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options) # must explicitly check for :null to allow change_column to work on migrations if options[:null] == false sql << " NOT NULL" @@ -94,7 +92,7 @@ module ActiveRecord sql end - def quote_value(value, column) + def quote_default_expression(value, column) column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) column.cast_type ||= type_for_column(column) 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 f8b6daea5a..d2236d046b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -124,8 +124,8 @@ module ActiveRecord # which is one of the following: # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>, # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, - # <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>, - # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. + # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>, + # <tt>:binary</tt>, <tt>:boolean</tt>. # # You may use a type not in this list as long as it is supported by your # database (for example, "polygon" in MySQL), but this will not be database @@ -226,7 +226,7 @@ module ActiveRecord # t.integer :shop_id, :creator_id # t.string :item_number, index: true # t.string :name, :value, default: "Untitled" - # t.timestamps + # t.timestamps null: false # end # # There's a short-hand method for each of the type values declared at the top. And then there's @@ -287,7 +287,9 @@ module ActiveRecord end # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and - # <tt>:updated_at</tt> to the table. + # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps + # + # t.timestamps null: false def timestamps(*args) options = args.extract_options! emit_warning_if_null_unspecified(options) @@ -412,10 +414,10 @@ module ActiveRecord # end # class Table - include TimestampDefaultDeprecation + attr_reader :name def initialize(table_name, base) - @table_name = table_name + @name = table_name @base = base end @@ -425,12 +427,12 @@ module ActiveRecord # ====== Creating a simple column # t.column(:name, :string) def column(column_name, type, options = {}) - @base.add_column(@table_name, column_name, type, options) + @base.add_column(name, column_name, type, options) end # Checks to see if a column exists. See SchemaStatements#column_exists? def column_exists?(column_name, type = nil, options = {}) - @base.column_exists?(@table_name, column_name, type, options) + @base.column_exists?(name, column_name, type, options) end # Adds a new index to the table. +column_name+ can be a single Symbol, or @@ -443,27 +445,26 @@ module ActiveRecord # ====== Creating a named index # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party') def index(column_name, options = {}) - @base.add_index(@table_name, column_name, options) + @base.add_index(name, column_name, options) end # Checks to see if an index exists. See SchemaStatements#index_exists? def index_exists?(column_name, options = {}) - @base.index_exists?(@table_name, column_name, options) + @base.index_exists?(name, column_name, options) end # Renames the given index on the table. # # t.rename_index(:user_id, :account_id) def rename_index(index_name, new_index_name) - @base.rename_index(@table_name, index_name, new_index_name) + @base.rename_index(name, index_name, new_index_name) end # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # - # t.timestamps + # t.timestamps null: false def timestamps(options = {}) - emit_warning_if_null_unspecified(options) - @base.add_timestamps(@table_name, options) + @base.add_timestamps(name, options) end # Changes the column's definition according to the new options. @@ -472,7 +473,7 @@ module ActiveRecord # t.change(:name, :string, limit: 80) # t.change(:description, :text) def change(column_name, type, options = {}) - @base.change_column(@table_name, column_name, type, options) + @base.change_column(name, column_name, type, options) end # Sets a new default value for a column. See SchemaStatements#change_column_default @@ -480,7 +481,7 @@ module ActiveRecord # t.change_default(:qualification, 'new') # t.change_default(:authorized, 1) def change_default(column_name, default) - @base.change_column_default(@table_name, column_name, default) + @base.change_column_default(name, column_name, default) end # Removes the column(s) from the table definition. @@ -488,7 +489,7 @@ module ActiveRecord # t.remove(:qualification) # t.remove(:qualification, :experience) def remove(*column_names) - @base.remove_columns(@table_name, *column_names) + @base.remove_columns(name, *column_names) end # Removes the given index from the table. @@ -502,21 +503,21 @@ module ActiveRecord # ====== Remove the index named by_branch_party in the table_name table # t.remove_index name: :by_branch_party def remove_index(options = {}) - @base.remove_index(@table_name, options) + @base.remove_index(name, options) end # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # # t.remove_timestamps def remove_timestamps - @base.remove_timestamps(@table_name) + @base.remove_timestamps(name) end # Renames a column. # # t.rename(:description, :name) def rename(column_name, new_column_name) - @base.rename_column(@table_name, column_name, new_column_name) + @base.rename_column(name, column_name, new_column_name) end # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. @@ -531,7 +532,7 @@ module ActiveRecord def references(*args) options = args.extract_options! args.each do |ref_name| - @base.add_reference(@table_name, ref_name, options) + @base.add_reference(name, ref_name, options) end end alias :belongs_to :references @@ -546,7 +547,7 @@ module ActiveRecord def remove_references(*args) options = args.extract_options! args.each do |ref_name| - @base.remove_reference(@table_name, ref_name, options) + @base.remove_reference(name, ref_name, options) end end alias :remove_belongs_to :remove_references @@ -558,8 +559,8 @@ module ActiveRecord [: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! - args.each do |name| - @base.add_column(@table_name, name, column_type, options) + args.each do |column_name| + @base.add_column(name, column_name, column_type, options) end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index cbf87df356..aa7c406050 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -571,6 +571,9 @@ module ActiveRecord # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' # def rename_index(table_name, old_name, new_name) + if new_name.length > allowed_index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance) old_index_def = indexes(table_name).detect { |i| i.name == old_name } return unless old_index_def @@ -740,7 +743,7 @@ module ActiveRecord end fk_name_to_delete = options.fetch(:name) do - fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column] } + fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s } if fk_to_delete fk_to_delete.name @@ -778,7 +781,7 @@ module ActiveRecord version = version.to_i sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) - migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i } + migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" } versions = Dir[*paths].map do |filename| filename.split('/').last.split('_').first.to_i @@ -835,11 +838,14 @@ module ActiveRecord columns end - # Adds timestamps (+created_at+ and +updated_at+) columns to the named table. + include TimestampDefaultDeprecation + # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. + # Additional options (like <tt>null: false</tt>) are forwarded to #add_column. # - # add_timestamps(:suppliers) + # add_timestamps(:suppliers, null: false) # def add_timestamps(table_name, options = {}) + emit_warning_if_null_unspecified(options) add_column table_name, :created_at, :datetime, options add_column table_name, :updated_at, :datetime, options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 582dd360f0..e0a4af5359 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -24,6 +24,7 @@ module ActiveRecord autoload :TableDefinition autoload :Table autoload :AlterTable + autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do @@ -262,10 +263,10 @@ module ActiveRecord # QUOTING ================================================== - # Returns a bind substitution value given a bind +index+ and +column+ + # Returns a bind substitution value given a bind +column+ # NOTE: The column param is currently being used by the sqlserver-adapter - def substitute_at(column, index = 0) - Arel::Nodes::BindParam.new '?' + def substitute_at(column, _unused = 0) + Arel::Nodes::BindParam.new end # REFERENTIAL INTEGRITY ==================================== 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 7f04e35042..bf16804aa0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -285,7 +285,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def clear_cache! super @@ -396,7 +398,7 @@ module ActiveRecord sql << "LIKE #{quote(like)}" if like execute_and_free(sql, 'SCHEMA') do |result| - result.collect { |field| field.first } + result.collect(&:first) end end @@ -585,14 +587,6 @@ module ActiveRecord end end - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" - end - end - # SHOW VARIABLES LIKE 'name' def show_variable(name) variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA') @@ -639,10 +633,6 @@ module ActiveRecord end end - def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) - where_sql - end - def strict_mode? self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) end @@ -656,6 +646,8 @@ module ActiveRecord def initialize_type_map(m) # :nodoc: super + register_class_with_limit m, %r(char)i, MysqlString + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) @@ -680,7 +672,7 @@ module ActiveRecord m.register_type(%r(enum)i) do |sql_type| limit = sql_type[/^enum\((.+)\)/i, 1] .split(',').map{|enum| enum.strip.length - 2}.max - Type::String.new(limit: limit) + MysqlString.new(limit: limit) end end @@ -794,7 +786,7 @@ module ActiveRecord private def version - @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } + @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map(&:to_i) end def mariadb? @@ -855,6 +847,26 @@ module ActiveRecord end end end + + class MysqlString < Type::String # :nodoc: + def type_cast_for_database(value) + case value + when true then "1" + when false then "0" + else super + end + end + + private + + def cast_value(value) + case value + when true then "1" + when false then "0" + else super + end + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 609ec7dabd..e54e3199ff 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -234,7 +234,7 @@ module ActiveRecord end end - # Takes the environment such as `:production` or `:development`. + # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that # matches. # diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 5b83131f0e..d19bd80576 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -66,7 +66,9 @@ module ActiveRecord exception.error_number if exception.respond_to?(:error_number) end + #-- # QUOTING ================================================== + #++ def quote_string(string) @connection.escape(string) @@ -80,7 +82,9 @@ module ActiveRecord end end + #-- # CONNECTION MANAGEMENT ==================================== + #++ def active? return false unless @connection @@ -104,7 +108,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds.dup)}" diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 3357ed52e5..23d8389abb 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -58,7 +58,7 @@ module ActiveRecord # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection. # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html) - # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html). + # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html). # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection. @@ -137,7 +137,9 @@ module ActiveRecord @connection.quote(string) end + #-- # CONNECTION MANAGEMENT ==================================== + #++ def active? if @connection.respond_to?(:stat) @@ -178,7 +180,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def select_rows(sql, name = nil, binds = []) @connection.query_with_result = true @@ -419,9 +423,7 @@ module ActiveRecord cols = nil if metadata = stmt.result_metadata - cols = cache[:cols] ||= metadata.fetch_fields.map { |field| - field.name - } + cols = cache[:cols] ||= metadata.fetch_fields.map(&:name) metadata.free end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index cf379ab210..d09468329a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -156,10 +156,6 @@ module ActiveRecord end end - def substitute_at(column, index = 0) - Arel::Nodes::BindParam.new "$#{index + 1}" - end - def exec_query(sql, name = 'SQL', binds = []) execute_and_clear(sql, name, binds) do |result| types = {} diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index f95f45c689..991c41327f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -14,22 +14,6 @@ module ActiveRecord @connection.unescape_bytea(value) if value end - # Quotes PostgreSQL-specific data types for SQL input. - def quote(value, column = nil) #:nodoc: - return super unless column - - case value - when Float - if value.infinite? || value.nan? - "'#{value}'" - else - super - end - else - super - end - end - # Quotes strings for use in SQL input. def quote_string(s) #:nodoc: @connection.escape(s) @@ -94,6 +78,12 @@ module ActiveRecord elsif value.hex? "X'#{value}'" end + when Float + if value.infinite? || value.nan? + "'#{value}'" + else + super + end else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 51853c6812..f29b793a3f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -4,12 +4,6 @@ module ActiveRecord class SchemaCreation < AbstractAdapter::SchemaCreation private - def visit_AddColumn(o) - sql_type = type_to_sql(o.type, 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 != :primary_key @@ -19,14 +13,22 @@ module ActiveRecord sql end + def column_options(o) + column_options = super + column_options[:array] = o.array + column_options + end + def add_column_options!(sql, options) - if options[:array] || options[:column].try(:array) + if options[:array] sql << '[]' end + super + end - column = options.fetch(:column) { return super } - if column.type == :uuid && options[:default] =~ /\(\)/ - sql << " DEFAULT #{options[:default]}" + def quote_default_expression(value, column) + if column.type == :uuid && value =~ /\(\)/ + value else super end @@ -411,7 +413,10 @@ module ActiveRecord pk, seq = pk_and_sequence_for(new_name) if seq && seq.identifier == "#{table_name}_#{pk}_seq" new_seq = "#{new_name}_#{pk}_seq" + idx = "#{table_name}_pkey" + new_idx = "#{new_name}_pkey" execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}" + execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" end rename_table_indexes(table_name, new_name) @@ -430,7 +435,12 @@ module ActiveRecord quoted_table_name = quote_table_name(table_name) sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale]) sql_type << "[]" if options[:array] - execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" + sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{sql_type}" + sql << " USING #{options[:using]}" if options[:using] + if options[:cast_as] + sql << " USING CAST(#{quote_column_name(column_name)} AS #{type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale])})" + end + execute sql change_column_default(table_name, column_name, options[:default]) if options_include_default?(options) change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) @@ -478,6 +488,9 @@ module ActiveRecord end def rename_index(table_name, old_name, new_name) + if new_name.length > allowed_index_name_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb index 0290bcb48c..9a0b80d7d3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb @@ -18,7 +18,11 @@ module ActiveRecord end def quoted - parts.map { |p| PGconn.quote_ident(p) }.join SEPARATOR + if schema + PGconn.quote_ident(schema) << SEPARATOR << PGconn.quote_ident(identifier) + else + PGconn.quote_ident(identifier) + end end def ==(o) @@ -32,8 +36,11 @@ module ActiveRecord protected def unquote(part) - return unless part - part.gsub(/(^"|"$)/,'') + if part && part.start_with?('"') + part[1..-2] + else + part + end end def parts @@ -57,7 +64,11 @@ module ActiveRecord # * <tt>"schema_name".table_name</tt> # * <tt>"schema.name"."table name"</tt> def extract_schema_qualified_name(string) - table, schema = string.scan(/[^".\s]+|"[^"]*"/)[0..1].reverse + schema, table = string.scan(/[^".\s]+|"[^"]*"/) + if table.nil? + table = schema + schema = nil + end PostgreSQL::Name.new(schema, table) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e472741660..3698957d8d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -13,7 +13,7 @@ require 'active_record/connection_adapters/postgresql/database_statements' require 'arel/visitors/bind_visitor' # Make sure we're using pg high enough for PGResult#values -gem 'pg', '~> 0.11' +gem 'pg', '~> 0.15' require 'pg' require 'ipaddr' @@ -104,6 +104,7 @@ module ActiveRecord macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, json: { name: "json" }, + jsonb: { name: "jsonb" }, ltree: { name: "ltree" }, citext: { name: "citext" }, point: { name: "point" }, @@ -124,7 +125,7 @@ module ActiveRecord PostgreSQL::SchemaCreation.new self end - # Adds `:array` option to the default set provided by the + # Adds +:array+ option to the default set provided by the # AbstractAdapter def prepare_column_options(column, types) # :nodoc: spec = super @@ -133,7 +134,7 @@ module ActiveRecord spec end - # Adds `:array` as a valid migration key + # Adds +:array+ as a valid migration key def migration_keys super + [:array] end @@ -595,9 +596,7 @@ module ActiveRecord } log(sql, name, type_casted_binds, stmt_key) do - @connection.send_query_prepared(stmt_key, type_casted_binds.map { |_, val| val }) - @connection.block - @connection.get_last_result + @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val }) end rescue ActiveRecord::StatementInvalid => e pgerror = e.original_exception diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index a10ce330c7..37ff4e4613 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -61,9 +61,7 @@ module ActiveRecord end def size - [@columns, @columns_hash, @primary_keys, @tables].map { |x| - x.size - }.inject :+ + [@columns, @columns_hash, @primary_keys, @tables].map(&:size).inject :+ end # Clear out internal caches for table with +table_name+. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 1b5e3bdbac..c3865a8fdd 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -88,11 +88,11 @@ module ActiveRecord include Comparable def initialize(version_string) - @version = version_string.split('.').map { |v| v.to_i } + @version = version_string.split('.').map(&:to_i) end def <=>(version_string) - @version <=> version_string.split('.').map { |v| v.to_i } + @version <=> version_string.split('.').map(&:to_i) end end @@ -179,10 +179,6 @@ module ActiveRecord true end - def supports_add_column? - true - end - def supports_views? true end @@ -270,7 +266,9 @@ module ActiveRecord end end + #-- # DATABASE STATEMENTS ====================================== + #++ def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" @@ -451,12 +449,12 @@ module ActiveRecord # See: http://www.sqlite.org/lang_altertable.html # SQLite has an additional restriction on the ALTER TABLE statement - def valid_alter_table_options( type, options) + def valid_alter_table_type?(type) type.to_sym != :primary_key end def add_column(table_name, column_name, type, options = {}) #:nodoc: - if supports_add_column? && valid_alter_table_options( type, options ) + if valid_alter_table_type?(type) super(table_name, column_name, type, options) else alter_table(table_name) do |definition| @@ -558,7 +556,7 @@ module ActiveRecord end copy_table_indexes(from, to, options[:rename] || {}) copy_table_contents(from, to, - @definition.columns.map {|column| column.name}, + @definition.columns.map(&:name), options[:rename] || {}) end @@ -571,7 +569,7 @@ module ActiveRecord name = name[1..-1] end - to_column_names = columns(to).map { |c| c.name } + to_column_names = columns(to).map(&:name) columns = index.columns.map {|c| rename[c] || c }.select do |column| to_column_names.include?(column) end @@ -588,7 +586,7 @@ module ActiveRecord def copy_table_contents(from, to, columns, rename = {}) #:nodoc: column_mappings = Hash[columns.map {|name| [name, name]}] rename.each { |a| column_mappings[a.last] = a.first } - from_columns = columns(from).collect {|col| col.name} + from_columns = columns(from).collect(&:name) columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} quoted_columns = columns.map { |col| quote_column_name(col) } * ',' diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 952aeaa703..c812e7842a 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -235,7 +235,7 @@ module ActiveRecord # scope :published_and_commented, -> { published.and(self.arel_table[:comments_count].gt(0)) } # end def arel_table # :nodoc: - @arel_table ||= Arel::Table.new(table_name, arel_engine) + @arel_table ||= Arel::Table.new(table_name) end # Returns the Arel engine. @@ -536,8 +536,6 @@ module ActiveRecord end def init_internals - @attributes.ensure_initialized(self.class.primary_key) - @aggregation_cache = {} @association_cache = {} @readonly = false diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 5958373e88..f053372cfb 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -142,7 +142,7 @@ module ActiveRecord private def save_changed_attribute(attr_name, old) if (mapping = self.class.defined_enums[attr_name.to_s]) - value = read_attribute(attr_name) + value = _read_attribute(attr_name) if attribute_changed?(attr_name) if mapping[old] == value clear_attribute_changes([attr_name]) diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 5b3fdf16f5..fc28ab585f 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -52,10 +52,29 @@ module ActiveRecord # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be # saved because record is invalid. class RecordNotSaved < ActiveRecordError + attr_reader :record + + def initialize(message, record = nil) + @record = record + super(message) + end end # Raised by ActiveRecord::Base.destroy! when a call to destroy would return false. + # + # begin + # complex_operation_that_internally_calls_destroy! + # rescue ActiveRecord::RecordNotDestroyed => invalid + # puts invalid.record.errors + # end + # class RecordNotDestroyed < ActiveRecordError + attr_reader :record + + def initialize(record) + @record = record + super() + end end # Superclass for all database execution errors. @@ -177,6 +196,7 @@ module ActiveRecord # offending attribute. class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute + def initialize(message, exception, attribute) super(message) @exception = exception @@ -189,6 +209,7 @@ module ActiveRecord # objects, each corresponding to the error while assigning to an attribute. class MultiparameterAssignmentErrors < ActiveRecordError attr_reader :errors + def initialize(errors) @errors = errors end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index db6421dacb..4732462b05 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -745,7 +745,7 @@ module ActiveRecord end def column_names - @column_names ||= @connection.columns(@table_name).collect { |c| c.name } + @column_names ||= @connection.columns(@table_name).collect(&:name) end def read_fixture_files(path, model_class) @@ -806,7 +806,9 @@ module ActiveRecord def find if model_class - model_class.find(fixture[model_class.primary_key]) + model_class.unscoped do + model_class.find(fixture[model_class.primary_key]) + end else raise FixtureClassNotFound, "No class attached to find." end @@ -866,7 +868,7 @@ module ActiveRecord fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"] 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 } + fixture_set_names = fixture_set_names.flatten.map(&:to_s) end self.fixture_table_names |= fixture_set_names @@ -906,7 +908,7 @@ module ActiveRecord def uses_transaction(*methods) @uses_transaction = [] unless defined?(@uses_transaction) - @uses_transaction.concat methods.map { |m| m.to_s } + @uses_transaction.concat methods.map(&:to_s) end def uses_transaction?(method) diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index e820835626..a388b529c9 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -5,10 +5,10 @@ module ActiveRecord end module VERSION - MAJOR = 4 - MINOR = 2 + MAJOR = 5 + MINOR = 0 TINY = 0 - PRE = "beta4" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index f58145ab05..b91e9ac137 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -192,7 +192,7 @@ module ActiveRecord def type_condition(table = arel_table) sti_column = table[inheritance_column] - sti_names = ([self] + descendants).map { |model| model.sti_name } + sti_names = ([self] + descendants).map(&:sti_name) sti_column.in(sti_names) end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 52eeb8ae1f..ced694ba9a 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -120,7 +120,7 @@ module ActiveRecord if locking_enabled? column_name = self.class.locking_column column = self.class.columns_hash[column_name] - substitute = self.class.connection.substitute_at(column, relation.bind_values.length) + substitute = self.class.connection.substitute_at(column) relation = relation.where(self.class.arel_table[column_name].eq(substitute)) relation.bind_values << [column, self[column_name].to_i] diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 2024b225e4..3cac465440 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -184,8 +184,8 @@ module ActiveRecord # you wish to downgrade. Alternatively, you can also use the STEP option if you # wish to rollback last few migrations. <tt>rake db:migrate STEP=2</tt> will rollback # the latest two migrations. - # - # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception, + # + # If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception, # that step will fail and you'll have some manual work to do. # # == Database support @@ -395,7 +395,14 @@ module ActiveRecord def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? - ActiveRecord::Tasks::DatabaseTasks.load_schema_current_if_exists + # Roundrip to Rake to allow plugins to hook into database initialization. + FileUtils.cd Rails.root do + current_config = Base.connection_config + Base.clear_all_connections! + system("bin/rake db:test:prepare") + # Establish a new connection, the old database may be gone (db:test:prepare uses purge) + Base.establish_connection(current_config) + end check_pending! end end @@ -640,7 +647,7 @@ module ActiveRecord end def method_missing(method, *arguments, &block) - arg_list = arguments.map{ |a| a.inspect } * ', ' + arg_list = arguments.map(&:inspect) * ', ' say_with_time "#{method}(#{arg_list})" do unless @connection.respond_to? :revert diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index a444aac23c..92ad9c9101 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -231,7 +231,7 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(column_types) + @attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key) end def column_types # :nodoc: @@ -257,7 +257,7 @@ module ActiveRecord # Returns an array of column names as strings. def column_names - @column_names ||= columns.map { |column| column.name } + @column_names ||= columns.map(&:name) end # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 755ff2b2f1..99e104598b 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -139,7 +139,7 @@ module ActiveRecord # Attributes marked as readonly are silently ignored if the record is # being updated. def save!(*) - create_or_update || raise(RecordNotSaved) + create_or_update || raise(RecordNotSaved.new(nil, self)) end # Deletes the record in the database and freezes this instance to @@ -166,7 +166,7 @@ module ActiveRecord # and <tt>destroy</tt> returns +false+. See # ActiveRecord::Callbacks for further details. def destroy - raise ReadOnlyRecord if readonly? + raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? destroy_associations destroy_row if persisted? @destroyed = true @@ -181,7 +181,7 @@ module ActiveRecord # and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See # ActiveRecord::Callbacks for further details. def destroy! - destroy || raise(ActiveRecord::RecordNotDestroyed) + destroy || raise(ActiveRecord::RecordNotDestroyed, self) end # Returns an instance of the specified +klass+ with the attributes of the @@ -487,7 +487,7 @@ module ActiveRecord def relation_for_destroy pk = self.class.primary_key column = self.class.columns_hash[pk] - substitute = self.class.connection.substitute_at(column, 0) + substitute = self.class.connection.substitute_at(column) relation = self.class.unscoped.where( self.class.arel_table[pk].eq(substitute)) @@ -497,7 +497,7 @@ module ActiveRecord end def create_or_update - raise ReadOnlyRecord if readonly? + raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? result = new_record? ? _create_record : _update_record result != false end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index a4ceacbf44..f1bdbc845c 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -36,8 +36,6 @@ module ActiveRecord config.eager_load_namespaces << ActiveRecord rake_tasks do - require "active_record/base" - namespace :db do task :load_config do ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 3ec25f9f17..6c09456c4d 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -305,7 +305,7 @@ db_namespace = namespace :db do end # desc "Recreate the test database from the current schema" - task :load => %w(db:test:deprecated db:test:purge) do + task :load => %w(db:test:purge) do case ActiveRecord::Base.schema_format when :ruby db_namespace["test:load_schema"].invoke @@ -315,7 +315,7 @@ db_namespace = namespace :db do end # desc "Recreate the test database from an existent schema.rb file" - task :load_schema => %w(db:test:deprecated db:test:purge) do + task :load_schema => %w(db:test:purge) do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false @@ -328,7 +328,7 @@ db_namespace = namespace :db do end # desc "Recreate the test database from an existent structure.sql file" - task :load_structure => %w(db:test:deprecated db:test:purge) do + task :load_structure => %w(db:test:purge) do ActiveRecord::Tasks::DatabaseTasks.load_schema_for ActiveRecord::Base.configurations['test'], :sql, ENV['SCHEMA'] end @@ -349,12 +349,12 @@ db_namespace = namespace :db do task :clone_structure => %w(db:test:deprecated db:structure:dump db:test:load_structure) # desc "Empty the test database" - task :purge => %w(db:test:deprecated environment load_config) do + task :purge => %w(environment load_config) do ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations['test'] end # desc 'Check for pending migrations and load the test schema' - task :prepare => %w(db:test:deprecated environment load_config) do + task :prepare => %w(environment load_config) do unless ActiveRecord::Base.configurations.blank? db_namespace['test:load'].invoke end @@ -366,7 +366,7 @@ namespace :railties do namespace :install do # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" task :migrations => :'db:load_config' do - to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } + to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map(&:strip) railties = {} Rails.application.migration_railties.each do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 85bbac43e4..ce78f1756d 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -11,7 +11,7 @@ module ActiveRecord # Attributes listed as readonly will be used to create a new record but update operations will # ignore these fields. def attr_readonly(*attributes) - self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || []) + self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || []) end # Returns an array of all the attributes that have been specified as readonly. diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 4b58f2deb7..96520d1d49 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -63,7 +63,7 @@ module ActiveRecord # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value. # - # Account.reflections # => {balance: AggregateReflection} + # Account.reflections # => {"balance" => AggregateReflection} # # @api public def reflections @@ -287,7 +287,7 @@ module ActiveRecord def association_scope_cache(conn, owner) key = conn.prepared_statements if polymorphic? - key = [key, owner.read_attribute(@foreign_type)] + key = [key, owner._read_attribute(@foreign_type)] end @association_scope_cache[key] ||= @scope_lock.synchronize { @association_scope_cache[key] ||= yield @@ -791,7 +791,7 @@ module ActiveRecord def source_reflection_name # :nodoc: return @source_reflection_name if @source_reflection_name - names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq + names = [name.to_s.singularize, name].collect(&:to_sym).uniq names = names.find_all { |n| through_reflection.klass._reflect_on_association(n) } diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 03bce4f5b7..7df50f7c97 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -84,7 +84,6 @@ module ActiveRecord um = relation .arel .compile_update(substitutes, @klass.primary_key) - reorder_bind_params(um.ast, bvs) @klass.connection.update( um, @@ -99,7 +98,7 @@ module ActiveRecord end substitutes = values.each_with_index.map do |(arel_attr, _), i| - [arel_attr, @klass.connection.substitute_at(binds[i][0], i)] + [arel_attr, @klass.connection.substitute_at(binds[i][0])] end [substitutes, binds] @@ -305,11 +304,11 @@ module ActiveRecord klass.current_scope = previous end - # Updates all records with details given if they match a set of conditions supplied, limits and order can - # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the - # database. It does not instantiate the involved models and it does not trigger Active Record callbacks - # or validations. Values passed to `update_all` will not go through ActiveRecord's type-casting behavior. - # It should receive only values that can be passed as-is to the SQL database. + # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE + # statement and sends it straight to the database. It does not instantiate the involved models and it does not + # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through + # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL + # database. # # ==== Parameters # @@ -328,7 +327,7 @@ module ActiveRecord def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? - stmt = Arel::UpdateManager.new(arel.engine) + stmt = Arel::UpdateManager.new stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) stmt.table(table) @@ -401,7 +400,7 @@ module ActiveRecord if conditions where(conditions).destroy_all else - to_a.each {|object| object.destroy }.tap { reset } + to_a.each(&:destroy).tap { reset } end end @@ -466,7 +465,7 @@ module ActiveRecord if conditions where(conditions).delete_all else - stmt = Arel::DeleteManager.new(arel.engine) + stmt = Arel::DeleteManager.new stmt.from(table) if joins_values.any? @@ -640,17 +639,21 @@ module ActiveRecord preload = preload_values preload += includes_values unless eager_loading? - preloader = ActiveRecord::Associations::Preloader.new + preloader = build_preloader preload.each do |associations| preloader.preload @records, associations end - @records.each { |record| record.readonly! } if readonly_value + @records.each(&:readonly!) if readonly_value @loaded = true @records end + def build_preloader + ActiveRecord::Associations::Preloader.new + end + def references_eager_loaded_tables? joined_tables = arel.join_sources.map do |join| if join.is_a?(Arel::Nodes::StringJoin) @@ -663,7 +666,7 @@ module ActiveRecord joined_tables += [table.name, table.table_alias] # always convert table names to downcase as in Oracle quoted table names are in uppercase - joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq + joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq (references_values - joined_tables).any? end @@ -672,7 +675,7 @@ module ActiveRecord return [] if string.blank? # always convert table names to downcase as in Oracle quoted table names are in uppercase # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries - string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_'] + string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_'] end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index eacae73ebb..357861caaa 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -108,7 +108,7 @@ module ActiveRecord # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. Note that <tt>take!</tt> accepts no arguments. def take! - take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") end # Find the first record (or first N records if a parameter is supplied). @@ -176,7 +176,7 @@ module ActiveRecord # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record # is found. Note that <tt>last!</tt> accepts no arguments. def last! - last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") end # Find the second record. @@ -323,7 +323,7 @@ module ActiveRecord # 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 = arel.where_sql(@klass.arel_engine) conditions = " [#{conditions}]" if conditions if Array(ids).size == 1 @@ -415,7 +415,7 @@ module ActiveRecord end def using_limitable_reflections?(reflections) - reflections.none? { |r| r.collection? } + reflections.none?(&:collection?) end protected @@ -498,7 +498,7 @@ module ActiveRecord end def find_nth!(index) - find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql}]") + find_nth(index, offset_index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]") end def find_nth_with_limit(offset, limit) diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index a8febf6a18..a27f990f74 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -118,18 +118,6 @@ module ActiveRecord where_values = kept + rhs_wheres bind_values = filter_binds(lhs_binds, removed) + rhs_binds - conn = relation.klass.connection - bv_index = 0 - where_values.map! do |node| - if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right - substitute = conn.substitute_at(bind_values[bv_index].first, bv_index) - bv_index += 1 - Arel::Nodes::Equality.new(node.left, substitute) - else - node - end - end - relation.where_values = where_values relation.bind_values = bind_values diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index e4b6b49087..938bf55c6d 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -25,7 +25,7 @@ module ActiveRecord if value.empty? queries << '1=0' else - table = Arel::Table.new(column, default_table.engine) + table = Arel::Table.new(column) association = klass._reflect_on_association(column) value.each do |k, v| @@ -37,7 +37,7 @@ module ActiveRecord if column.include?('.') table_name, column = column.split('.', 2) - table = Arel::Table.new(table_name, default_table.engine) + table = Arel::Table.new(table_name) end queries.concat expand(klass, table, column, value) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a686e3263b..d58af01339 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -427,14 +427,12 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id def joins(*args) check_if_method_has_arguments!(:joins, args) - - args.compact! - args.flatten! - spawn.joins!(*args) end def joins!(*args) # :nodoc: + args.compact! + args.flatten! self.joins_values += args self end @@ -860,7 +858,7 @@ module ActiveRecord private def build_arel - arel = Arel::SelectManager.new(table.engine, table) + arel = Arel::SelectManager.new(table) build_joins(arel, joins_values.flatten) unless joins_values.empty? @@ -881,19 +879,9 @@ module ActiveRecord arel.from(build_from) if from_value arel.lock(lock_value) if lock_value - # Reorder bind indexes if joins produced bind values - bvs = arel.bind_values + bind_values - reorder_bind_params(arel.ast, bvs) arel end - def reorder_bind_params(ast, bvs) - ast.grep(Arel::Nodes::BindParam).each_with_index do |bp, i| - column = bvs[i].first - bp.replace connection.substitute_at(column, i) - end - end - def symbol_unscoping(scope) if !VALID_UNSCOPING_VALUES.include?(scope) raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." @@ -1069,8 +1057,13 @@ module ActiveRecord def build_select(arel, selects) if !selects.empty? expanded_select = selects.map do |field| - columns_hash.key?(field.to_s) ? arel_table[field] : field + if (Symbol === field || String === field) && columns_hash.key?(field.to_s) + arel_table[field] + else + field + end end + arel.project(*expanded_select) else arel.project(@klass.arel_table[Arel.star]) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 6a130c145b..88dfddebcc 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -93,7 +93,7 @@ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 attrs = PredicateBuilder.resolve_column_aliases self, attrs attrs = expand_hash_conditions_for_aggregates(attrs) - table = Arel::Table.new(table_name, arel_engine).alias(default_table_name) + table = Arel::Table.new(table_name).alias(default_table_name) PredicateBuilder.build_from_hash(self, attrs, table).map { |b| connection.visitor.compile b }.join(' AND ') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 261fb9d992..1dd2cadc01 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -244,12 +244,7 @@ HEADER def ignored?(table_name) ['schema_migrations', ignore_tables].flatten.any? do |ignored| - case ignored - when String; remove_prefix_and_suffix(table_name) == ignored - when Regexp; remove_prefix_and_suffix(table_name) =~ ignored - else - raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.' - end + ignored === remove_prefix_and_suffix(table_name) end end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index ec1edf0e01..35420e6551 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -139,7 +139,7 @@ module ActiveRecord # Article.published.featured.latest_article # Article.featured.titles def scope(name, body, &block) - unless body.respond_to?:call + unless body.respond_to?(:call) raise ArgumentError, 'The scope body needs to be callable.' end diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index bd9079b596..48c12dcf9f 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -11,7 +11,7 @@ module ActiveRecord #:nodoc: def serializable_hash(options = nil) options = options.try(:clone) || {} - options[:except] = Array(options[:except]).map { |n| n.to_s } + options[:except] = Array(options[:except]).map(&:to_s) options[:except] |= Array(self.class.inheritance_column) super(options) diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index aece446384..192a19f05d 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -1,22 +1,33 @@ 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: + # Initializing the cache is done by passing the statement in the create block: # - # cache = ActiveRecord::StatementCache.new do - # Book.where(name: "my book").limit(100) + # cache = StatementCache.create(Book.connection) do |params| + # Book.where(name: "my book").where("author_id > 3") # end # # The cached statement is executed by using the +execute+ method: # - # cache.execute + # cache.execute([], Book, Book.connection) # # 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 - class Substitute; end + # + # If you want to cache the statement without the values you can use the +bind+ method of the + # block parameter. + # + # cache = StatementCache.create(Book.connection) do |params| + # Book.where(name: params.bind) + # end + # + # And pass the bind values as the first argument of +execute+ call. + # + # cache.execute(["my book"], Book, Book.connection) + class StatementCache # :nodoc: + class Substitute; end # :nodoc: - class Query + class Query # :nodoc: def initialize(sql) @sql = sql end @@ -26,7 +37,7 @@ module ActiveRecord end end - class PartialQuery < Query + class PartialQuery < Query # :nodoc: def initialize values @values = values @indexes = values.each_with_index.find_all { |thing,i| @@ -51,11 +62,11 @@ module ActiveRecord PartialQuery.new collected end - class Params + class Params # :nodoc: def bind; Substitute.new; end end - class BindMap + class BindMap # :nodoc: def initialize(bind_values) @indexes = [] @bind_values = bind_values @@ -68,7 +79,7 @@ module ActiveRecord end def bind(values) - bvs = @bind_values.map { |pair| pair.dup } + bvs = @bind_values.map(&:dup) @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] } bvs end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index d890196f47..eafbb2c249 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -31,6 +31,7 @@ module ActiveRecord end establish_connection configuration else + $stderr.puts error.inspect $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding'] end diff --git a/activerecord/lib/active_record/type/hash_lookup_type_map.rb b/activerecord/lib/active_record/type/hash_lookup_type_map.rb index bf92680268..82d9327fc0 100644 --- a/activerecord/lib/active_record/type/hash_lookup_type_map.rb +++ b/activerecord/lib/active_record/type/hash_lookup_type_map.rb @@ -3,16 +3,14 @@ module ActiveRecord class HashLookupTypeMap < TypeMap # :nodoc: delegate :key?, to: :@mapping - def lookup(type, *args) - @mapping.fetch(type, proc { default_value }).call(type, *args) + def alias_type(type, alias_type) + register_type(type) { |_, *args| lookup(alias_type, *args) } end - def fetch(type, *args, &block) - @mapping.fetch(type, block).call(type, *args) - end + private - def alias_type(type, alias_type) - register_type(type) { |_, *args| lookup(alias_type, *args) } + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) end end end diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index d69e5b3f28..750f353472 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -14,6 +14,11 @@ module ActiveRecord alias type_cast_for_database type_cast + def type_cast_from_database(value) + return if value.nil? + value.to_i + end + protected attr_reader :range @@ -33,7 +38,7 @@ module ActiveRecord def ensure_in_range(value) unless range.cover?(value) - raise RangeError, "#{value} is too large for #{self.class} with limit #{limit || 4}" + raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}" end end diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index 17004b3593..3191a868ef 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -1,6 +1,6 @@ module ActiveRecord module Type - class Serialized < SimpleDelegator # :nodoc: + class Serialized < DelegateClass(Type::Value) # :nodoc: include Mutable include Decorator diff --git a/activerecord/lib/active_record/type/string.rb b/activerecord/lib/active_record/type/string.rb index 150defb106..fbc0af2c5a 100644 --- a/activerecord/lib/active_record/type/string.rb +++ b/activerecord/lib/active_record/type/string.rb @@ -15,8 +15,8 @@ module ActiveRecord case value when ::Numeric, ActiveSupport::Duration then value.to_s when ::String then ::String.new(value) - when true then "1" - when false then "0" + when true then "t" + when false then "f" else super end end @@ -25,8 +25,8 @@ module ActiveRecord def cast_value(value) case value - when true then "1" - when false then "0" + when true then "t" + when false then "f" # String.new is slightly faster than dup else ::String.new(value.to_s) end diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 88c5f9c497..09f5ba6b74 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -1,24 +1,28 @@ +require 'thread_safe' + module ActiveRecord module Type class TypeMap # :nodoc: def initialize @mapping = {} + @cache = ThreadSafe::Cache.new do |h, key| + h.fetch_or_store(key, ThreadSafe::Cache.new) + end end def lookup(lookup_key, *args) - matching_pair = @mapping.reverse_each.detect do |key, _| - key === lookup_key - end + fetch(lookup_key, *args) { default_value } + end - if matching_pair - matching_pair.last.call(lookup_key, *args) - else - default_value + def fetch(lookup_key, *args, &block) + @cache[lookup_key].fetch_or_store(args) do + perform_fetch(lookup_key, *args, &block) end end def register_type(key, value = nil, &block) raise ::ArgumentError unless value || block + @cache.clear if block @mapping[key] = block @@ -40,6 +44,18 @@ module ActiveRecord private + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end + end + def default_value @default_value ||= Value.new end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 7f7d49cdb4..a6c8ff7f3a 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -5,13 +5,14 @@ module ActiveRecord # +record+ method to retrieve the record which did not validate. # # begin - # complex_operation_that_calls_save!_internally + # complex_operation_that_internally_calls_save! # rescue ActiveRecord::RecordInvalid => invalid # puts invalid.record.errors # end class RecordInvalid < ActiveRecordError - attr_reader :record # :nodoc: - def initialize(record) # :nodoc: + attr_reader :record + + def initialize(record) @record = record errors = @record.errors.full_messages.join(", ") super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid")) diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb index c7aa814ba8..61b30749d9 100644 --- a/activerecord/lib/active_record/validations/presence.rb +++ b/activerecord/lib/active_record/validations/presence.rb @@ -8,7 +8,7 @@ module ActiveRecord associated_records = Array.wrap(record.send(attribute)) # Superclass validates presence. Ensure present records aren't about to be destroyed. - if associated_records.present? && associated_records.all? { |r| r.marked_for_destruction? } + if associated_records.present? && associated_records.all?(&:marked_for_destruction?) record.errors.add(attribute, :blank, options) end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 2dba4c7b94..3e8afe37a8 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -79,7 +79,7 @@ module ActiveRecord scope_value = record.send(reflection.foreign_key) scope_item = reflection.foreign_key else - scope_value = record.read_attribute(scope_item) + scope_value = record._read_attribute(scope_item) end relation = relation.and(table[scope_item].eq(scope_value)) end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index f7bf6987c4..fb0fbb4759 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb @@ -15,5 +15,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes_with_index.each do |attribute| -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <% end -%> +<% attributes.select(&:reference?).reject(&:polymorphic?).each do |attribute| -%> + add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> +<% end -%> end end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index ae9c74fd05..7df9bcad25 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -4,6 +4,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% attributes.each do |attribute| -%> <%- if attribute.reference? -%> add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- unless attribute.polymorphic? -%> + add_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> + <%- end -%> <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> @@ -26,6 +29,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action -%> <%- if attribute.reference? -%> remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- unless attribute.polymorphic? -%> + remove_foreign_key :<%= table_name %>, :<%= attribute.name.pluralize %> + <%- end -%> <%- else -%> <%- if attribute.has_index? -%> remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index a84673e452..413f67da26 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me - ActiveRecord::Base.connection.add_timestamps :delete_me + ActiveRecord::Base.connection.add_timestamps :delete_me, null: true assert column_present?('delete_me', 'updated_at', 'datetime') assert column_present?('delete_me', 'created_at', 'datetime') ensure diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 3dabb1104a..ce01b16362 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -47,9 +47,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert !@connection.active? # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + @fixture_connections.each(&:verify!) end def test_successful_reconnection_after_timeout_with_manual_reconnect @@ -69,8 +67,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_bind_value_substitute - bind_param = @connection.substitute_at('foo', 0) - assert_equal Arel.sql('?'), bind_param + bind_param = @connection.substitute_at('foo') + assert_equal Arel.sql('?'), bind_param.to_sql end def test_exec_no_binds diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 61ae0abfd1..403f7cbc74 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -101,7 +101,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase gs = nil assert_nothing_raised { gs = Select.find(2).groups } assert_equal gs.length, 2 - assert(gs.collect{|x| x.id}.sort == [2, 3]) + assert(gs.collect(&:id).sort == [2, 3]) end # has_and_belongs_to_many with reserved-word table name @@ -110,7 +110,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase s = nil assert_nothing_raised { s = Distinct.find(1).selects } assert_equal s.length, 2 - assert(s.collect{|x|x.id}.sort == [1, 2]) + assert(s.collect(&:id).sort == [1, 2]) end # activerecord model introspection with reserved-word table and column names diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index 87c5277e64..ab547747df 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -81,7 +81,7 @@ module ActiveRecord table = 'key_tests' - indexes = @connection.indexes(table).sort_by {|i| i.name} + indexes = @connection.indexes(table).sort_by(&:name) assert_equal 3,indexes.size index_a = indexes.select{|i| i.name == index_a_name}[0] diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 49cfafd7a5..3d97182356 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -92,7 +92,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me - ActiveRecord::Base.connection.add_timestamps :delete_me + ActiveRecord::Base.connection.add_timestamps :delete_me, null: true assert column_present?('delete_me', 'updated_at', 'datetime') assert column_present?('delete_me', 'created_at', 'datetime') ensure diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 62c5abbf41..d261e2db55 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -44,9 +44,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert !@connection.active? # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + @fixture_connections.each(&:verify!) end def test_successful_reconnection_after_timeout_with_manual_reconnect diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 675703caa1..f67f21fab1 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 799d927ee4..7f97b454bb 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -100,7 +100,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase gs = nil assert_nothing_raised { gs = Select.find(2).groups } assert_equal gs.length, 2 - assert(gs.collect{|x| x.id}.sort == [2, 3]) + assert(gs.collect(&:id).sort == [2, 3]) end # has_and_belongs_to_many with reserved-word table name @@ -109,7 +109,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase s = nil assert_nothing_raised { s = Distinct.find(1).selects } assert_equal s.length, 2 - assert(s.collect{|x|x.id}.sort == [1, 2]) + assert(s.collect(&:id).sort == [1, 2]) end # activerecord model introspection with reserved-word table and column names diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 1b7e60565d..47707b7d4f 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -51,7 +51,7 @@ module ActiveRecord table = 'key_tests' - indexes = @connection.indexes(table).sort_by {|i| i.name} + indexes = @connection.indexes(table).sort_by(&:name) assert_equal 3,indexes.size index_a = indexes.select{|i| i.name == index_a_name}[0] diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 7872f91943..aeebec034d 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -17,7 +17,6 @@ class PostgresqlByteaTest < ActiveRecord::TestCase end end @column = ByteaDataType.columns_hash['payload'] - assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)) end teardown do @@ -25,9 +24,17 @@ class PostgresqlByteaTest < ActiveRecord::TestCase end def test_column + assert @column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn) assert_equal :binary, @column.type end + def test_binary_columns_are_limitless_the_upper_limit_is_one_GB + assert_equal 'bytea', @connection.type_to_sql(:binary, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + @connection.type_to_sql :binary, 4294967295 + end + end + def test_type_cast_binary_converts_the_encoding assert @column diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb new file mode 100644 index 0000000000..ec1b446dab --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -0,0 +1,26 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class PGChangeSchemaTest < ActiveRecord::TestCase + attr_reader :connection + + def setup + super + @connection = ActiveRecord::Base.connection + connection.create_table(:strings) do |t| + t.string :somedate + end + end + + def teardown + connection.drop_table :strings + end + + def test_change_string_to_date + connection.change_column :strings, :somedate, :timestamp, using: 'CAST("somedate" AS timestamp)' + assert_equal :datetime, connection.columns(:strings).find { |c| c.name == 'somedate' }.type + end + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 7d179944d4..ab7fd3c6d5 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -177,9 +177,7 @@ module ActiveRecord "successfully querying with the same connection pid." # Repair all fixture connections so other tests won't break. - @fixture_connections.each do |c| - c.verify! - end + @fixture_connections.each(&:verify!) end def test_set_session_variable_true diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index a0a34e4b87..2c4839338c 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -94,6 +94,13 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert @first_oid.reload assert_equal new_value, @first_oid.obj_id end + + def test_text_columns_are_limitless_the_upper_limit_is_one_GB + assert_equal 'text', @connection.type_to_sql(:text, 100_000) + assert_raise ActiveRecord::ActiveRecordError do + @connection.type_to_sql :text, 4294967295 + end + end end class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index d99c4a292e..83cedc5a7b 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- require "cases/helper" require 'support/connection_helper' -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' class PostgresqlEnumTest < ActiveRecord::TestCase include ConnectionHelper diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 19053b6732..d2dd04b84b 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 1296eb72c0..6a9c6483fe 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -1,41 +1,38 @@ # encoding: utf-8 - require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' -class PostgresqlHstoreTest < ActiveRecord::TestCase - class Hstore < ActiveRecord::Base - self.table_name = 'hstores' +if ActiveRecord::Base.connection.supports_extensions? + class PostgresqlHstoreTest < ActiveRecord::TestCase + class Hstore < ActiveRecord::Base + self.table_name = 'hstores' - store_accessor :settings, :language, :timezone - end + store_accessor :settings, :language, :timezone + end - def setup - @connection = ActiveRecord::Base.connection + def setup + @connection = ActiveRecord::Base.connection - unless @connection.extension_enabled?('hstore') - @connection.enable_extension 'hstore' - @connection.commit_db_transaction - end + unless @connection.extension_enabled?('hstore') + @connection.enable_extension 'hstore' + @connection.commit_db_transaction + end - @connection.reconnect! + @connection.reconnect! - @connection.transaction do - @connection.create_table('hstores') do |t| - t.hstore 'tags', :default => '' - t.hstore 'payload', array: true - t.hstore 'settings' + @connection.transaction do + @connection.create_table('hstores') do |t| + t.hstore 'tags', :default => '' + t.hstore 'payload', array: true + t.hstore 'settings' + end end + @column = Hstore.columns_hash['tags'] end - @column = Hstore.columns_hash['tags'] - end - teardown do - @connection.execute 'drop table if exists hstores' - end + teardown do + @connection.execute 'drop table if exists hstores' + end - if ActiveRecord::Base.connection.supports_extensions? def test_hstore_included_in_extensions assert @connection.respond_to?(:extensions), "connection should have a list of extensions" assert @connection.extensions.include?('hstore'), "extension list should include hstore" @@ -315,10 +312,8 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase dupe = record.dup assert_equal({"one" => "two"}, dupe.tags.to_hash) end - end - - private + private def assert_array_cycle(array) # test creation x = Hstore.create!(payload: array) @@ -346,4 +341,5 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase x.reload assert_equal(hash, x.tags) end + end end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 86ba849445..340ca29c0e 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,10 +1,10 @@ # encoding: utf-8 - require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' +require 'support/schema_dumping_helper' module PostgresqlJSONSharedTestCases + include SchemaDumpingHelper + class JsonDataType < ActiveRecord::Base self.table_name = 'json_data_type' @@ -64,6 +64,11 @@ module PostgresqlJSONSharedTestCases JsonDataType.reset_column_information end + def test_schema_dumping + output = dump_table_schema("json_data_type") + assert_match(/t.#{column_type.to_s}\s+"payload",\s+default: {}/, output) + end + def test_cast_value_on_write x = JsonDataType.new payload: {"string" => "foo", :symbol => :bar} assert_equal({"string" => "foo", :symbol => :bar}, x.payload_before_type_cast) diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index a71c0dfb26..c3c696b871 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -304,11 +304,8 @@ module ActiveRecord end def test_substitute_at - bind = @connection.substitute_at(nil, 0) - assert_equal Arel.sql('$1'), bind - - bind = @connection.substitute_at(nil, 1) - assert_equal Arel.sql('$2'), bind + bind = @connection.substitute_at(nil) + assert_equal Arel.sql('$1'), bind.to_sql end def test_partial_index diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb new file mode 100644 index 0000000000..056a035622 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb @@ -0,0 +1,34 @@ +require "cases/helper" + +class PostgresqlRenameTableTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table :before_rename, force: true + end + + def teardown + @connection.execute 'DROP TABLE IF EXISTS "before_rename"' + @connection.execute 'DROP TABLE IF EXISTS "after_rename"' + end + + test "renaming a table also renames the primary key index" do + # sanity check + assert_equal 1, num_indices_named("before_rename_pkey") + assert_equal 0, num_indices_named("after_rename_pkey") + + @connection.rename_table :before_rename, :after_rename + + assert_equal 0, num_indices_named("before_rename_pkey") + assert_equal 1, num_indices_named("after_rename_pkey") + end + + private + + def num_indices_named(name) + @connection.execute(<<-SQL).values.length + SELECT 1 FROM "pg_index" + JOIN "pg_class" ON "pg_index"."indexrelid" = "pg_class"."oid" + WHERE "pg_class"."relname" = '#{name}' + SQL + end +end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 350cb3e065..92c9a13ee3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -412,7 +412,7 @@ class SchemaTest < ActiveRecord::TestCase 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} + indexes = @connection.indexes(TABLE_NAME).sort_by(&:name) assert_equal 4,indexes.size do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) diff --git a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb b/activerecord/test/cases/adapters/postgresql/sql_types_test.rb deleted file mode 100644 index d7d40f6385..0000000000 --- a/activerecord/test/cases/adapters/postgresql/sql_types_test.rb +++ /dev/null @@ -1,18 +0,0 @@ -require "cases/helper" - -class SqlTypesTest < ActiveRecord::TestCase - def test_binary_types - assert_equal 'bytea', type_to_sql(:binary, 100_000) - assert_raise ActiveRecord::ActiveRecordError do - type_to_sql :binary, 4294967295 - end - assert_equal 'text', type_to_sql(:text, 100_000) - assert_raise ActiveRecord::ActiveRecordError do - type_to_sql :text, 4294967295 - end - end - - def type_to_sql(*args) - ActiveRecord::Base.connection.type_to_sql(*args) - end -end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 376dc2490e..5753da1173 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -1,8 +1,6 @@ # encoding: utf-8 - require "cases/helper" -require 'active_record/base' -require 'active_record/connection_adapters/postgresql_adapter' +require 'support/schema_dumping_helper' module PostgresqlUUIDHelper def connection @@ -112,6 +110,8 @@ end class PostgresqlLargeKeysTest < ActiveRecord::TestCase include PostgresqlUUIDHelper + include SchemaDumpingHelper + def setup connection.create_table('big_serials', id: :bigserial) do |t| t.string 'name' @@ -119,10 +119,8 @@ class PostgresqlLargeKeysTest < ActiveRecord::TestCase end def test_omg - schema = StringIO.new - ActiveRecord::SchemaDumper.dump(connection, schema) - assert_match "create_table \"big_serials\", id: :bigserial, force: true", - schema.string + schema = dump_table_schema "big_serials" + assert_match "create_table \"big_serials\", id: :bigserial, force: true", schema end def teardown @@ -132,6 +130,7 @@ end class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase include PostgresqlUUIDHelper + include SchemaDumpingHelper class UUID < ActiveRecord::Base self.table_name = 'pg_uuids' @@ -191,17 +190,15 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase end def test_schema_dumper_for_uuid_primary_key - schema = StringIO.new - ActiveRecord::SchemaDumper.dump(connection, schema) - assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema.string) - assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema.string) + schema = dump_table_schema "pg_uuids" + assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema) + assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema) end def test_schema_dumper_for_uuid_primary_key_with_custom_default - schema = StringIO.new - ActiveRecord::SchemaDumper.dump(connection, schema) - assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema.string) - assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema.string) + schema = dump_table_schema "pg_uuids_2" + assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema) + assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema) end end end diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index f1d6119d2e..de6e35ef57 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' module ActiveRecord module ConnectionAdapters diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 8c1c22d3bf..9d09ff49c7 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -133,8 +133,8 @@ module ActiveRecord end def test_bind_value_substitute - bind_param = @conn.substitute_at('foo', 0) - assert_equal Arel.sql('?'), bind_param + bind_param = @conn.substitute_at('foo') + assert_equal Arel.sql('?'), bind_param.to_sql end def test_exec_no_binds @@ -327,11 +327,11 @@ module ActiveRecord def test_columns with_example_table do - columns = @conn.columns('ex').sort_by { |x| x.name } + columns = @conn.columns('ex').sort_by(&:name) assert_equal 2, columns.length - assert_equal %w{ id number }.sort, columns.map { |x| x.name } - assert_equal [nil, nil], columns.map { |x| x.default } - assert_equal [true, true], columns.map { |x| x.null } + assert_equal %w{ id number }.sort, columns.map(&:name) + assert_equal [nil, nil], columns.map(&:default) + assert_equal [true, true], columns.map(&:null) end end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index b6333240a8..025c9fe6f9 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -115,6 +115,15 @@ if ActiveRecord::Base.connection.supports_migrations? end end + def test_timestamps_without_null_is_deprecated_on_add_timestamps + assert_deprecated do + ActiveRecord::Schema.define do + create_table :has_timestamps + add_timestamps :has_timestamps + end + end + end + def test_no_deprecation_warning_from_timestamps_on_create_table assert_not_deprecated do ActiveRecord::Schema.define do diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 25555bd75c..17394cb6f7 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1,5 +1,6 @@ require 'cases/helper' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/company' require 'models/topic' @@ -57,6 +58,35 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end end + def test_default_scope_on_relations_is_not_cached + counter = 0 + + comments = Class.new(ActiveRecord::Base) { + self.table_name = 'comments' + self.inheritance_column = 'not_there' + + posts = Class.new(ActiveRecord::Base) { + self.table_name = 'posts' + self.inheritance_column = 'not_there' + + default_scope -> { + counter += 1 + where("id = :inc", :inc => counter) + } + + has_many :comments, :class => comments + } + belongs_to :post, :class => posts, :inverse_of => false + } + + assert_equal 0, counter + comment = comments.first + assert_equal 0, counter + sql = capture_sql { comment.post } + comment.reload + assert_not_equal sql, capture_sql { comment.post } + end + def test_proxy_assignment account = Account.find(1) assert_nothing_raised { account.firm = account.firm } diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index 5b7e462f64..a531e0e02c 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -3,6 +3,7 @@ require 'models/post' require 'models/author' require 'models/project' require 'models/developer' +require 'models/computer' require 'models/company' class AssociationCallbacksTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 0ff87d53ea..f571198079 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -70,9 +70,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase teardown do [Circle, Square, Triangle, PaintColor, PaintTexture, - ShapeExpression, NonPolyOne, NonPolyTwo].each do |c| - c.delete_all - end + ShapeExpression, NonPolyOne, NonPolyTwo].each(&:delete_all) end def generate_test_object_graphs diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 8234ee95be..4321a198ca 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -17,6 +17,7 @@ require 'models/subscriber' require 'models/subscription' require 'models/book' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/member' require 'models/membership' @@ -269,6 +270,14 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_three_level_nested_preloading_does_not_raise_exception_when_association_does_not_exist + post_id = Comment.where(author_id: nil).where.not(post_id: nil).first.post_id + + assert_nothing_raised do + Post.preload(:comments => [{:author => :essays}]).find(post_id) + end + end + def test_nested_loading_through_has_one_association aa = AuthorAddress.all.merge!(:includes => {:author => :posts}).find(author_addresses(:david_address).id) assert_equal aa.author.posts.count, aa.author.posts.length @@ -329,31 +338,31 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_limit comments = Comment.all.merge!(:includes => :post, :limit => 5, :order => 'comments.id').to_a assert_equal 5, comments.length - assert_equal [1,2,3,5,6], comments.collect { |c| c.id } + assert_equal [1,2,3,5,6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_conditions comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').to_a assert_equal 3, comments.length - assert_equal [5,6,7], comments.collect { |c| c.id } + assert_equal [5,6,7], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset comments = Comment.all.merge!(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').to_a assert_equal 3, comments.length - assert_equal [3,5,6], comments.collect { |c| c.id } + assert_equal [3,5,6], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions comments = Comment.all.merge!(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').to_a assert_equal 3, comments.length - assert_equal [6,7,8], comments.collect { |c| c.id } + assert_equal [6,7,8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array comments = Comment.all.merge!(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').to_a assert_equal 3, comments.length - assert_equal [6,7,8], comments.collect { |c| c.id } + assert_equal [6,7,8], comments.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name @@ -368,7 +377,7 @@ class EagerAssociationTest < ActiveRecord::TestCase comments = Comment.all.merge!(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').to_a end assert_equal 3, comments.length - assert_equal [5,6,7], comments.collect { |c| c.id } + assert_equal [5,6,7], comments.collect(&:id) assert_no_queries do comments.first.post end @@ -397,13 +406,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').to_a assert_equal 1, posts.length - assert_equal [1], posts.collect { |p| p.id } + assert_equal [1], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations posts = Post.all.merge!(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').to_a assert_equal 1, posts.length - assert_equal [2], posts.collect { |p| p.id } + assert_equal [2], posts.collect(&:id) end def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name @@ -536,13 +545,13 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_and_limit_and_conditions posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").to_a assert_equal 2, posts.size - assert_equal [4,5], posts.collect { |p| p.id } + assert_equal [4,5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array posts = Post.all.merge!(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").to_a assert_equal 2, posts.size - assert_equal [4,5], posts.collect { |p| p.id } + assert_equal [4,5], posts.collect(&:id) end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers @@ -903,6 +912,12 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_no_queries {assert_equal posts(:sti_comments), comment.post} end + def test_eager_association_with_scope_with_joins + assert_nothing_raised do + Post.includes(:very_special_comment_with_post_with_joins).to_a + end + end + def test_preconfigured_includes_with_has_many posts = authors(:david).posts_with_comments one = posts.detect { |p| p.id == 1 } diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 4c1fdfdd9a..9d373cd73b 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -3,6 +3,7 @@ require 'models/post' require 'models/comment' require 'models/project' require 'models/developer' +require 'models/computer' require 'models/company_in_module' class AssociationsExtensionsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 859310575e..aea9207bfe 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' require 'models/project' require 'models/company' require 'models/customer' @@ -78,9 +79,13 @@ class SubDeveloper < Developer :association_foreign_key => "developer_id" end +class DeveloperWithSymbolClassName < Developer + has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys +end + class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, - :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings + :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings, :computers def setup_data_for_habtm_case ActiveRecord::Base.connection.execute('delete from countries_treaties') @@ -550,7 +555,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_dynamic_find_all_should_respect_readonly_access projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?} - projects(:active_record).readonly_developers.each { |d| d.readonly? } + projects(:active_record).readonly_developers.each(&:readonly?) end def test_new_with_values_in_collection @@ -883,4 +888,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase child.special_projects << SpecialProject.new("name" => "Special Project") assert child.save, 'child object should be saved' end + + def test_habtm_with_reflection_using_class_name_and_fixtures + assert_not_nil Developer._reflections['shared_computers'] + # Checking the fixture for named association is important here, because it's the only way + # we've been able to reproduce this bug + assert_not_nil File.read(File.expand_path("../../../fixtures/developers.yml", __FILE__)).index("shared_computers") + assert_equal developers(:david).shared_computers.first, computers(:laptop) + end + + def test_with_symbol_class_name + assert_nothing_raised NoMethodError do + DeveloperWithSymbolClassName.new + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index e34b993029..c075872617 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' require 'models/project' require 'models/company' require 'models/contract' @@ -76,6 +77,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase dev.developer_projects.map(&:project_id).sort end + def test_default_scope_on_relations_is_not_cached + counter = 0 + posts = Class.new(ActiveRecord::Base) { + self.table_name = 'posts' + self.inheritance_column = 'not_there' + post = self + + comments = Class.new(ActiveRecord::Base) { + self.table_name = 'comments' + self.inheritance_column = 'not_there' + belongs_to :post, :class => post + default_scope -> { + counter += 1 + where("id = :inc", :inc => counter) + } + } + has_many :comments, :class => comments, :foreign_key => 'post_id' + } + assert_equal 0, counter + post = posts.first + assert_equal 0, counter + sql = capture_sql { post.comments.to_a } + post.comments.reset + assert_not_equal sql, capture_sql { post.comments.to_a } + end + def test_has_many_build_with_options college = College.create(name: 'UFMT') Student.create(active: true, college_id: college.id, name: 'Sarah') @@ -387,6 +414,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "Summit", Firm.all.merge!(:order => "id").first.clients_using_primary_key.first.name end + def test_update_all_on_association_accessed_before_save + firm = Firm.new(name: 'Firm') + firm.clients << Client.first + firm.save! + assert_equal firm.clients.count, firm.clients.update_all(description: 'Great!') + end + def test_belongs_to_sanity c = Client.new assert_nil c.firm, "belongs_to failed sanity check on new object" @@ -555,17 +589,21 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_with_bang_on_has_many_when_parent_is_new_raises - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create! :name=>"Whoever" end + + assert_equal "You cannot call create unless the parent is saved", error.message end def test_regular_create_on_has_many_when_parent_is_new_raises - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do firm = Firm.new firm.plain_clients.create :name=>"Whoever" end + + assert_equal "You cannot call create unless the parent is saved", error.message end def test_create_with_bang_on_has_many_raises_when_record_not_saved @@ -576,9 +614,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_with_bang_on_habtm_when_parent_is_new_raises - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do Developer.new("name" => "Aredridel").projects.create! end + + assert_equal "You cannot call create unless the parent is saved", error.message end def test_adding_a_mismatch_class @@ -1179,7 +1219,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !clients.empty?, "37signals has clients after load" destroyed = companies(:first_firm).clients_of_firm.destroy_all assert_equal clients.sort_by(&:id), destroyed.sort_by(&:id) - assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" + assert destroyed.all?(&:frozen?), "destroyed clients should be frozen" assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all" assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh" end @@ -1319,10 +1359,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !account.valid? assert !orig_accounts.empty? - assert_raise ActiveRecord::RecordNotSaved do + error = assert_raise ActiveRecord::RecordNotSaved do firm.accounts = [account] end + assert_equal orig_accounts, firm.accounts + assert_equal "Failed to replace accounts because one or more of the " \ + "new records could not be saved.", error.message end def test_replace_with_same_content @@ -1895,6 +1938,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb1, bulb2], car.all_bulbs.sort_by(&:id) end + test 'unscopes the default scope of associated model when used with include' do + car = Car.create! + bulb = Bulb.create! name: "other", car: car + + assert_equal bulb, Car.find(car.id).all_bulbs.first + assert_equal bulb, Car.includes(:all_bulbs).find(car.id).all_bulbs.first + end + test "raises RecordNotDestroyed when replaced child can't be destroyed" do car = Car.create! original_child = FailedBulb.create!(car: car) @@ -1955,4 +2006,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, car.bulbs.count assert_equal 1, car.tyres.count end + + test 'associations replace in memory when records have the same id' do + bulb = Bulb.create! + car = Car.create!(bulbs: [bulb]) + + new_bulb = Bulb.find(bulb.id) + new_bulb.name = "foo" + car.bulbs = [new_bulb] + + assert_equal "foo", car.bulbs.first.name + end + + test 'in memory replacement executes no queries' do + bulb = Bulb.create! + car = Car.create!(bulbs: [bulb]) + + new_bulb = Bulb.find(bulb.id) + + assert_no_queries do + car.bulbs = [new_bulb] + end + end + + test 'in memory replacements do not execute callbacks' do + raise_after_add = false + klass = Class.new(ActiveRecord::Base) do + self.table_name = :cars + has_many :bulbs, after_add: proc { raise if raise_after_add } + + def self.name + "Car" + end + end + bulb = Bulb.create! + car = klass.create!(bulbs: [bulb]) + + new_bulb = Bulb.find(bulb.id) + raise_after_add = true + + assert_nothing_raised do + car.bulbs = [new_bulb] + end + end + + test 'in memory replacements sets inverse instance' do + bulb = Bulb.create! + car = Car.create!(bulbs: [bulb]) + + new_bulb = Bulb.find(bulb.id) + car.bulbs = [new_bulb] + + assert_same car, new_bulb.car + end + + test 'in memory replacement maintains order' do + first_bulb = Bulb.create! + second_bulb = Bulb.create! + car = Car.create!(bulbs: [first_bulb, second_bulb]) + + same_bulb = Bulb.find(first_bulb.id) + car.bulbs = [second_bulb, same_bulb] + + assert_equal [first_bulb, second_bulb], car.bulbs + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index cddf1a1f72..589a232bdb 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -15,6 +15,7 @@ require 'models/toy' require 'models/contract' require 'models/company' require 'models/developer' +require 'models/computer' require 'models/subscriber' require 'models/book' require 'models/subscription' @@ -40,7 +41,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_preload_sti_rhs_class developers = Developer.includes(:firms).all.to_a assert_no_queries do - developers.each { |d| d.firms } + developers.each(&:firms) end end @@ -614,8 +615,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_create_on_new_record p = Post.new - assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") } - assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } + error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") } + assert_equal "You cannot call create unless the parent is saved", error.message + + error = assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } + assert_equal "You cannot call create unless the parent is saved", error.message end def test_associate_with_create_and_invalid_options diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d412b3168e..1a6d25f7d0 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/developer' +require 'models/computer' require 'models/project' require 'models/company' require 'models/ship' @@ -409,9 +410,11 @@ class HasOneAssociationsTest < ActiveRecord::TestCase pirate = pirates(:redbeard) new_ship = Ship.new - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = new_ship end + + assert_equal "Failed to save the new associated ship.", error.message assert_nil pirate.ship assert_nil new_ship.pirate_id end @@ -421,20 +424,25 @@ class HasOneAssociationsTest < ActiveRecord::TestCase pirate.ship.name = nil assert !pirate.ship.valid? - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = ships(:interceptor) end + assert_equal ships(:black_pearl), pirate.ship assert_equal pirate.id, pirate.ship.pirate_id + assert_equal "Failed to remove the existing associated ship. " + + "The record failed to save after its foreign key was set to nil.", error.message end def test_replacement_failure_due_to_new_record_should_raise_error pirate = pirates(:blackbeard) new_ship = Ship.new - assert_raise(ActiveRecord::RecordNotSaved) do + error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = new_ship end + + assert_equal "Failed to save the new associated ship.", error.message assert_equal ships(:black_pearl), pirate.ship assert_equal pirate.id, pirate.ship.pirate_id assert_equal pirate.id, ships(:black_pearl).reload.pirate_id diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 07cf65a760..9938302885 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -54,7 +54,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_find_with_implicit_inner_joins_without_select_does_not_imply_readonly authors = Author.joins(:posts) assert_not authors.empty?, "expected authors to be non-empty" - assert authors.none? {|a| a.readonly? }, "expected no authors to be readonly" + assert authors.none?(&:readonly?), "expected no authors to be readonly" end def test_find_with_implicit_inner_joins_honors_readonly_with_select diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index cace7ba142..140ccdac72 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -393,18 +393,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_one - assert_equal Tagging.find(1,2).sort_by { |t| t.id }, authors(:david).taggings_2 + assert_equal Tagging.find(1,2).sort_by(&:id), authors(:david).taggings_2 end def test_has_many_through_polymorphic_has_many - assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id } + assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by(&:id) end def test_include_has_many_through_polymorphic_has_many author = Author.includes(:taggings).find authors(:david).id expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do - assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id } + assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id) end end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 9b0cf4c18f..c6769edcbf 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,6 +1,7 @@ require "cases/helper" require 'models/computer' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/company' require 'models/categorization' @@ -47,7 +48,7 @@ class AssociationsTest < ActiveRecord::TestCase assert_kind_of Firm, firm firm.clear_association_cache - assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort + assert_equal Firm.find(1).clients.collect(&:name).sort, firm.clients.collect(&:name).sort end def test_clear_association_cache_new_record diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index c3e8ceb0da..731d433706 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,6 +1,7 @@ require "cases/helper" require 'models/minimalistic' require 'models/developer' +require 'models/computer' require 'models/auto_id' require 'models/boolean' require 'models/computer' diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index dc20c3c676..de63672963 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -109,7 +109,15 @@ module ActiveRecord test "fetch_value returns nil for unknown attributes" do attributes = attributes_with_uninitialized_key - assert_nil attributes.fetch_value(:wibble) + assert_nil attributes.fetch_value(:wibble) { "hello" } + end + + test "fetch_value returns nil for unknown attributes when types has a default" do + types = Hash.new(Type::Value.new) + builder = AttributeSet::Builder.new(types) + attributes = builder.build_from_database + + assert_nil attributes.fetch_value(:wibble) { "hello" } end test "fetch_value uses the given block for uninitialized attributes" do @@ -123,6 +131,15 @@ module ActiveRecord assert_nil attributes.fetch_value(:bar) end + test "the primary_key is always initialized" do + builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo) + attributes = builder.build_from_database + + assert attributes.key?(:foo) + assert_equal [:foo], attributes.keys + assert attributes[:foo].initialized? + end + class MyType def type_cast_from_user(value) return if value.nil? diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 734fd5fe18..04d5a2869c 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -4,6 +4,7 @@ require 'models/comment' require 'models/company' require 'models/customer' require 'models/developer' +require 'models/computer' require 'models/invoice' require 'models/line_item' require 'models/order' @@ -773,13 +774,13 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_should_destroy_has_many_as_part_of_the_save_transaction_if_they_were_marked_for_destruction 2.times { |i| @pirate.birds.create!(:name => "birds_#{i}") } - assert !@pirate.birds.any? { |child| child.marked_for_destruction? } + assert !@pirate.birds.any?(&:marked_for_destruction?) - @pirate.birds.each { |child| child.mark_for_destruction } + @pirate.birds.each(&:mark_for_destruction) klass = @pirate.birds.first.class ids = @pirate.birds.map(&:id) - assert @pirate.birds.all? { |child| child.marked_for_destruction? } + assert @pirate.birds.all?(&:marked_for_destruction?) ids.each { |id| assert klass.find_by_id(id) } @pirate.save @@ -813,14 +814,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.birds.each { |bird| bird.name = '' } assert !@pirate.valid? - @pirate.birds.each { |bird| bird.destroy } + @pirate.birds.each(&:destroy) assert @pirate.valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many @pirate.birds.create!(:name => "birds_1") - @pirate.birds.each { |bird| bird.mark_for_destruction } + @pirate.birds.each(&:mark_for_destruction) assert @pirate.save @pirate.birds.each { |bird| bird.expects(:destroy).never } @@ -887,7 +888,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase association_name_with_callbacks = "birds_with_#{callback_type}_callbacks" @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") - @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction } + @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction) child_id = @pirate.send(association_name_with_callbacks).first.id @pirate.ship_log.clear @@ -905,8 +906,8 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_should_destroy_habtm_as_part_of_the_save_transaction_if_they_were_marked_for_destruction 2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") } - assert !@pirate.parrots.any? { |parrot| parrot.marked_for_destruction? } - @pirate.parrots.each { |parrot| parrot.mark_for_destruction } + assert !@pirate.parrots.any?(&:marked_for_destruction?) + @pirate.parrots.each(&:mark_for_destruction) assert_no_difference "Parrot.count" do @pirate.save @@ -939,14 +940,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.parrots.each { |parrot| parrot.name = '' } assert !@pirate.valid? - @pirate.parrots.each { |parrot| parrot.destroy } + @pirate.parrots.each(&:destroy) assert @pirate.valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm @pirate.parrots.create!(:name => "parrots_1") - @pirate.parrots.each { |parrot| parrot.mark_for_destruction } + @pirate.parrots.each(&:mark_for_destruction) assert @pirate.save Pirate.transaction do @@ -991,7 +992,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase association_name_with_callbacks = "parrots_with_#{callback_type}_callbacks" @pirate.send(association_name_with_callbacks).create!(:name => "Crowe the One-Eyed") - @pirate.send(association_name_with_callbacks).each { |c| c.mark_for_destruction } + @pirate.send(association_name_with_callbacks).each(&:mark_for_destruction) child_id = @pirate.send(association_name_with_callbacks).first.id @pirate.ship_log.clear diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 3675401555..098d5b8451 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -10,6 +10,7 @@ require 'models/category' require 'models/company' require 'models/customer' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/default' require 'models/auto_id' diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 0bc7ee6d64..c4634d11e2 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -31,9 +31,9 @@ module ActiveRecord if ActiveRecord::Base.connection.supports_statement_cache? def test_binds_are_logged - sub = @connection.substitute_at(@pk, 0) + sub = @connection.substitute_at(@pk) binds = [[@pk, 1]] - sql = "select * from topics where id = #{sub}" + sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) @@ -42,9 +42,9 @@ module ActiveRecord end def test_binds_are_logged_after_type_cast - sub = @connection.substitute_at(@pk, 0) + sub = @connection.substitute_at(@pk) binds = [[@pk, "3"]] - sql = "select * from topics where id = #{sub}" + sql = "select * from topics where id = #{sub.to_sql}" @connection.exec_query(sql, 'SQL', binds) diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index c8f56e3c73..e3c3c2fcdf 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -1,4 +1,6 @@ require "cases/helper" +require 'models/developer' +require 'models/computer' class CallbackDeveloper < ActiveRecord::Base self.table_name = 'developers' @@ -441,7 +443,8 @@ class CallbacksTest < ActiveRecord::TestCase david = ImmutableDeveloper.find(1) assert david.valid? assert !david.save - assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } + assert_equal exc.record, david david = ImmutableDeveloper.find(1) david.salary = 10_000_000 @@ -475,7 +478,8 @@ class CallbacksTest < ActiveRecord::TestCase def test_before_destroy_returning_false david = ImmutableDeveloper.find(1) assert !david.destroy - assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } + assert_equal exc.record, david assert_not_nil ImmutableDeveloper.find_by_id(1) someone = CallbackCancellationDeveloper.find(1) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index dc73faa5be..a607c1319f 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -10,6 +10,7 @@ require 'models/reply' require 'models/entrant' require 'models/project' require 'models/developer' +require 'models/computer' require 'models/customer' require 'models/toy' require 'models/matey' @@ -933,7 +934,7 @@ class FinderTest < ActiveRecord::TestCase joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id'). where('project_id=1').to_a assert_equal 3, developers_on_project_one.length - developer_names = developers_on_project_one.map { |d| d.name } + developer_names = developers_on_project_one.map(&:name) assert developer_names.include?('David') assert developer_names.include?('Jamis') end @@ -988,7 +989,7 @@ class FinderTest < ActiveRecord::TestCase end def test_select_values - assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } + assert_equal ["1","2","3","4","5","6","7","8","9", "10", "11"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map!(&:to_s) assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy", "Ex Nihilo Part Deux", "Apex"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") end @@ -1014,7 +1015,7 @@ class FinderTest < ActiveRecord::TestCase where(client_of: [2, 1, nil], name: ['37signals', 'Summit', 'Microsoft']). order('client_of DESC'). - map { |x| x.client_of } + map(&:client_of) assert client_of.include?(nil) assert_equal [2, 1].sort, client_of.compact.sort @@ -1024,7 +1025,7 @@ class FinderTest < ActiveRecord::TestCase client_of = Company. where(client_of: [nil]). order('client_of DESC'). - map { |x| x.client_of } + map(&:client_of) assert_equal [], client_of.compact end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 7141d3ee7f..9edeb8b47f 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -5,11 +5,13 @@ require 'models/admin/randomly_named_c1' require 'models/admin/user' require 'models/binary' require 'models/book' +require 'models/bulb' require 'models/category' require 'models/company' require 'models/computer' require 'models/course' require 'models/developer' +require 'models/computer' require 'models/joke' require 'models/matey' require 'models/parrot' @@ -852,3 +854,16 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name end end + +class FixturesWithDefaultScopeTest < ActiveRecord::TestCase + fixtures :bulbs + + test "inserts fixtures excluded by a default scope" do + assert_equal 1, Bulb.count + assert_equal 2, Bulb.unscoped.count + end + + test "allows access to fixtures excluded by a default scope" do + assert_equal "special", bulbs(:special).name + end +end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 792950d24d..0338669016 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -22,7 +22,7 @@ class InheritanceTest < ActiveRecord::TestCase company = Company.first company = company.dup company.extend(Module.new { - def read_attribute(name) + def _read_attribute(name) return ' ' if name == 'type' super end diff --git a/activerecord/test/cases/integration_test.rb b/activerecord/test/cases/integration_test.rb index dfb8a608cb..0021988083 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -3,6 +3,7 @@ require 'cases/helper' require 'models/company' require 'models/developer' +require 'models/computer' require 'models/owner' require 'models/pet' diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 777a48ad14..6613783fba 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -213,6 +213,12 @@ module ActiveRecord t.rename :bar, :baz end end + + def test_table_name_set + with_change_table do |t| + assert_equal :delete_me, t.name + end + end end end end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 77a752f050..62186e13a5 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -25,30 +25,30 @@ module ActiveRecord if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_column_positioning - assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } + assert_equal %w(first second third), conn.columns(:testings).map(&:name) end def test_add_column_with_positioning conn.add_column :testings, :new_col, :integer - assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } + assert_equal %w(first second third new_col), conn.columns(:testings).map(&:name) end def test_add_column_with_positioning_first conn.add_column :testings, :new_col, :integer, :first => true - assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } + assert_equal %w(new_col first second third), conn.columns(:testings).map(&:name) end def test_add_column_with_positioning_after conn.add_column :testings, :new_col, :integer, :after => :first - assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } + assert_equal %w(first new_col second third), conn.columns(:testings).map(&:name) end def test_change_column_with_positioning conn.change_column :testings, :second, :integer, :first => true - assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } + assert_equal %w(second first third), conn.columns(:testings).map(&:name) conn.change_column :testings, :second, :integer, :after => :third - assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + assert_equal %w(first third second), conn.columns(:testings).map(&:name) end end end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 406dd70c37..51e21528c2 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -17,11 +17,11 @@ module ActiveRecord setup do @connection = ActiveRecord::Base.connection - @connection.create_table "rockets" do |t| + @connection.create_table "rockets", force: true do |t| t.string :name end - @connection.create_table "astronauts" do |t| + @connection.create_table "astronauts", force: true do |t| t.string :name t.references :rocket end @@ -162,6 +162,14 @@ module ActiveRecord assert_equal [], @connection.foreign_keys("astronauts") end + def test_remove_foreign_key_by_symbol_column + @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id + + assert_equal 1, @connection.foreign_keys("astronauts").size + @connection.remove_foreign_key :astronauts, column: :rocket_id + assert_equal [], @connection.foreign_keys("astronauts") + end + def test_remove_foreign_key_by_name @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk" diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index ac932378fd..b23b9a679f 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -36,6 +36,20 @@ module ActiveRecord assert connection.index_name_exists?(table_name, 'new_idx', true) end + def test_rename_index_too_long + too_long_index_name = good_index_name + 'x' + # keep the names short to make Oracle and similar behave + connection.add_index(table_name, [:foo], :name => 'old_idx') + e = assert_raises(ArgumentError) { + connection.rename_index(table_name, 'old_idx', too_long_index_name) + } + assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) + + # if the adapter doesn't support the indexes call, pick defaults that let the test pass + assert connection.index_name_exists?(table_name, 'old_idx', false) + end + + def test_double_add_index connection.add_index(table_name, [:foo], :name => 'some_idx') assert_raises(ArgumentError) { diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 6b7a0a9000..3192b797b4 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -5,6 +5,7 @@ require 'bigdecimal/util' require 'models/person' require 'models/topic' require 'models/developer' +require 'models/computer' require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" require MIGRATIONS_ROOT + "/rename/1_we_need_things" diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index f05ca900aa..c0daa83e9c 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -149,7 +149,7 @@ class MigratorTest < ActiveRecord::TestCase def test_up_calls_up migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] ActiveRecord::Migrator.new(:up, migrations).migrate - assert migrations.all? { |m| m.went_up } + assert migrations.all?(&:went_up) assert migrations.all? { |m| !m.went_down } assert_equal 2, ActiveRecord::Migrator.current_version end @@ -160,7 +160,7 @@ class MigratorTest < ActiveRecord::TestCase migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] ActiveRecord::Migrator.new(:down, migrations).migrate assert migrations.all? { |m| !m.went_up } - assert migrations.all? { |m| m.went_down } + assert migrations.all?(&:went_down) assert_equal 0, ActiveRecord::Migrator.current_version end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index e87773df94..6f65bf80eb 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/company_in_module' require 'models/shop' require 'models/developer' +require 'models/computer' class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers, :collections, :products, :variants diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index cf96c3fccf..5c7e8a65d2 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -13,7 +13,7 @@ require 'active_support/hash_with_indifferent_access' class TestNestedAttributesInGeneral < ActiveRecord::TestCase teardown do - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) end def test_base_should_have_an_empty_nested_attributes_options @@ -300,13 +300,13 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => false, :reject_if => proc(&:empty?) @pirate.update(ship_attributes: { id: @pirate.ship.id, _destroy: '1' }) assert_equal @ship, @pirate.reload.ship - Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) end def test_should_also_work_with_a_HashWithIndifferentAccess @@ -494,12 +494,12 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_not_destroy_an_existing_record_if_allow_destroy_is_false - Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc { |attributes| attributes.empty? } + Ship.accepts_nested_attributes_for :pirate, :allow_destroy => false, :reject_if => proc(&:empty?) @ship.update(pirate_attributes: { id: @ship.pirate.id, _destroy: '1' }) assert_nothing_raised(ActiveRecord::RecordNotFound) { @ship.pirate.reload } ensure - Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + Ship.accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?) end def test_should_work_with_update_as_well @@ -855,7 +855,7 @@ end module NestedAttributesLimitTests def teardown - Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + Pirate.accepts_nested_attributes_for :parrots, :allow_destroy => true, :reject_if => proc(&:empty?) end def test_limit_with_less_records diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 2170fe6118..6fc4731f01 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -8,6 +8,7 @@ require 'models/reply' require 'models/category' require 'models/company' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/minimalistic' require 'models/warehouse_thing' @@ -126,7 +127,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_difference('Topic.count', -topics_by_mary.size) do destroyed = Topic.destroy_all(conditions).sort_by(&:id) assert_equal topics_by_mary, destroyed - assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen" + assert destroyed.all?(&:frozen?), "destroyed topics should be frozen" end end @@ -136,7 +137,7 @@ class PersistenceTest < ActiveRecord::TestCase assert_difference('Client.count', -2) do destroyed = Client.destroy([2, 3]).sort_by(&:id) assert_equal clients, destroyed - assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen" + assert destroyed.all?(&:frozen?), "destroyed clients should be frozen" end end diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 8eea10143f..98888150a8 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -13,7 +13,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase teardown do ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) - @per_test_teardown.each {|td| td.call } + @per_test_teardown.each(&:call) end # Will deadlock due to lack of Monitor timeouts in 1.9 diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index 2afd25c989..1c919f0b57 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -3,6 +3,7 @@ require 'models/author' require 'models/post' require 'models/comment' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/reader' require 'models/person' @@ -22,9 +23,15 @@ class ReadOnlyTest < ActiveRecord::TestCase assert !dev.save dev.name = 'Forbidden.' end - assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } - assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! } - assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy } + + e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save } + assert_equal "Developer is marked as readonly", e.message + + e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! } + assert_equal "Developer is marked as readonly", e.message + + e = assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy } + assert_equal "Developer is marked as readonly", e.message end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 84abaf0291..e86b892a0a 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -50,13 +50,13 @@ class ReflectionTest < ActiveRecord::TestCase end def test_columns_are_returned_in_the_order_they_were_declared - column_names = Topic.columns.map { |column| column.name } + column_names = Topic.columns.map(&:name) assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count unique_replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns - content_column_names = content_columns.map {|column| column.name} + content_column_names = content_columns.map(&:name) assert_equal 13, content_columns.length assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort end @@ -209,6 +209,10 @@ class ReflectionTest < ActiveRecord::TestCase assert_not_equal Object.new, Firm._reflections['clients'] end + def test_reflections_should_return_keys_as_strings + assert Category.reflections.keys.all? { |key| key.is_a? String }, "Model.reflections is expected to return string for keys" + end + def test_has_and_belongs_to_many_reflection assert_equal :has_and_belongs_to_many, Category.reflections['posts'].macro assert_equal :posts, Category.reflect_on_all_associations(:has_and_belongs_to_many).first.name diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index 8f40a1890d..eb76ef6328 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -2,6 +2,7 @@ require 'cases/helper' require 'models/author' require 'models/comment' require 'models/developer' +require 'models/computer' require 'models/post' require 'models/project' require 'models/rating' diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 410b5ba5a3..fc9637a167 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -7,6 +7,7 @@ require 'models/comment' require 'models/author' require 'models/entrant' require 'models/developer' +require 'models/computer' require 'models/reply' require 'models/company' require 'models/bird' @@ -248,7 +249,7 @@ class RelationTest < ActiveRecord::TestCase def test_finding_with_reorder topics = Topic.order('author_name').order('title').reorder('id').to_a - topics_titles = topics.map{ |t| t.title } + topics_titles = topics.map(&:title) assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day', 'The Fifth Topic of the day'], topics_titles end @@ -440,7 +441,7 @@ class RelationTest < ActiveRecord::TestCase where('project_id=1').to_a assert_equal 3, developers_on_project_one.length - developer_names = developers_on_project_one.map { |d| d.name } + developer_names = developers_on_project_one.map(&:name) assert developer_names.include?('David') assert developer_names.include?('Jamis') end @@ -651,8 +652,8 @@ class RelationTest < ActiveRecord::TestCase expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do - assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id } - assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id } + assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id) + assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id) end authors = Author.all @@ -1702,7 +1703,7 @@ class RelationTest < ActiveRecord::TestCase end def test_unscope_removes_binds - left = Post.where(id: Arel::Nodes::BindParam.new('?')) + left = Post.where(id: Arel::Nodes::BindParam.new) column = Post.columns_hash['id'] left.bind_values += [[column, 20]] diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 2241c41f36..8731531f54 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -162,16 +162,6 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "schema_migrations"}, output end - def test_schema_dump_illegal_ignored_table_value - stream = StringIO.new - old_ignore_tables, ActiveRecord::SchemaDumper.ignore_tables = ActiveRecord::SchemaDumper.ignore_tables, [5] - assert_raise(StandardError) do - ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) - end - ensure - ActiveRecord::SchemaDumper.ignore_tables = old_ignore_tables - end - def test_schema_dumps_index_columns_in_right_order index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip if current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) @@ -271,13 +261,6 @@ class SchemaDumperTest < ActiveRecord::TestCase end end - def test_schema_dump_includes_json_shorthand_definition - output = standard_dump - if %r{create_table "postgresql_json_data_type"} =~ output - assert_match %r|t.json "json_data", default: {}|, output - end - end - def test_schema_dump_includes_inet_shorthand_definition output = standard_dump if %r{create_table "postgresql_network_addresses"} =~ output diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index a5c4404175..5bdebe6ac7 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -2,13 +2,14 @@ require 'cases/helper' require 'models/post' require 'models/comment' require 'models/developer' +require 'models/computer' class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts, :comments def test_default_scope - expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary } + expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect(&:salary) + received = DeveloperOrderedBySalary.all.collect(&:salary) assert_equal expected, received end @@ -85,14 +86,14 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_scope_overwrites_default - expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect { |dev| dev.name } - received = DeveloperOrderedBySalary.by_name.to_a.collect { |dev| dev.name } + expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect(&:name) + received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name) assert_equal expected, received end def test_reorder_overrides_default_scope_order - expected = Developer.order('name DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name } + expected = Developer.order('name DESC').collect(&:name) + received = DeveloperOrderedBySalary.reorder('name DESC').collect(&:name) assert_equal expected, received end @@ -145,34 +146,34 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_multiple_where_clauses - expected = Developer.order('salary DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name } + expected = Developer.order('salary DESC').collect(&:name) + received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect(&:name) assert_equal expected, received end def test_unscope_string_where_clauses_involved dev_relation = Developer.order('salary DESC').where("created_at > ?", 1.year.ago) - expected = dev_relation.collect { |dev| dev.name } + expected = dev_relation.collect(&:name) dev_ordered_relation = DeveloperOrderedBySalary.where(name: 'Jamis').where("created_at > ?", 1.year.ago) - received = dev_ordered_relation.unscope(where: [:name]).collect { |dev| dev.name } + received = dev_ordered_relation.unscope(where: [:name]).collect(&:name) assert_equal expected, received end def test_unscope_with_grouping_attributes - expected = Developer.order('salary DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name } + expected = Developer.order('salary DESC').collect(&:name) + received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name) assert_equal expected, received - expected_2 = Developer.order('salary DESC').collect { |dev| dev.name } - received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name } + expected_2 = Developer.order('salary DESC').collect(&:name) + received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name) assert_equal expected_2, received_2 end def test_unscope_with_limit_in_query - expected = Developer.order('salary DESC').collect { |dev| dev.name } - received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name } + expected = Developer.order('salary DESC').collect(&:name) + received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name) assert_equal expected, received end @@ -182,42 +183,42 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_unscope_reverse_order - expected = Developer.all.collect { |dev| dev.name } - received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name } + expected = Developer.all.collect(&:name) + received = Developer.order('salary DESC').reverse_order.unscope(:order).collect(&:name) assert_equal expected, received end def test_unscope_select - expected = Developer.order('salary ASC').collect { |dev| dev.name } - received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect { |dev| dev.name } + expected = Developer.order('salary ASC').collect(&:name) + received = Developer.order('salary DESC').reverse_order.select(:name).unscope(:select).collect(&:name) assert_equal expected, received - expected_2 = Developer.all.collect { |dev| dev.id } - received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id } + expected_2 = Developer.all.collect(&:id) + received_2 = Developer.select(:name).unscope(:select).collect(&:id) assert_equal expected_2, received_2 end def test_unscope_offset - expected = Developer.all.collect { |dev| dev.name } - received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name } + expected = Developer.all.collect(&:name) + received = Developer.offset(5).unscope(:offset).collect(&:name) assert_equal expected, received end def test_unscope_joins_and_select_on_developers_projects - expected = Developer.all.collect { |dev| dev.name } - received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name } + expected = Developer.all.collect(&:name) + received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect(&:name) assert_equal expected, received end def test_unscope_includes - expected = Developer.all.collect { |dev| dev.name } - received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name } + expected = Developer.all.collect(&:name) + received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name) assert_equal expected, received end def test_unscope_having - expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name } - received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name } + expected = DeveloperOrderedBySalary.all.collect(&:name) + received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect(&:name) assert_equal expected, received end @@ -280,8 +281,8 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_order_in_default_scope_should_not_prevail - expected = Developer.all.merge!(order: 'salary desc').to_a.collect { |dev| dev.salary } - received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect { |dev| dev.salary } + expected = Developer.all.merge!(order: 'salary desc').to_a.collect(&:salary) + received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect(&:salary) assert_equal expected, received end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index c2816c3670..41f3449828 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -5,6 +5,7 @@ require 'models/comment' require 'models/reply' require 'models/author' require 'models/developer' +require 'models/computer' class NamedScopingTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 73835c85a8..d7bcbf6203 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/post' require 'models/author' require 'models/developer' +require 'models/computer' require 'models/project' require 'models/comment' require 'models/category' diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index eb44c4a83c..5ba17359f0 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -43,7 +43,7 @@ module ActiveRecord patterns_to_match.each do |pattern| failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql } end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" end def assert_queries(num = 1, options = {}) diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index abf6becc17..db474c63a4 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -1,6 +1,7 @@ require 'cases/helper' require 'support/ddl_helper' require 'models/developer' +require 'models/computer' require 'models/owner' require 'models/pet' require 'models/toy' diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 5cccf2dda5..72c07d8aea 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -2,6 +2,7 @@ require "cases/helper" require 'models/topic' require 'models/reply' require 'models/developer' +require 'models/computer' require 'models/book' require 'models/author' require 'models/post' @@ -12,7 +13,7 @@ class TransactionTest < ActiveRecord::TestCase fixtures :topics, :developers, :authors, :posts def setup - @first, @second = Topic.find(1, 2).sort_by { |t| t.id } + @first, @second = Topic.find(1, 2).sort_by(&:id) end def test_persisted_in_a_model_with_custom_primary_key_after_failed_save @@ -698,7 +699,7 @@ if current_adapter?(:PostgreSQLAdapter) end end - threads.each { |t| t.join } + threads.each(&:join) end end @@ -746,7 +747,7 @@ if current_adapter?(:PostgreSQLAdapter) Developer.connection.close end - threads.each { |t| t.join } + threads.each(&:join) end assert_equal original_salary, Developer.find(1).salary diff --git a/activerecord/test/cases/type/integer_test.rb b/activerecord/test/cases/type/integer_test.rb index 53d6a3a6aa..5942f77e18 100644 --- a/activerecord/test/cases/type/integer_test.rb +++ b/activerecord/test/cases/type/integer_test.rb @@ -68,7 +68,7 @@ module ActiveRecord end end - test "very large numbers are in range" do + test "very large numbers are out of range" do assert_raises(::RangeError) do Integer.new.type_cast_from_user("9999999999999999999999999999999") end diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb index 420177ed49..4d78f287f1 100644 --- a/activerecord/test/cases/type/string_test.rb +++ b/activerecord/test/cases/type/string_test.rb @@ -4,8 +4,8 @@ module ActiveRecord class StringTypeTest < ActiveRecord::TestCase test "type casting" do type = Type::String.new - assert_equal "1", type.type_cast_from_user(true) - assert_equal "0", type.type_cast_from_user(false) + assert_equal "t", type.type_cast_from_user(true) + assert_equal "f", type.type_cast_from_user(false) assert_equal "123", type.type_cast_from_user(123) end diff --git a/activerecord/test/cases/type/type_map_test.rb b/activerecord/test/cases/type/type_map_test.rb index 4e32f92dd0..172c6dfc4c 100644 --- a/activerecord/test/cases/type/type_map_test.rb +++ b/activerecord/test/cases/type/type_map_test.rb @@ -124,6 +124,53 @@ module ActiveRecord assert_equal mapping.lookup(3), 'string' assert_kind_of Type::Value, mapping.lookup(4) end + + def test_fetch + mapping = TypeMap.new + mapping.register_type(1, "string") + + assert_equal "string", mapping.fetch(1) { "int" } + assert_equal "int", mapping.fetch(2) { "int" } + end + + def test_fetch_yields_args + mapping = TypeMap.new + + assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") } + assert_equal "bar-1-2-3", mapping.fetch("bar", 1, 2, 3) { |*args| args.join("-") } + end + + def test_fetch_memoizes + mapping = TypeMap.new + + looked_up = false + mapping.register_type(1) do + fail if looked_up + looked_up = true + "string" + end + + assert_equal "string", mapping.fetch(1) + assert_equal "string", mapping.fetch(1) + end + + def test_fetch_memoizes_on_args + mapping = TypeMap.new + mapping.register_type("foo") { |*args| args.join("-") } + + assert_equal "foo-1-2-3", mapping.fetch("foo", 1, 2, 3) { |*args| args.join("-") } + assert_equal "foo-2-3-4", mapping.fetch("foo", 2, 3, 4) { |*args| args.join("-") } + end + + def test_register_clears_cache + mapping = TypeMap.new + + mapping.register_type(1, "string") + mapping.lookup(1) + mapping.register_type(1, "int") + + assert_equal "int", mapping.lookup(1) + end end end end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 18221cc73d..c6b58d469d 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -41,7 +41,7 @@ class TopicWithUniqEvent < Topic end class UniquenessValidationTest < ActiveRecord::TestCase - fixtures :topics, 'warehouse-things', :developers + fixtures :topics, 'warehouse-things' repair_validations(Topic, Reply) diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb index 2bbf0f23b3..b30666d876 100644 --- a/activerecord/test/cases/validations_repair_helper.rb +++ b/activerecord/test/cases/validations_repair_helper.rb @@ -5,9 +5,7 @@ module ActiveRecord module ClassMethods def repair_validations(*model_classes) teardown do - model_classes.each do |k| - k.clear_validators! - end + model_classes.each(&:clear_validators!) end end end @@ -15,9 +13,7 @@ module ActiveRecord def repair_validations(*model_classes) yield if block_given? ensure - model_classes.each do |k| - k.clear_validators! - end + model_classes.each(&:clear_validators!) end end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 55804f9576..db8159eff8 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -4,6 +4,7 @@ require 'models/topic' require 'models/reply' require 'models/person' require 'models/developer' +require 'models/computer' require 'models/parrot' require 'models/company' diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml index ce30cff9e7..e3b55d640e 100644 --- a/activerecord/test/config.example.yml +++ b/activerecord/test/config.example.yml @@ -69,12 +69,6 @@ connections: username: rails encoding: utf8 - openbase: - arunit: - username: admin - arunit2: - username: admin - oracle: arunit: adapter: oracle_enhanced diff --git a/activerecord/test/fixtures/bulbs.yml b/activerecord/test/fixtures/bulbs.yml new file mode 100644 index 0000000000..e5ce2b796c --- /dev/null +++ b/activerecord/test/fixtures/bulbs.yml @@ -0,0 +1,5 @@ +defaulty: + name: defaulty + +special: + name: special diff --git a/activerecord/test/fixtures/computers.yml b/activerecord/test/fixtures/computers.yml index 7281a4d768..ad5ae2ec71 100644 --- a/activerecord/test/fixtures/computers.yml +++ b/activerecord/test/fixtures/computers.yml @@ -3,3 +3,8 @@ workstation: system: 'Linux' developer: 1 extendedWarranty: 1 + +laptop: + system: 'MacOS 1' + developer: 1 + extendedWarranty: 1 diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml index 1a74563dc6..54b74e7a74 100644 --- a/activerecord/test/fixtures/developers.yml +++ b/activerecord/test/fixtures/developers.yml @@ -2,6 +2,7 @@ david: id: 1 name: David salary: 80000 + shared_computers: laptop jamis: id: 2 diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index 7e8e82542f..afe4b3d707 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -2,7 +2,7 @@ class Customer < ActiveRecord::Base cattr_accessor :gps_conversion_was_run composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true - composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money } + composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new(&:to_money) composed_of :gps_location, :allow_nil => true composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location), :converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)} diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 3627cfdd09..d2a5a7fc49 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -15,6 +15,8 @@ class Developer < ActiveRecord::Base accepts_nested_attributes_for :projects + has_and_belongs_to_many :shared_computers, class_name: "Computer" + has_and_belongs_to_many :projects_extended_by_name, -> { extending(DeveloperProjectsAssociationExtension) }, :class_name => "Project", diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 90a3c3ecee..641a33f9be 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -36,8 +36,8 @@ class Pirate < ActiveRecord::Base has_one :foo_bulb, -> { where :name => 'foo' }, :foreign_key => :car_id, :class_name => "Bulb" - accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } - accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc(&:empty?) + accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc(&:empty?) accepts_nested_attributes_for :update_only_ship, :update_only => true accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks, :birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 36cf221d45..a9996e5236 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -78,6 +78,7 @@ class Post < ActiveRecord::Base has_one :very_special_comment has_one :very_special_comment_with_post, -> { includes(:post) }, :class_name => "VerySpecialComment" + has_one :very_special_comment_with_post_with_joins, -> { joins(:post).order('posts.id') }, class_name: "VerySpecialComment" has_many :special_comments has_many :nonexistant_comments, -> { where 'comments.id < 0' }, :class_name => 'Comment' diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index 77a4728d0b..5f618a50d2 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -6,7 +6,7 @@ class Ship < ActiveRecord::Base has_many :parts, :class_name => 'ShipPart' accepts_nested_attributes_for :parts, :allow_destroy => true - accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } + accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc(&:empty?) accepts_nested_attributes_for :update_only_pirate, :update_only => true validates_presence_of :name diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index e9294a11b9..7c3b170c08 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,9 @@ ActiveRecord::Schema.define do - %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_uuids postgresql_ltrees - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_citext).each do |table_name| + %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times + postgresql_network_addresses postgresql_uuids postgresql_ltrees postgresql_oids postgresql_xml_data_type defaults + geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent + postgresql_citext).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -108,15 +110,6 @@ _SQL _SQL end - if 't' == select_value("select 'json'=ANY(select typname from pg_type)") - execute <<_SQL - CREATE TABLE postgresql_json_data_type ( - id SERIAL PRIMARY KEY, - json_data json default '{}'::json - ); -_SQL - end - execute <<_SQL CREATE TABLE postgresql_numbers ( id SERIAL PRIMARY KEY, diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 539a0d2a49..720a127585 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -228,6 +228,11 @@ ActiveRecord::Schema.define do t.integer :extendedWarranty, null: false end + create_table :computers_developers, id: false, force: true do |t| + t.references :computer + t.references :developer + end + create_table :contracts, force: true do |t| t.integer :developer_id t.integer :company_id |