diff options
Diffstat (limited to 'activerecord')
215 files changed, 3771 insertions, 2750 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 89a12d4223..d73c0859ee 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,457 +1,3 @@ -* Add support for PostgreSQL operator classes to `add_index`. - Example: - add_index :users, :name, using: :gist, opclass: { name: :gist_trgm_ops } - - *Greg Navis* - -* Don't allow scopes to be defined which conflict with instance methods on `Relation`. - - Fixes #31120. - - *kinnrot* - - -## Rails 5.2.0.beta2 (November 28, 2017) ## - -* No changes. - - -## Rails 5.2.0.beta1 (November 27, 2017) ## - -* Add new error class `QueryCanceled` which will be raised - when canceling statement due to user request. - - *Ryuta Kamizono* - -* Add `#up_only` to database migrations for code that is only relevant when - migrating up, e.g. populating a new column. - - *Rich Daley* - -* Require raw SQL fragments to be explicitly marked when used in - relation query methods. - - Before: - ``` - Article.order("LENGTH(title)") - ``` - - After: - ``` - Article.order(Arel.sql("LENGTH(title)")) - ``` - - This prevents SQL injection if applications use the [strongly - discouraged] form `Article.order(params[:my_order])`, under the - mistaken belief that only column names will be accepted. - - Raw SQL strings will now cause a deprecation warning, which will - become an UnknownAttributeReference error in Rails 6.0. Applications - can opt in to the future behavior by setting `allow_unsafe_raw_sql` - to `:disabled`. - - Common and judged-safe string values (such as simple column - references) are unaffected: - ``` - Article.order("title DESC") - ``` - - *Ben Toews* - -* `update_all` will now pass its values to `Type#cast` before passing them to - `Type#serialize`. This means that `update_all(foo: 'true')` will properly - persist a boolean. - - *Sean Griffin* - -* Add new error class `StatementTimeout` which will be raised - when statement timeout exceeded. - - *Ryuta Kamizono* - -* Fix `bin/rails db:migrate` with specified `VERSION`. - `bin/rails db:migrate` with empty VERSION behaves as without `VERSION`. - Check a format of `VERSION`: Allow a migration version number - or name of a migration file. Raise error if format of `VERSION` is invalid. - Raise error if target migration doesn't exist. - - *bogdanvlviv* - -* Fixed a bug where column orders for an index weren't written to - `db/schema.rb` when using the sqlite adapter. - - Fixes #30902. - - *Paul Kuruvilla* - -* Remove deprecated method `#sanitize_conditions`. - - *Rafael Mendonça França* - -* Remove deprecated method `#scope_chain`. - - *Rafael Mendonça França* - -* Remove deprecated configuration `.error_on_ignored_order_or_limit`. - - *Rafael Mendonça França* - -* Remove deprecated arguments from `#verify!`. - - *Rafael Mendonça França* - -* Remove deprecated argument `name` from `#indexes`. - - *Rafael Mendonça França* - -* Remove deprecated method `ActiveRecord::Migrator.schema_migrations_table_name`. - - *Rafael Mendonça França* - -* Remove deprecated method `supports_primary_key?`. - - *Rafael Mendonça França* - -* Remove deprecated method `supports_migrations?`. - - *Rafael Mendonça França* - -* Remove deprecated methods `initialize_schema_migrations_table` and `initialize_internal_metadata_table`. - - *Rafael Mendonça França* - -* Raises when calling `lock!` in a dirty record. - - *Rafael Mendonça França* - -* Remove deprecated support to passing a class to `:class_name` on associations. - - *Rafael Mendonça França* - -* Remove deprecated argument `default` from `index_name_exists?`. - - *Rafael Mendonça França* - -* Remove deprecated support to `quoted_id` when typecasting an Active Record object. - - *Rafael Mendonça França* - -* Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong - ar_internal_metadata's data for a test database. - - Before: - ``` - $ RAILS_ENV=test rails dbconsole - > SELECT * FROM ar_internal_metadata; - key|value|created_at|updated_at - environment|development|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 - ``` - - After: - ``` - $ RAILS_ENV=test rails dbconsole - > SELECT * FROM ar_internal_metadata; - key|value|created_at|updated_at - environment|test|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 - ``` - - Fixes #26731. - - *bogdanvlviv* - -* Fix longer sequence name detection for serial columns. - - Fixes #28332. - - *Ryuta Kamizono* - -* MySQL: Don't lose `auto_increment: true` in the `db/schema.rb`. - - Fixes #30894. - - *Ryuta Kamizono* - -* Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT`. - - Fixes #30886. - - *Ryuta Kamizono* - -* PostgreSQL `tsrange` now preserves subsecond precision. - - PostgreSQL 9.1+ introduced range types, and Rails added support for using - this datatype in Active Record. However, the serialization of - `PostgreSQL::OID::Range` was incomplete, because it did not properly - cast the bounds that make up the range. This led to subseconds being - dropped in SQL commands: - - Before: - - connection.type_cast(tsrange.serialize(range_value)) - # => "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)" - - Now: - - connection.type_cast(tsrange.serialize(range_value)) - # => "[2010-01-01 13:30:00.670277,2011-02-02 19:30:00.745125)" - - *Thomas Cannon* - -* Passing a `Set` to `Relation#where` now behaves the same as passing an - array. - - *Sean Griffin* - -* Use given algorithm while removing index from database. - - Fixes #24190. - - *Mehmet Emin İNAÇ* - -* Update payload names for `sql.active_record` instrumentation to be - more descriptive. - - Fixes #30586. - - *Jeremy Green* - -* Add new error class `LockWaitTimeout` which will be raised - when lock wait timeout exceeded. - - *Gabriel Courtemanche* - -* Remove deprecated `#migration_keys`. - - *Ryuta Kamizono* - -* Automatically guess the inverse associations for STI. - - *Yuichiro Kaneko* - -* Ensure `sum` honors `distinct` on `has_many :through` associations - - Fixes #16791. - - *Aaron Wortham* - -* Add `binary` fixture helper method. - - *Atsushi Yoshida* - -* When using `Relation#or`, extract the common conditions and put them before the OR condition. - - *Maxime Handfield Lapointe* - -* `Relation#or` now accepts two relations who have different values for - `references` only, as `references` can be implicitly called by `where`. - - Fixes #29411. - - *Sean Griffin* - -* `ApplicationRecord` is no longer generated when generating models. If you - need to generate it, it can be created with `rails g application_record`. - - *Lisa Ugray* - -* Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT` to keep the existing select list. - - *Ryuta Kamizono* - -* When a `has_one` association is destroyed by `dependent: destroy`, - `destroyed_by_association` will now be set to the reflection, matching the - behaviour of `has_many` associations. - - *Lisa Ugray* - -* Fix `unscoped(where: [columns])` removing the wrong bind values - - When the `where` is called on a relation after a `or`, unscoping the column of that later `where` removed - bind values used by the `or` instead. (possibly other cases too) - - ``` - Post.where(id: 1).or(Post.where(id: 2)).where(foo: 3).unscope(where: :foo).to_sql - # Currently: - # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 2 OR "posts"."id" = 3) - # With fix: - # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 1 OR "posts"."id" = 2) - ``` - - *Maxime Handfield Lapointe* - -* Values constructed using multi-parameter assignment will now use the - post-type-cast value for rendering in single-field form inputs. - - *Sean Griffin* - -* `Relation#joins` is no longer affected by the target model's - `current_scope`, with the exception of `unscoped`. - - Fixes #29338. - - *Sean Griffin* - -* Change sqlite3 boolean serialization to use 1 and 0 - - SQLite natively recognizes 1 and 0 as true and false, but does not natively - recognize 't' and 'f' as was previously serialized. - - This change in serialization requires a migration of stored boolean data - for SQLite databases, so it's implemented behind a configuration flag - whose default false value is deprecated. - - *Lisa Ugray* - -* Skip query caching when working with batches of records (`find_each`, `find_in_batches`, - `in_batches`). - - Previously, records would be fetched in batches, but all records would be retained in memory - until the end of the request or job. - - *Eugene Kenny* - -* Prevent errors raised by `sql.active_record` notification subscribers from being converted into - `ActiveRecord::StatementInvalid` exceptions. - - *Dennis Taylor* - -* Fix eager loading/preloading association with scope including joins. - - Fixes #28324. - - *Ryuta Kamizono* - -* Fix transactions to apply state to child transactions - - Previously, if you had a nested transaction and the outer transaction was rolledback, the record from the - inner transaction would still be marked as persisted. - - This change fixes that by applying the state of the parent transaction to the child transaction when the - parent transaction is rolledback. This will correctly mark records from the inner transaction as not persisted. - - *Eileen M. Uchitelle*, *Aaron Patterson* - -* Deprecate `set_state` method in `TransactionState` - - Deprecated the `set_state` method in favor of setting the state via specific methods. If you need to mark the - state of the transaction you can now use `rollback!`, `commit!` or `nullify!` instead of - `set_state(:rolledback)`, `set_state(:committed)`, or `set_state(nil)`. - - *Eileen M. Uchitelle*, *Aaron Patterson* - -* Deprecate delegating to `arel` in `Relation`. - - *Ryuta Kamizono* - -* Fix eager loading to respect `store_full_sti_class` setting. - - *Ryuta Kamizono* - -* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block - without being connected. - - *Tsukasa Oishi* - -* Previously, when building records using a `has_many :through` association, - if the child records were deleted before the parent was saved, they would - still be persisted. Now, if child records are deleted before the parent is saved - on a `has_many :through` association, the child records will not be persisted. - - *Tobias Kraze* - -* Merging two relations representing nested joins no longer transforms the joins of - the merged relation into LEFT OUTER JOIN. Example to clarify: - - ``` - Author.joins(:posts).merge(Post.joins(:comments)) - # Before the change: - #=> SELECT ... FROM authors INNER JOIN posts ON ... LEFT OUTER JOIN comments ON... - - # After the change: - #=> SELECT ... FROM authors INNER JOIN posts ON ... INNER JOIN comments ON... - ``` - - TODO: Add to the Rails 5.2 upgrade guide - - *Maxime Handfield Lapointe* - -* `ActiveRecord::Persistence#touch` does not work well when optimistic locking enabled and - `locking_column`, without default value, is null in the database. - - *bogdanvlviv* - -* Fix destroying existing object does not work well when optimistic locking enabled and - `locking_column` is null in the database. - - *bogdanvlviv* - -* Use bulk INSERT to insert fixtures for better performance. - - *Kir Shatrov* - -* Prevent creation of bind param if casted value is nil. - - *Ryuta Kamizono* - -* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`. - - *Ryuta Kamizono* - -* Loading model schema from database is now thread-safe. - - Fixes #28589. - - *Vikrant Chaudhary*, *David Abdemoulaie* - -* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries - in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key - that does not include a timestamp any more. - - NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps - until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+ - - *DHH* - -* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump - - *Rusty Geldmacher*, *Guillermo Iguaran* - -* Add type caster to `RuntimeReflection#alias_name` - - Fixes #28959. - - *Jon Moss* - -* Deprecate `supports_statement_cache?`. - - *Ryuta Kamizono* - -* Raise error `UnknownMigrationVersionError` on the movement of migrations - when the current migration does not exist. - - *bogdanvlviv* - -* Fix `bin/rails db:forward` first migration. - - *bogdanvlviv* - -* Support Descending Indexes for MySQL. - - MySQL 8.0.1 and higher supports descending indexes: `DESC` in an index definition is no longer ignored. - See https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html. - - *Ryuta Kamizono* - -* Fix inconsistency with changed attributes when overriding AR attribute reader. - - *bogdanvlviv* - -* When calling the dynamic fixture accessor method with no arguments, it now returns all fixtures of this type. - Previously this method always returned an empty array. - - *Kevin McPhillips* - - -Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes. +Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index f9e4444f07..cce00cbc3a 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 David Heinemeier Hansson +Copyright (c) 2004-2018 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 57c82bf469..591c451da5 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -68,7 +68,7 @@ end (Dir["test/cases/**/*_test.rb"].reject { |x| x.include?("/adapters/") } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(Gem.ruby, "-w" , "-Itest", file) + sh(Gem.ruby, "-w", "-Itest", file) end || raise("Failures") end end diff --git a/activerecord/bin/test b/activerecord/bin/test index 83c192531e..9ecf27ce67 100755 --- a/activerecord/bin/test +++ b/activerecord/bin/test @@ -1,6 +1,12 @@ #!/usr/bin/env ruby # frozen_string_literal: true +adapter_index = ARGV.index("--adapter") || ARGV.index("-a") +if adapter_index + ARGV.delete_at(adapter_index) + ENV["ARCONN"] = ARGV.delete_at(adapter_index).strip +end + COMPONENT_ROOT = File.expand_path("..", __dir__) require_relative "../../tools/test" @@ -17,4 +23,5 @@ module Minitest end end +Minitest.load_plugins Minitest.extensions.unshift "active_record" diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 5de6503144..b4377ad6be 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true #-- -# Copyright (c) 2004-2017 David Heinemeier Hansson +# Copyright (c) 2004-2018 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index e5e89734d2..27a641f05b 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -35,7 +35,7 @@ module ActiveRecord # the database). # # class Customer < ActiveRecord::Base - # composed_of :balance, class_name: "Money", mapping: %w(amount currency) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # end # @@ -177,9 +177,9 @@ module ActiveRecord # # Once a #composed_of relationship is specified for a model, records can be loaded from the database # by specifying an instance of the value object in the conditions hash. The following example - # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD": + # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago": # - # Customer.where(balance: Money.new(20, "USD")) + # Customer.where(address: Address.new("May Street", "Chicago")) # module ClassMethods # Adds reader and writer methods for manipulating a value object: @@ -212,8 +212,7 @@ module ActiveRecord # # Option examples: # composed_of :temperature, mapping: %w(reading celsius) - # 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) # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location # composed_of :gps_location, allow_nil: true diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index 2b0b2864bc..403667fb70 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -2,8 +2,8 @@ module ActiveRecord class AssociationRelation < Relation - def initialize(klass, table, predicate_builder, association) - super(klass, table, predicate_builder) + def initialize(klass, association) + super(klass) @association = association end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 661605d3e5..b1cad0d0a4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1061,12 +1061,6 @@ module ActiveRecord # belongs_to :dungeon, inverse_of: :evil_wizard # end # - # There are limitations to <tt>:inverse_of</tt> support: - # - # * does not work with <tt>:through</tt> associations. - # * does not work with <tt>:polymorphic</tt> associations. - # * inverse associations for #belongs_to associations #has_many are ignored. - # # For more information, see the documentation for the +:inverse_of+ option. # # == Deleting from associations @@ -1279,6 +1273,9 @@ module ActiveRecord # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many # association will use "person_id" as the default <tt>:foreign_key</tt>. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option. # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the polymorphic association @@ -1352,8 +1349,7 @@ module ActiveRecord # <tt>:autosave</tt> to <tt>true</tt>. # [:inverse_of] # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_many association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. + # that is the inverse of this #has_many association. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # [:extend] # Specifies a module or array of modules that will be extended into the association object returned. @@ -1449,6 +1445,9 @@ module ActiveRecord # Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association # will use "person_id" as the default <tt>:foreign_key</tt>. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option. # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the polymorphic association @@ -1464,6 +1463,9 @@ module ActiveRecord # <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the # source reflection. You can only use a <tt>:through</tt> query through a #has_one # or #belongs_to association on the join model. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option. # [:source] # Specifies the source association name used by #has_one <tt>:through</tt> queries. # Only use it if the name cannot be inferred from the association. @@ -1484,8 +1486,7 @@ module ActiveRecord # <tt>:autosave</tt> to <tt>true</tt>. # [:inverse_of] # Specifies the name of the #belongs_to association on the associated object - # that is the inverse of this #has_one association. Does not work in combination - # with <tt>:through</tt> or <tt>:as</tt> options. + # that is the inverse of this #has_one association. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # [:required] # When set to +true+, the association will also have its presence validated. @@ -1570,6 +1571,9 @@ module ActiveRecord # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly, # <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key # of "favorite_person_id". + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option. # [:foreign_type] # Specify the column used to store the associated object's type, if this is a polymorphic # association. By default this is guessed to be the name of the association with a "_type" @@ -1619,8 +1623,7 @@ module ActiveRecord # +after_commit+ and +after_rollback+ callbacks are executed. # [:inverse_of] # Specifies the name of the #has_one or #has_many association on the associated - # object that is the inverse of this #belongs_to association. Does not work in - # combination with the <tt>:polymorphic</tt> options. + # object that is the inverse of this #belongs_to association. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. # [:optional] # When set to +true+, the association will not have its presence validated. @@ -1789,6 +1792,9 @@ module ActiveRecord # of this class in lower-case and "_id" suffixed. So a Person class that makes # a #has_and_belongs_to_many association to Project will use "person_id" as the # default <tt>:foreign_key</tt>. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the <tt>:inverse_of</tt> option. # [:association_foreign_key] # Specify the foreign key used for the association on the receiving side of the association. # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 14881cfe17..4f3893588e 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -30,20 +30,12 @@ module ActiveRecord join.left.scan( /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i ).size - elsif join.respond_to? :left + elsif join.is_a?(Arel::Nodes::Join) join.left.name == name ? 1 : 0 elsif join.is_a?(Hash) join.fetch(name, 0) else - # this branch is reached by two tests: - # - # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37 - # with :posts - # - # activerecord/test/cases/associations/eager_test.rb:1133 - # with :comments - # - 0 + raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join" end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index ca1f9f1650..7667c6ed8b 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -124,7 +124,7 @@ module ActiveRecord # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope - AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all) + AssociationRelation.create(klass, self).merge!(klass.all) end def extensions diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index ba54cd8f49..bd2012df84 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -12,17 +12,20 @@ module ActiveRecord if record raise_on_type_mismatch!(record) update_counters_on_replace(record) - replace_keys(record) set_inverse_instance(record) @updated = true else decrement_counters - remove_keys end self.target = record end + def target=(record) + replace_keys(record) + super + end + def default(&block) writer(owner.instance_exec(&block)) if reader.nil? end @@ -49,9 +52,9 @@ module ActiveRecord def update_counters(by) if require_counter_update? && foreign_key_present? if target && !stale_target? - target.increment!(reflection.counter_cache_column, by) + target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch]) else - klass.update_counters(target_id, reflection.counter_cache_column => by) + klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch]) end end end @@ -78,11 +81,8 @@ module ActiveRecord end def replace_keys(record) - owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class)) - end - - def remove_keys - owner[reflection.foreign_key] = nil + owner[reflection.foreign_key] = record ? + record._read_attribute(reflection.association_primary_key(record.class)) : nil end def foreign_key_present? diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 4ce3474bd5..55d789c66a 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -13,12 +13,7 @@ module ActiveRecord def replace_keys(record) super - owner[reflection.foreign_type] = record.class.base_class.name - end - - def remove_keys - super - owner[reflection.foreign_type] = nil + owner[reflection.foreign_type] = record ? record.class.base_class.name : nil end def different_target?(record) diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index ca3032d967..7c69cd65ee 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -104,8 +104,8 @@ module ActiveRecord::Associations::Builder # :nodoc: def self.define_readers(mixin, name) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}(*args) - association(:#{name}).reader(*args) + def #{name} + association(:#{name}).reader end CODE end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 9904ee4bed..c161454c1a 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -114,9 +114,13 @@ module ActiveRecord::Associations::Builder # :nodoc: BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method) }} - model.after_save callback.(:saved_changes), if: :saved_changes? - model.after_touch callback.(:changes_to_save) - model.after_destroy callback.(:changes_to_save) + unless reflection.counter_cache_column + model.after_create callback.(:saved_changes), if: :saved_changes? + model.after_destroy callback.(:changes_to_save) + end + + model.after_update callback.(:saved_changes), if: :saved_changes? + model.after_touch callback.(:changes_to_save) end def self.add_default_callbacks(model, reflection) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 921237a735..443ccaaa72 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -52,11 +52,11 @@ module ActiveRecord # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items def ids_writer(ids) - pk_type = reflection.association_primary_key_type + primary_key = reflection.association_primary_key + pk_type = klass.type_for_attribute(primary_key) ids = Array(ids).reject(&:blank?) ids.map! { |i| pk_type.cast(i) } - primary_key = reflection.association_primary_key records = klass.where(primary_key => ids).index_by do |r| r.public_send(primary_key) end.values_at(*ids).compact diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 8b4a48a38c..9a30198b95 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -32,7 +32,7 @@ module ActiveRecord class CollectionProxy < Relation def initialize(klass, association) #:nodoc: @association = association - super klass, klass.arel_table, klass.predicate_builder + super klass extensions = association.extensions extend(*extensions) if extensions.any? diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 07c7f28d2d..cf85a87fa7 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -63,7 +63,7 @@ module ActiveRecord count = if reflection.has_cached_counter? owner._read_attribute(reflection.counter_cache_column).to_i else - scope.count + scope.count(:all) end # If there's nothing in the database and @target has no new records 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 adbf52b87c..59929b8c4e 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -8,9 +8,7 @@ module ActiveRecord def initialize(owner, reflection) super - - @through_records = {} - @through_association = nil + @through_records = {} end def concat(*records) @@ -50,11 +48,6 @@ module ActiveRecord end private - - def through_association - @through_association ||= owner.association(through_reflection.name) - end - # The through record (built with build_record) is temporarily cached # so that it may be reused if insert_record is subsequently called. # @@ -140,21 +133,15 @@ module ActiveRecord scope = through_association.scope scope.where! construct_join_attributes(*records) + scope = scope.where(through_scope_attributes) case method when :destroy if scope.klass.primary_key - count = scope.destroy_all.length + count = scope.destroy_all.count(&:destroyed?) else scope.each(&:_run_destroy_callbacks) - - arel = scope.arel - - stmt = Arel::DeleteManager.new - stmt.from scope.klass.arel_table - stmt.wheres = arel.constraints - - count = scope.klass.connection.delete(stmt, "SQL") + count = scope.delete_all end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 36746f9115..491282adf7 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -6,17 +6,16 @@ module ActiveRecord class HasOneThroughAssociation < HasOneAssociation #:nodoc: include ThroughAssociation - def replace(record) - create_through_record(record) + def replace(record, save = true) + create_through_record(record, save) self.target = record end private - - def create_through_record(record) + def create_through_record(record, save) ensure_not_nested - through_proxy = owner.association(through_reflection.name) + through_proxy = through_association through_record = through_proxy.load_target if through_record && !record @@ -30,7 +29,7 @@ module ActiveRecord if through_record through_record.update(attributes) - elsif owner.new_record? + elsif owner.new_record? || !save through_proxy.build(attributes) else through_proxy.create(attributes) diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index e1087be9b3..59320431ee 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -83,7 +83,7 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] def preload(records, associations, preload_scope = nil) - records = records.compact + records = Array.wrap(records).compact if records.empty? [] diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 735da152b7..ec6f0df484 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -82,11 +82,11 @@ module ActiveRecord end def association_key_type - @klass.type_for_attribute(association_key_name.to_s).type + @klass.type_for_attribute(association_key_name).type end def owner_key_type - @model.type_for_attribute(owner_key_name.to_s).type + @model.type_for_attribute(owner_key_name).type end def load_records(&block) diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index bce2a95ce1..5afb0bc068 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -4,9 +4,24 @@ module ActiveRecord module Associations # = Active Record Through Association module ThroughAssociation #:nodoc: - delegate :source_reflection, :through_reflection, to: :reflection + delegate :source_reflection, to: :reflection private + def through_reflection + @through_reflection ||= begin + refl = reflection.through_reflection + + while refl.through_reflection? + refl = refl.through_reflection + end + + refl + end + end + + def through_association + @through_association ||= owner.association(through_reflection.name) + end # We merge in these scopes for two reasons: # @@ -38,24 +53,22 @@ module ActiveRecord def construct_join_attributes(*records) ensure_mutable - if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key + association_primary_key = source_reflection.association_primary_key(reflection.klass) + + if association_primary_key == reflection.klass.primary_key && !options[:source_type] join_attributes = { source_reflection.name => records } else join_attributes = { - source_reflection.foreign_key => - records.map { |record| - record.send(source_reflection.association_primary_key(reflection.klass)) - } + source_reflection.foreign_key => records.map(&association_primary_key.to_sym) } end if options[:source_type] - join_attributes[source_reflection.foreign_type] = - records.map { |record| record.class.base_class.name } + join_attributes[source_reflection.foreign_type] = [ options[:source_type] ] end if records.count == 1 - Hash[join_attributes.map { |k, v| [k, v.first] }] + join_attributes.transform_values!(&:first) else join_attributes end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 64f81ca582..c77790dc9a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -456,7 +456,7 @@ module ActiveRecord arel_table = self.class.arel_table attribute_names.each do |name| - attrs[arel_table[name]] = typecasted_attribute_value(name) + attrs[arel_table[name]] = _read_attribute(name) end attrs end @@ -483,9 +483,5 @@ module ActiveRecord def pk_attribute?(name) name == self.class.primary_key end - - def typecasted_attribute_value(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 3de6fe566d..df4c79b0f6 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -32,9 +32,7 @@ module ActiveRecord # <tt>reload</tt> the record and clears changed attributes. def reload(*) super.tap do - @previously_changed = ActiveSupport::HashWithIndifferentAccess.new @mutations_before_last_save = nil - @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new @mutations_from_database = nil end end @@ -114,12 +112,12 @@ module ActiveRecord # Alias for +changed+ def changed_attribute_names_to_save - changes_to_save.keys + mutations_from_database.changed_attribute_names end # Alias for +changed_attributes+ def attributes_in_database - changes_to_save.transform_values(&:first) + mutations_from_database.changed_values end private diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 0b7c9398a8..35150889d9 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -57,7 +57,7 @@ module ActiveRecord # store_listing = StoreListing.new(price_in_cents: '10.1') # # # before - # store_listing.price_in_cents # => BigDecimal.new(10.1) + # store_listing.price_in_cents # => BigDecimal(10.1) # # class StoreListing < ActiveRecord::Base # attribute :price_in_cents, :integer diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 9ab2780760..9dbdf845bd 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -232,7 +232,7 @@ module ActiveRecord # # For example: # - # class Topic + # class Topic < ActiveRecord::Base # has_many :children # # after_save :log_children @@ -257,7 +257,7 @@ module ActiveRecord # # For example: # - # class Topic + # class Topic < ActiveRecord::Base # has_many :children # # after_commit :log_children diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index 88b398ad45..dfba78614e 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -3,11 +3,11 @@ module ActiveRecord module CollectionCacheKey def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: - query_signature = Digest::MD5.hexdigest(collection.to_sql) + query_signature = ActiveSupport::Digest.hexdigest(collection.to_sql) key = "#{collection.model_name.cache_key}/query-#{query_signature}" - if collection.loaded? - size = collection.size + if collection.loaded? || collection.distinct_value + size = collection.records.size if size > 0 timestamp = collection.max_by(×tamp_column)._read_attribute(timestamp_column) end @@ -15,13 +15,12 @@ module ActiveRecord if collection.eager_loading? collection = collection.send(:apply_join_dependency) end - column_type = type_for_attribute(timestamp_column.to_s) + column_type = type_for_attribute(timestamp_column) column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column)) select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" if collection.has_limit_or_offset? - query = collection.spawn - query.select_values = [column] + query = collection.select(column) subquery_alias = "subquery_for_cache_key" subquery_column = "#{subquery_alias}.#{timestamp_column}" subquery = query.arel.as(subquery_alias) 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 9849f9d5d7..c730584902 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1005,9 +1005,7 @@ module ActiveRecord def retrieve_connection(spec_name) #:nodoc: pool = retrieve_connection_pool(spec_name) raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool - conn = pool.connection - raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn - conn + pool.connection end # Returns true if a connection that's accessible to this class has diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 36048bee03..08f3e15a4b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -356,35 +356,33 @@ module ActiveRecord # Inserts a set of fixtures into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixtures(fixtures, table_name) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `insert_fixtures` is deprecated and will be removed in the next version of Rails. + Consider using `insert_fixtures_set` for performance improvement. + MSG return if fixtures.empty? - columns = schema_cache.columns_hash(table_name) + execute(build_fixture_sql(fixtures, table_name), "Fixtures Insert") + end - values = fixtures.map do |fixture| - fixture = fixture.stringify_keys + def insert_fixtures_set(fixture_set, tables_to_delete = []) + fixture_inserts = fixture_set.map do |table_name, fixtures| + next if fixtures.empty? - unknown_columns = fixture.keys - columns.keys - if unknown_columns.any? - raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) - end + build_fixture_sql(fixtures, table_name) + end.compact - columns.map do |name, column| - if fixture.key?(name) - type = lookup_cast_type_from_column(column) - bind = Relation::QueryAttribute.new(name, fixture[name], type) - with_yaml_fallback(bind.value_for_database) - else - Arel.sql("DEFAULT") + table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup } + total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts)) + + disable_referential_integrity do + transaction(requires_new: true) do + total_sql.each do |sql| + execute sql, "Fixtures Load" + yield if block_given? end end end - - table = Arel::Table.new(table_name) - manager = Arel::InsertManager.new - manager.into(table) - columns.each_key { |column| manager.columns << table[column] } - manager.values = manager.create_values_list(values) - execute manager.to_sql, "Fixtures Insert" end def empty_insert_statement_value @@ -416,6 +414,44 @@ module ActiveRecord alias join_to_delete join_to_update private + def default_insert_value(column) + Arel.sql("DEFAULT") + end + + def build_fixture_sql(fixtures, table_name) + columns = schema_cache.columns_hash(table_name) + + values = fixtures.map do |fixture| + fixture = fixture.stringify_keys + + unknown_columns = fixture.keys - columns.keys + if unknown_columns.any? + raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) + end + + columns.map do |name, column| + if fixture.key?(name) + type = lookup_cast_type_from_column(column) + bind = Relation::QueryAttribute.new(name, fixture[name], type) + with_yaml_fallback(bind.value_for_database) + else + default_insert_value(column) + end + end + end + + table = Arel::Table.new(table_name) + manager = Arel::InsertManager.new + manager.into(table) + columns.each_key { |column| manager.columns << table[column] } + manager.values = manager.create_values_list(values) + + manager.to_sql + end + + def combine_multi_statements(total_sql) + total_sql.join(";\n") + end # Returns a subquery for the given key using the join information. def subquery_for(key, select) 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 bd05fb8f6e..4a191d337c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -95,6 +95,7 @@ module ActiveRecord if options_sql = options[:options] create_sql << " #{options_sql}" end + create_sql end def column_options(o) 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 4f58b0242c..db033db913 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -715,7 +715,7 @@ module ActiveRecord # # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) # - # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it). + # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it). # # ====== Creating a partial index # @@ -1049,8 +1049,8 @@ module ActiveRecord sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i) - versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file| - ActiveRecord::Migrator.parse_migration_filename(file).first.to_i + versions = migration_context.migration_files.map do |file| + migration_context.parse_migration_filename(file).first.to_i end unless migrated.include?(version) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fc80d332f9..559f068c39 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -119,6 +119,14 @@ module ActiveRecord end end + def migrations_paths # :nodoc: + @config[:migrations_paths] || Migrator.migrations_paths + end + + def migration_context # :nodoc: + MigrationContext.new(migrations_paths) + end + class Version include Comparable @@ -318,6 +326,11 @@ module ActiveRecord false end + # Does this adapter support foreign/external tables? + def supports_foreign_tables? + false + end + # This is meant to be implemented by the adapters that support extensions def disable_extension(name) end @@ -537,12 +550,7 @@ module ActiveRecord end def extract_limit(sql_type) - case sql_type - when /^bigint/i - 8 - when /\((.*)\)/ - $1.to_i - end + $1.to_i if sql_type =~ /\((.*)\)/ end def translate_exception_class(e, sql) 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 479131caad..1fb0bd8d56 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -117,11 +117,11 @@ module ActiveRecord end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: - query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1 + query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1 end def release_advisory_lock(lock_name) # :nodoc: - query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1 + query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1 end def native_database_types @@ -249,7 +249,7 @@ module ActiveRecord # create_database 'matt_development', charset: :big5 def create_database(name, options = {}) if options[:collation] - execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}" + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" else execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" end @@ -292,10 +292,6 @@ module ActiveRecord SQL end - def create_table(table_name, **options) #:nodoc: - super(table_name, options: "ENGINE=InnoDB", **options) - end - def bulk_change_table(table_name, operations) #:nodoc: sqls = operations.flat_map do |command, args| table, arguments = args.shift, args @@ -403,12 +399,13 @@ module ActiveRecord fk.constraint_name AS 'name', rc.update_rule AS 'on_update', rc.delete_rule AS 'on_delete' - FROM information_schema.key_column_usage fk - JOIN information_schema.referential_constraints rc + FROM information_schema.referential_constraints rc + JOIN information_schema.key_column_usage fk USING (constraint_schema, constraint_name) WHERE fk.referenced_column_name IS NOT NULL AND fk.table_schema = #{scope[:schema]} AND fk.table_name = #{scope[:name]} + AND rc.constraint_schema = #{scope[:schema]} AND rc.table_name = #{scope[:name]} SQL @@ -529,23 +526,49 @@ module ActiveRecord index.using == :btree || super end - def insert_fixtures(*) - without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super } + def insert_fixtures_set(fixture_set, tables_to_delete = []) + with_multi_statements do + super { discard_remaining_results } + end end private + def combine_multi_statements(total_sql) + total_sql.each_with_object([]) do |sql, total_sql_chunks| + previous_packet = total_sql_chunks.last + sql << ";\n" + if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty? + total_sql_chunks << sql + else + previous_packet << sql + end + end + end + + def max_allowed_packet_reached?(current_packet, previous_packet) + if current_packet.bytesize > max_allowed_packet + raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable." + elsif previous_packet.nil? + false + else + (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet + end + end + + def max_allowed_packet + bytes_margin = 2 + @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin) + end - def without_sql_mode(mode) - result = execute("SELECT @@SESSION.sql_mode") - current_mode = result.first[0] - return yield unless current_mode.include?(mode) + def with_multi_statements + previous_flags = @config[:flags] + @config[:flags] = Mysql2::Client::MULTI_STATEMENTS + reconnect! - sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')" - execute("SET @@SESSION.sql_mode = #{sql_mode}") yield ensure - sql_mode = "CONCAT(@@sql_mode, ',#{mode}')" - execute("SET @@SESSION.sql_mode = #{sql_mode}") + @config[:flags] = previous_flags + reconnect! end def initialize_type_map(m = type_map) @@ -716,7 +739,8 @@ module ActiveRecord # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' subselect.distinct unless select.limit || select.offset || select.orders.any? - Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key.name)) + key_name = quote_column_name(key.name) + Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name)) end def supports_rename_index? diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index a058a72872..458c9bfd70 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -11,7 +11,7 @@ module ActiveRecord else super end - @connection.next_result while @connection.more_results? + discard_remaining_results result end @@ -50,11 +50,18 @@ module ActiveRecord alias :exec_update :exec_delete private + def default_insert_value(column) + Arel.sql("DEFAULT") unless column.auto_increment? + end def last_inserted_id(result) @connection.last_id end + def discard_remaining_results + @connection.abandon_results! + end + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb index 879dba7afd..e7d33855c4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb @@ -6,7 +6,7 @@ module ActiveRecord module OID # :nodoc: class Decimal < Type::Decimal # :nodoc: def infinity(options = {}) - BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1) + BigDecimal("Infinity") * (options[:negative] ? -1 : 1) end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index a89aa5ea09..6edb7cfd3c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -60,7 +60,7 @@ module ActiveRecord end def type_cast_single_for_database(value) - infinity?(value) ? "" : @subtype.serialize(value) + infinity?(value) ? value : @subtype.serialize(value) end def extract_bounds(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 9fdeab06c1..e75202b0be 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -138,7 +138,7 @@ module ActiveRecord end def encode_range(range) - "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}" + "[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}" end def determine_encoding_of_strings_in_array(value) @@ -154,6 +154,14 @@ module ActiveRecord else _type_cast(values) end end + + def type_cast_range_value(value) + infinity?(value) ? "" : type_cast(value) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index bf5fbb30e1..7999434ad8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -38,7 +38,7 @@ module ActiveRecord " TABLESPACE = \"#{value}\"" when :connection_limit " CONNECTION LIMIT = #{value}" - else + else "" end end @@ -124,9 +124,13 @@ module ActiveRecord # add info on sort order (only desc order is explicitly specified, asc is the default) # and non-default opclasses - expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc| + expressions.scan(/(?<column>\w+)\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls| opclasses[column] = opclass.to_sym if opclass - orders[column] = :desc if desc + if nulls + orders[column] = [desc, nulls].compact.join(" ") + else + orders[column] = :desc if desc + end end end @@ -364,6 +368,31 @@ module ActiveRecord SQL end + def bulk_change_table(table_name, operations) + sql_fragments = [] + non_combinable_operations = [] + + operations.each do |command, args| + table, arguments = args.shift, args + method = :"#{command}_for_alter" + + if respond_to?(method, true) + sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) } + sql_fragments << sqls + non_combinable_operations.concat(procs) + else + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + sql_fragments = [] + non_combinable_operations = [] + send(command, table, *arguments) + end + end + + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + end + # Renames a table. # Also renames a table's primary key sequence if the sequence name exists and # matches the Active Record default. @@ -394,7 +423,7 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: clear_cache! - sqls, procs = change_column_for_alter(table_name, column_name, type, options) + sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) } execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}" procs.each(&:call) end @@ -502,6 +531,14 @@ module ActiveRecord end end + def foreign_tables + query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA") + end + + def foreign_table_exists?(table_name) + query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present? + end + # Maps logical Rails types to PostgreSQL-specific data types. def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: sql = \ @@ -665,12 +702,10 @@ module ActiveRecord def change_column_for_alter(table_name, column_name, type, options = {}) sqls = [change_column_sql(table_name, column_name, type, options)] - procs = [] sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default) sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null) - procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) - - [sqls, procs] + sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + sqls end @@ -694,6 +729,14 @@ module ActiveRecord "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" end + def add_timestamps_for_alter(table_name, options = {}) + [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)] + end + + def remove_timestamps_for_alter(table_name, options = {}) + [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] + end + def add_index_opclass(quoted_columns, **options) opclasses = options_for_index_columns(options[:opclass]) quoted_columns.each do |name, column| @@ -708,7 +751,7 @@ module ActiveRecord def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) - scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view + scope[:type] ||= "'r','v','m','f'" # (r)elation/table, (v)iew, (m)aterialized view, (f)oreign table sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup sql << " WHERE n.nspname = #{scope[:schema]}" @@ -725,6 +768,8 @@ module ActiveRecord "'r'" when "VIEW" "'v','m'" + when "FOREIGN TABLE" + "'f'" end scope = {} scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 23fc69d649..dc6287e32c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility -gem "pg", "~> 0.18" +gem "pg", ">= 0.18", "< 2.0" require "pg" require "active_record/connection_adapters/abstract_adapter" @@ -122,6 +122,10 @@ module ActiveRecord include PostgreSQL::SchemaStatements include PostgreSQL::DatabaseStatements + def supports_bulk_alter? + true + end + def supports_index_sort_order? true end @@ -277,7 +281,7 @@ module ActiveRecord end def discard! # :nodoc: - @connection.socket_io.reopen(IO::NULL) + @connection.socket_io.reopen(IO::NULL) rescue nil @connection = nil end @@ -314,6 +318,10 @@ module ActiveRecord postgresql_version >= 90300 end + def supports_foreign_tables? + postgresql_version >= 90300 + end + def supports_pgcrypto_uuid? postgresql_version >= 90400 end diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index f34b6733da..c29cf1f9a1 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -28,7 +28,7 @@ module ActiveRecord coder["columns_hash"] = @columns_hash coder["primary_keys"] = @primary_keys coder["data_sources"] = @data_sources - coder["version"] = ActiveRecord::Migrator.current_version + coder["version"] = connection.migration_context.current_version end def init_with(coder) @@ -100,7 +100,7 @@ module ActiveRecord def marshal_dump # if we get current version during initialization, it happens stack over flow. - @version = ActiveRecord::Migrator.current_version + @version = connection.migration_context.current_version [@version, @columns, @columns_hash, @primary_keys, @data_sources] end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 574a7b4572..a958600446 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -60,7 +60,7 @@ module ActiveRecord include SQLite3::SchemaStatements NATIVE_DATABASE_TYPES = { - primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", + primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL", string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, @@ -101,7 +101,7 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super(connection, logger, config) - @active = nil + @active = true @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) configure_connection @@ -144,7 +144,7 @@ module ActiveRecord end def active? - @active != false + @active end # Disconnects from the database if already connected. Otherwise, this @@ -290,19 +290,18 @@ module ActiveRecord rename_table_indexes(table_name, new_name) end - # See: https://www.sqlite.org/lang_altertable.html - # SQLite has an additional restriction on the ALTER TABLE statement - def valid_alter_table_type?(type) - type.to_sym != :primary_key + def valid_alter_table_type?(type, options = {}) + !invalid_alter_table_type?(type, options) end + deprecate :valid_alter_table_type? def add_column(table_name, column_name, type, options = {}) #:nodoc: - if valid_alter_table_type?(type) - super(table_name, column_name, type, options) - else + if invalid_alter_table_type?(type, options) alter_table(table_name) do |definition| definition.column(column_name, type, options) end + else + super end end @@ -368,8 +367,22 @@ module ActiveRecord end def insert_fixtures(rows, table_name) - rows.each do |row| - insert_fixture(row, table_name) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `insert_fixtures` is deprecated and will be removed in the next version of Rails. + Consider using `insert_fixtures_set` for performance improvement. + MSG + insert_fixtures_set(table_name => rows) + end + + def insert_fixtures_set(fixture_set, tables_to_delete = []) + disable_referential_integrity do + transaction(requires_new: true) do + tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" } + + fixture_set.each do |table_name, rows| + rows.each { |row| insert_fixture(row, table_name) } + end + end end end @@ -386,6 +399,12 @@ module ActiveRecord end alias column_definitions table_structure + # See: https://www.sqlite.org/lang_altertable.html + # SQLite has an additional restriction on the ALTER TABLE statement + def invalid_alter_table_type?(type, options) + type.to_sym == :primary_key || options[:primary_key] + end + def alter_table(table_name, options = {}) altered_table_name = "a#{table_name}" caller = lambda { |definition| yield definition if block_given? } @@ -407,18 +426,21 @@ module ActiveRecord options[:id] = false create_table(to, options) do |definition| @definition = definition - @definition.primary_key(from_primary_key) if from_primary_key.present? + if from_primary_key.is_a?(Array) + @definition.primary_keys from_primary_key + end columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name - next if column_name == from_primary_key @definition.column(column_name, column.type, limit: column.limit, default: column.default, precision: column.precision, scale: column.scale, - null: column.null, collation: column.collation) + null: column.null, collation: column.collation, + primary_key: column_name == from_primary_key + ) end yield @definition if block_given? end @@ -431,6 +453,9 @@ module ActiveRecord def copy_table_indexes(from, to, rename = {}) indexes(from).each do |index| name = index.name + # indexes sqlite creates for internal use start with `sqlite_` and + # don't need to be copied + next if name.starts_with?("sqlite_") if to == "a#{from}" name = "t#{name}" elsif from == "a#{to}" @@ -446,6 +471,7 @@ module ActiveRecord # index name can't be the same opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true } opts[:unique] = true if index.unique + opts[:where] = index.where if index.where add_index(to, columns, opts) end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 481159e501..e1a0b2ecf8 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -18,6 +18,13 @@ module ActiveRecord mattr_accessor :logger, instance_writer: false ## + # :singleton-method: + # + # Specifies if the methods calling database queries should be logged below + # their relevant queries. Defaults to false. + mattr_accessor :verbose_query_logs, instance_writer: false, default: false + + ## # Contains the database configuration - as is typically stored in config/database.yml - # as a Hash. # @@ -274,7 +281,7 @@ module ActiveRecord end def relation - relation = Relation.create(self, arel_table, predicate_builder) + relation = Relation.create(self) if finder_needs_type_condition? && !ignore_default_scope? relation.where!(type_condition) @@ -375,8 +382,10 @@ module ActiveRecord _run_initialize_callbacks - @new_record = true - @destroyed = false + @new_record = true + @destroyed = false + @_start_transaction_state = {} + @transaction_state = nil super end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 86f13d75d5..d9a75d9ad6 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -169,13 +169,13 @@ module ActiveRecord # self.use_transactional_tests = true # # test "godzilla" do - # assert !Foo.all.empty? + # assert_not_empty Foo.all # Foo.destroy_all - # assert Foo.all.empty? + # assert_empty Foo.all # end # # test "godzilla aftermath" do - # assert !Foo.all.empty? + # assert_not_empty Foo.all # end # end # @@ -540,47 +540,38 @@ module ActiveRecord } unless files_to_read.empty? - connection.disable_referential_integrity do - fixtures_map = {} - - fixture_sets = files_to_read.map do |fs_name| - klass = class_names[fs_name] - conn = klass ? klass.connection : connection - fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new - conn, - fs_name, - klass, - ::File.join(fixtures_directory, fs_name)) - end - - update_all_loaded_fixtures fixtures_map - - connection.transaction(requires_new: true) do - deleted_tables = Hash.new { |h, k| h[k] = Set.new } - fixture_sets.each do |fs| - conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection - table_rows = fs.table_rows + fixtures_map = {} + + fixture_sets = files_to_read.map do |fs_name| + klass = class_names[fs_name] + conn = klass ? klass.connection : connection + fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new + conn, + fs_name, + klass, + ::File.join(fixtures_directory, fs_name)) + end - table_rows.each_key do |table| - unless deleted_tables[conn].include? table - conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete" - end - deleted_tables[conn] << table - end + update_all_loaded_fixtures fixtures_map + fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection } - table_rows.each do |fixture_set_name, rows| - conn.insert_fixtures(rows, fixture_set_name) - end + fixture_sets_by_connection.each do |conn, set| + table_rows_for_connection = Hash.new { |h, k| h[k] = [] } - # Cap primary key sequences to max(pk). - if conn.respond_to?(:reset_pk_sequence!) - conn.reset_pk_sequence!(fs.table_name) - end + set.each do |fs| + fs.table_rows.each do |table, rows| + table_rows_for_connection[table].unshift(*rows) end end + conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys) - cache_fixtures(connection, fixtures_map) + # Cap primary key sequences to max(pk). + if conn.respond_to?(:reset_pk_sequence!) + set.each { |fs| conn.reset_pk_sequence!(fs.table_name) } + end end + + cache_fixtures(connection, fixtures_map) end cached_fixtures(connection, fixture_set_names) end diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index 7e47dac016..72035a986b 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -7,10 +7,10 @@ module ActiveRecord end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 - PRE = "beta2" + 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 0715c11473..f3fe610c09 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -47,14 +47,13 @@ module ActiveRecord # Determines if one of the attributes passed in is the inheritance column, # and if the inheritance column is attr accessible, it initializes an # instance of the given subclass instead of the base class. - def new(*args, &block) + def new(attributes = nil, &block) if abstract_class? || self == Base raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated." end - attrs = args.first if has_attribute?(inheritance_column) - subclass = subclass_from_attributes(attrs) + subclass = subclass_from_attributes(attributes) if subclass.nil? && base_class == self subclass = subclass_from_attributes(column_defaults) @@ -62,7 +61,7 @@ module ActiveRecord end if subclass && subclass != self - subclass.new(*args, &block) + subclass.new(attributes, &block) else super end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 405f3a30c6..9234029c22 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -90,6 +90,47 @@ module ActiveRecord def logger ActiveRecord::Base.logger end + + def debug(progname = nil, &block) + return unless super + + if ActiveRecord::Base.verbose_query_logs + log_query_source + end + end + + def log_query_source + source_line, line_number = extract_callstack(caller_locations) + + if source_line + if defined?(::Rails.root) + app_root = "#{::Rails.root.to_s}/".freeze + source_line = source_line.sub(app_root, "") + end + + logger.debug(" ↳ #{ source_line }:#{ line_number }") + end + end + + def extract_callstack(callstack) + line = callstack.find do |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + end + + offending_line = line || callstack.first + + [ + offending_line.path, + offending_line.lineno + ] + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __FILE__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || + path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 5c10d4ff24..5322571820 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -550,7 +550,7 @@ module ActiveRecord end def call(env) - mtime = ActiveRecord::Migrator.last_migration.mtime.to_i + mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i if @last_check < mtime ActiveRecord::Migration.check_pending!(connection) @last_check = mtime @@ -575,11 +575,11 @@ module ActiveRecord # Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending. def check_pending!(connection = Base.connection) - raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection) + raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration? end def load_schema_if_pending! - if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? + if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations? # Roundtrip to Rake to allow plugins to hook into database initialization. root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root FileUtils.cd(root) do @@ -876,10 +876,10 @@ module ActiveRecord FileUtils.mkdir_p(destination) unless File.exist?(destination) - destination_migrations = ActiveRecord::Migrator.migrations(destination) + destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations last = destination_migrations.last sources.each do |scope, path| - source_migrations = ActiveRecord::Migrator.migrations(path) + source_migrations = ActiveRecord::MigrationContext.new(path).migrations source_migrations.each do |migration| source = File.binread(migration.filename) @@ -997,132 +997,147 @@ module ActiveRecord end end - class Migrator#:nodoc: - class << self - attr_writer :migrations_paths - alias :migrations_path= :migrations_paths= - - def migrate(migrations_paths, target_version = nil, &block) - case - when target_version.nil? - up(migrations_paths, target_version, &block) - when current_version == 0 && target_version == 0 - [] - when current_version > target_version - down(migrations_paths, target_version, &block) - else - up(migrations_paths, target_version, &block) - end - end + class MigrationContext # :nodoc: + attr_reader :migrations_paths - def rollback(migrations_paths, steps = 1) - move(:down, migrations_paths, steps) - end + def initialize(migrations_paths) + @migrations_paths = migrations_paths + end - def forward(migrations_paths, steps = 1) - move(:up, migrations_paths, steps) + def migrate(target_version = nil, &block) + case + when target_version.nil? + up(target_version, &block) + when current_version == 0 && target_version == 0 + [] + when current_version > target_version + down(target_version, &block) + else + up(target_version, &block) end + end - def up(migrations_paths, target_version = nil) - migrations = migrations(migrations_paths) - migrations.select! { |m| yield m } if block_given? + def rollback(steps = 1) + move(:down, steps) + end - new(:up, migrations, target_version).migrate + def forward(steps = 1) + move(:up, steps) + end + + def up(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations end - def down(migrations_paths, target_version = nil) - migrations = migrations(migrations_paths) - migrations.select! { |m| yield m } if block_given? + Migrator.new(:up, selected_migrations, target_version).migrate + end - new(:down, migrations, target_version).migrate + def down(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations end - def run(direction, migrations_paths, target_version) - new(direction, migrations(migrations_paths), target_version).run - end + Migrator.new(:down, selected_migrations, target_version).migrate + end - def open(migrations_paths) - new(:up, migrations(migrations_paths), nil) - end + def run(direction, target_version) + Migrator.new(direction, migrations, target_version).run + end - def get_all_versions - if SchemaMigration.table_exists? - SchemaMigration.all_versions.map(&:to_i) - else - [] - end - end + def open + Migrator.new(:up, migrations, nil) + end - def current_version(connection = nil) - get_all_versions.max || 0 - rescue ActiveRecord::NoDatabaseError + def get_all_versions + if SchemaMigration.table_exists? + SchemaMigration.all_versions.map(&:to_i) + else + [] end + end - def needs_migration?(connection = nil) - (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0 - end + def current_version + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError + end - def any_migrations? - migrations(migrations_paths).any? - end + def needs_migration? + (migrations.collect(&:version) - get_all_versions).size > 0 + end - def last_migration #:nodoc: - migrations(migrations_paths).last || NullMigration.new - end + def any_migrations? + migrations.any? + end - def migrations_paths - @migrations_paths ||= ["db/migrate"] - # just to not break things if someone uses: migrations_path = some_string - Array(@migrations_paths) - end + def last_migration #:nodoc: + migrations.last || NullMigration.new + end - def parse_migration_filename(filename) # :nodoc: - File.basename(filename).scan(Migration::MigrationFilenameRegexp).first + def parse_migration_filename(filename) # :nodoc: + File.basename(filename).scan(Migration::MigrationFilenameRegexp).first + end + + def migrations + migrations = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + name = name.camelize + + MigrationProxy.new(name, version, file, scope) end - def migrations(paths) - paths = Array(paths) + migrations.sort_by(&:version) + end - migrations = migration_files(paths).map do |file| - version, name, scope = parse_migration_filename(file) - raise IllegalMigrationNameError.new(file) unless version - version = version.to_i - name = name.camelize + def migrations_status + db_list = ActiveRecord::SchemaMigration.normalized_versions - MigrationProxy.new(name, version, file, scope) - end + file_list = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = ActiveRecord::SchemaMigration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end.compact - migrations.sort_by(&:version) + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] end - def migrations_status(paths) - paths = Array(paths) - - db_list = ActiveRecord::SchemaMigration.normalized_versions + (db_list + file_list).sort_by { |_, version, _| version } + end - file_list = migration_files(paths).map do |file| - version, name, scope = parse_migration_filename(file) - raise IllegalMigrationNameError.new(file) unless version - version = ActiveRecord::SchemaMigration.normalize_migration_number(version) - status = db_list.delete(version) ? "up" : "down" - [status, version, (name + scope).humanize] - end.compact + def migration_files + paths = Array(migrations_paths) + Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }] + end - db_list.map! do |version| - ["up", version, "********** NO FILE **********"] - end + def current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end - (db_list + file_list).sort_by { |_, version, _| version } - end + def protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end - def migration_files(paths) - Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }] - end + def last_stored_environment + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? - private + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end - def move(direction, migrations_paths, steps) - migrator = new(direction, migrations(migrations_paths)) + private + def move(direction, steps) + migrator = Migrator.new(direction, migrations) if current_version != 0 && !migrator.current_migration raise UnknownMigrationVersionError.new(current_version) @@ -1137,10 +1152,29 @@ module ActiveRecord finish = migrator.migrations[start_index + steps] version = finish ? finish.version : 0 - send(direction, migrations_paths, version) + send(direction, version) + end + end + + class Migrator # :nodoc: + class << self + attr_accessor :migrations_paths + + def migrations_path=(path) + ActiveSupport::Deprecation.warn \ + "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \ + "You can set the `migrations_paths` on the `connection` instead through the `database.yml`." + self.migrations_paths = [path] + end + + # For cases where a table doesn't exist like loading from schema cache + def current_version + MigrationContext.new(migrations_paths).current_version end end + self.migrations_paths = ["db/migrate"] + def initialize(direction, migrations, target_version = nil) @direction = direction @target_version = target_version @@ -1203,7 +1237,7 @@ module ActiveRecord end def load_migrated - @migrated_versions = Set.new(self.class.get_all_versions) + @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions) end private @@ -1235,7 +1269,7 @@ module ActiveRecord # Stores the current environment in the database. def record_environment return if down? - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment end def ran?(migration) @@ -1277,10 +1311,10 @@ module ActiveRecord end def validate(migrations) - name , = migrations.group_by(&:name).find { |_, v| v.length > 1 } + name, = migrations.group_by(&:name).find { |_, v| v.length > 1 } raise DuplicateMigrationNameError.new(name) if name - version , = migrations.group_by(&:version).find { |_, v| v.length > 1 } + version, = migrations.group_by(&:version).find { |_, v| v.length > 1 } raise DuplicateMigrationVersionError.new(version) if version end @@ -1294,23 +1328,6 @@ module ActiveRecord end end - def self.last_stored_environment - return nil if current_version == 0 - raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? - - environment = ActiveRecord::InternalMetadata[:environment] - raise NoEnvironmentInSchemaError unless environment - environment - end - - def self.current_environment - ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - end - - def self.protected_environment? - ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment - end - def up? @direction == :up end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index bd8c054c28..0edaaa0cf9 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -13,7 +13,10 @@ module ActiveRecord const_get(name) end - V5_2 = Current + V6_0 = Current + + class V5_2 < V6_0 + end class V5_1 < V5_2 def change_column(table_name, column_name, type, options = {}) @@ -28,6 +31,14 @@ module ActiveRecord super end end + + def create_table(table_name, options = {}) + if adapter_name == "Mysql2" + super(table_name, options: "ENGINE=InnoDB", **options) + else + super + end + end end class V5_0 < V5_1 diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 1941d3d5ea..b04dc04899 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -87,19 +87,6 @@ module ActiveRecord # Sets the name of the internal metadata table. ## - # :singleton-method: protected_environments - # :call-seq: protected_environments - # - # The array of names of environments where destructive actions should be prohibited. By default, - # the value is <tt>["production"]</tt>. - - ## - # :singleton-method: protected_environments= - # :call-seq: protected_environments=(environments) - # - # Sets an array of names of environments where destructive actions should be prohibited. - - ## # :singleton-method: pluralize_table_names # :call-seq: pluralize_table_names # @@ -122,9 +109,9 @@ module ActiveRecord class_attribute :table_name_suffix, instance_writer: false, default: "" class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations" class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata" - class_attribute :protected_environments, instance_accessor: false, default: [ "production" ] class_attribute :pluralize_table_names, instance_writer: false, default: true + self.protected_environments = ["production"] self.inheritance_column = "type" self.ignored_columns = [].freeze @@ -238,6 +225,21 @@ module ActiveRecord (parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix end + # The array of names of environments where destructive actions should be prohibited. By default, + # the value is <tt>["production"]</tt>. + def protected_environments + if defined?(@protected_environments) + @protected_environments + else + superclass.protected_environments + end + end + + # Sets an array of names of environments where destructive actions should be prohibited. + def protected_environments=(environments) + @protected_environments = environments.map(&:to_s) + end + # Defines the name of the table column which will store the class name on single-table # inheritance situations. # @@ -359,8 +361,9 @@ module ActiveRecord # it). # # +attr_name+ The name of the attribute to retrieve the type for. Must be - # a string + # a string or a symbol. def type_for_attribute(attr_name, &block) + attr_name = attr_name.to_s if block attribute_types.fetch(attr_name, &block) else @@ -376,6 +379,7 @@ module ActiveRecord end def _default_attributes # :nodoc: + load_schema @default_attributes ||= ActiveModel::AttributeSet.new({}) end @@ -423,7 +427,7 @@ module ActiveRecord # end def reset_column_information connection.clear_cache! - undefine_attribute_methods + ([self] + descendants).each(&:undefine_attribute_methods) connection.schema_cache.clear_data_source_cache!(table_name) reload_schema_from_cache diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a13b0d0181..a45d011d75 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -359,10 +359,10 @@ module ActiveRecord # Any change to the attributes on either instance will affect both instances. # If you want to change the sti column as well, use #becomes! instead. def becomes(klass) - became = klass.new + became = klass.allocate + became.send(:initialize) became.instance_variable_set("@attributes", @attributes) became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database) - became.instance_variable_set("@changed_attributes", attributes_changed_by_setter) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) became.errors.copy!(errors) @@ -402,11 +402,7 @@ module ActiveRecord verify_readonly_attribute(name) public_send("#{name}=", value) - if has_changes_to_save? - save(validate: false) - else - true - end + save(validate: false) end # Updates the attributes of the model from the passed-in hash and saves the @@ -698,6 +694,7 @@ module ActiveRecord def create_or_update(*args, &block) _raise_readonly_record_error if readonly? + return false if destroyed? result = new_record? ? _create_record(&block) : _update_record(*args, &block) result != false end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 4538ed6a5f..ade5946dd5 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -59,6 +59,7 @@ module ActiveRecord console = ActiveSupport::Logger.new(STDERR) Rails.logger.extend ActiveSupport::Logger.broadcast console end + ActiveRecord::Base.verbose_query_logs = false end runner do @@ -91,6 +92,7 @@ module ActiveRecord if File.file?(filename) current_version = ActiveRecord::Migrator.current_version + next if current_version.nil? cache = YAML.load(File.read(filename)) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index fce3e1c5cf..2e55713311 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -6,7 +6,7 @@ db_namespace = namespace :db do desc "Set the environment value for the database" task "environment:set" => :load_config do ActiveRecord::InternalMetadata.create_table - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment end task check_protected_environments: :load_config do @@ -99,9 +99,8 @@ db_namespace = namespace :db do ActiveRecord::Tasks::DatabaseTasks.check_target_version - ActiveRecord::Migrator.run( + ActiveRecord::Base.connection.migration_context.run( :up, - ActiveRecord::Tasks::DatabaseTasks.migrations_paths, ActiveRecord::Tasks::DatabaseTasks.target_version ) db_namespace["_dump"].invoke @@ -113,9 +112,8 @@ db_namespace = namespace :db do ActiveRecord::Tasks::DatabaseTasks.check_target_version - ActiveRecord::Migrator.run( + ActiveRecord::Base.connection.migration_context.run( :down, - ActiveRecord::Tasks::DatabaseTasks.migrations_paths, ActiveRecord::Tasks::DatabaseTasks.target_version ) db_namespace["_dump"].invoke @@ -131,8 +129,7 @@ db_namespace = namespace :db do puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 - paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths - ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name| + ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name| puts "#{status.center(8)} #{version.ljust(14)} #{name}" end puts @@ -142,14 +139,14 @@ db_namespace = namespace :db do desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." task rollback: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 - ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) + ActiveRecord::Base.connection.migration_context.rollback(step) db_namespace["_dump"].invoke end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' task forward: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 - ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) + ActiveRecord::Base.connection.migration_context.forward(step) db_namespace["_dump"].invoke end @@ -172,12 +169,12 @@ db_namespace = namespace :db do desc "Retrieves the current schema version number" task version: :load_config do - puts "Current version: #{ActiveRecord::Migrator.current_version}" + puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}" end # desc "Raises an error if there are pending migrations" task abort_if_pending_migrations: :load_config do - pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations + pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations if pending_migrations.any? puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 87bfd75bca..e640d75d2f 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -34,7 +34,8 @@ module ActiveRecord def self.add_reflection(ar, name, reflection) ar.clear_reflections_cache - ar._reflections = ar._reflections.merge(name.to_s => reflection) + name = name.to_s + ar._reflections = ar._reflections.except(name).merge!(name => reflection) end def self.add_aggregate_reflection(ar, name, reflection) @@ -290,10 +291,14 @@ module ActiveRecord end def build_scope(table, predicate_builder = predicate_builder(table)) - Relation.create(klass, table, predicate_builder) + Relation.create( + klass, + table: table, + predicate_builder: predicate_builder + ) end - def join_primary_key(_) + def join_primary_key(*) foreign_key end @@ -458,10 +463,6 @@ module ActiveRecord options[:primary_key] || primary_key(klass || self.klass) end - def association_primary_key_type - klass.type_for_attribute(association_primary_key.to_s) - end - def active_record_primary_key @active_record_primary_key ||= options[:primary_key] || primary_key(active_record) end @@ -567,7 +568,7 @@ module ActiveRecord end VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] - INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key] + INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key] def add_as_source(seed) seed @@ -722,7 +723,7 @@ module ActiveRecord end end - def join_primary_key(klass) + def join_primary_key(klass = nil) polymorphic? ? association_primary_key(klass) : association_primary_key end @@ -859,10 +860,6 @@ module ActiveRecord actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) end - def association_primary_key_type - klass.type_for_attribute(association_primary_key.to_s) - end - # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form. # # class Post < ActiveRecord::Base diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 081ef5771f..d71c430045 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -10,7 +10,7 @@ module ActiveRecord SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :skip_query_cache] CLAUSE_METHODS = [:where, :having, :from] - INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] + INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having] VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS @@ -22,7 +22,7 @@ module ActiveRecord alias :loaded? :loaded alias :locked? :lock_value - def initialize(klass, table, predicate_builder, values = {}) + def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) @klass = klass @table = table @values = values @@ -52,8 +52,8 @@ module ActiveRecord # # user = users.new { |user| user.name = 'Oscar' } # user.name # => Oscar - def new(*args, &block) - scoping { @klass.new(*args, &block) } + def new(attributes = nil, &block) + scoping { klass.new(scope_for_create(attributes), &block) } end alias build new @@ -77,8 +77,12 @@ module ActiveRecord # # users.create(name: nil) # validation on name # # => #<User id: nil, name: nil, ...> - def create(*args, &block) - scoping { @klass.create(*args, &block) } + def create(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + scoping { klass.create(scope_for_create(attributes), &block) } + end end # Similar to #create, but calls @@ -87,8 +91,12 @@ module ActiveRecord # # Expects arguments in the same format as # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. - def create!(*args, &block) - scoping { @klass.create!(*args, &block) } + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + scoping { klass.create!(scope_for_create(attributes), &block) } + end end def first_or_create(attributes = nil, &block) # :nodoc: @@ -306,10 +314,10 @@ module ActiveRecord stmt = Arel::UpdateManager.new - stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) + stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates)) stmt.table(table) - if has_join_values? + if has_join_values? || offset_value @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) else stmt.key = arel_attribute(primary_key) @@ -357,8 +365,8 @@ module ActiveRecord # # If an invalid method is supplied, #delete_all raises an ActiveRecordError: # - # Post.limit(100).delete_all - # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit + # Post.distinct.delete_all + # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct def delete_all invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| value = get_value(method) @@ -376,7 +384,7 @@ module ActiveRecord stmt = Arel::DeleteManager.new stmt.from(table) - if has_join_values? + if has_join_values? || has_limit_or_offset? @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) else stmt.wheres = arel.constraints @@ -422,7 +430,7 @@ module ActiveRecord relation = self if eager_loading? - find_with_associations { |rel, _| relation = rel } + apply_join_dependency { |rel, _| relation = rel } end conn = klass.connection @@ -440,8 +448,10 @@ module ActiveRecord where_clause.to_h(relation_table_name) end - def scope_for_create - where_values_hash.merge!(create_with_value.stringify_keys) + def scope_for_create(attributes = nil) + scope = where_values_hash.merge!(create_with_value.stringify_keys) + scope.merge!(attributes) if attributes + scope end # Returns true if relation needs eager loading. @@ -523,7 +533,7 @@ module ActiveRecord skip_query_cache_if_necessary do @records = if eager_loading? - find_with_associations do |relation, join_dependency| + apply_join_dependency do |relation, join_dependency| if ActiveRecord::NullRelation === relation [] else diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index d49472fc70..dba2f33dd4 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -131,7 +131,14 @@ module ActiveRecord def calculate(operation, column_name) if has_include?(column_name) relation = apply_join_dependency - relation.distinct! if operation.to_s.downcase == "count" + + if operation.to_s.downcase == "count" && !distinct_value + relation.distinct! + # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT + if (column_name == :all || column_name.nil?) && select_values.empty? + relation.order_values = [] + end + end relation.calculate(operation, column_name) else @@ -217,7 +224,7 @@ module ActiveRecord if operation == "count" column_name ||= select_for_count if column_name == :all - if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?)) + if distinct && (group_values.any? || select_values.empty? && order_values.empty?) column_name = primary_key end elsif column_name =~ /\s*DISTINCT[\s(]+/i @@ -235,7 +242,7 @@ module ActiveRecord def aggregate_column(column_name) return column_name if Arel::Expressions === column_name - if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s) + if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name) @klass.arel_attribute(column_name) else Arel.sql(column_name == :all ? "*" : column_name.to_s) @@ -249,7 +256,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: column_alias = column_name - if operation == "count" && has_limit_or_offset? + if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?) # Shortcut when limit is zero. return 0 if limit_value == 0 @@ -391,14 +398,12 @@ module ActiveRecord end def build_count_subquery(relation, column_name, distinct) - relation.select_values = [ - if column_name == :all - distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE) - else - column_alias = Arel.sql("count_column") - aggregate_column(column_name).as(column_alias) - end - ] + if column_name == :all + relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct + else + column_alias = Arel.sql("count_column") + relation.select_values = [ aggregate_column(column_name).as(column_alias) ] + end subquery = relation.arel.as(Arel.sql("subquery_for_count")) select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index ff06ecbee1..5f959af5dc 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -148,7 +148,7 @@ module ActiveRecord # # [#<Person id:4>, #<Person id:3>, #<Person id:2>] def last(limit = nil) - return find_last(limit) if loaded? || limit_value + return find_last(limit) if loaded? || has_limit_or_offset? result = ordered_relation.limit(limit) result = result.reverse_order! @@ -313,7 +313,7 @@ module ActiveRecord return false if !conditions || limit_value == 0 if eager_loading? - relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + relation = apply_join_dependency(eager_loading: false) return relation.exists?(conditions) end @@ -358,24 +358,6 @@ module ActiveRecord offset_value || 0 end - def find_with_associations - # NOTE: the JoinDependency constructed here needs to know about - # any joins already present in `self`, so pass them in - # - # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136 - # incorrect SQL is generated. In that case, the join dependency for - # SpecialCategorizations is constructed without knowledge of the - # preexisting join in joins_values to categorizations (by way of - # the `has_many :through` for categories). - # - join_dependency = construct_join_dependency - - relation = apply_join_dependency(join_dependency) - relation._select!(join_dependency.aliases.columns) - - yield relation, join_dependency - end - def construct_relation_for_exists(conditions) relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) @@ -391,22 +373,29 @@ module ActiveRecord def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values + joins = joins_values.select { |join| join.is_a?(Arel::Nodes::Join) } ActiveRecord::Associations::JoinDependency.new( - klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading + klass, table, including, alias_tracker(joins), eager_loading: eager_loading ) end - def apply_join_dependency(join_dependency = construct_join_dependency) + def apply_join_dependency(eager_loading: true) + join_dependency = construct_join_dependency(eager_loading: eager_loading) relation = except(:includes, :eager_load, :preload).joins!(join_dependency) - if using_limitable_reflections?(join_dependency.reflections) - relation - else - if relation.limit_value + if eager_loading && !using_limitable_reflections?(join_dependency.reflections) + if has_limit_or_offset? limited_ids = limited_ids_for(relation) limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) end - relation.except(:limit, :offset) + relation.limit_value = relation.offset_value = nil + end + + if block_given? + relation._select!(join_dependency.aliases.columns) + yield relation, join_dependency + else + relation end end @@ -532,7 +521,11 @@ module ActiveRecord else relation = ordered_relation - if limit_value.nil? || index < limit_value + if limit_value + limit = [limit_value - index, limit].min + end + + if limit > 0 relation = relation.offset(offset_index + index) unless index.zero? relation.limit(limit).to_a else @@ -547,12 +540,11 @@ module ActiveRecord else relation = ordered_relation - relation.to_a[-index] - # TODO: can be made more performant on large result sets by - # for instance, last(index)[-index] (which would require - # refactoring the last(n) finder method to make test suite pass), - # or by using a combination of reverse_order, limit, and offset, - # e.g., reverse_order.offset(index-1).first + if equal?(relation) || has_limit_or_offset? + relation.records[-index] + else + relation.last(index)[-index] + end end end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index b736b21525..25510d4a57 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -23,7 +23,11 @@ module ActiveRecord # build a relation to merge in rather than directly merging # the values. def other - other = Relation.create(relation.klass, relation.table, relation.predicate_builder) + other = Relation.create( + relation.klass, + table: relation.table, + predicate_builder: relation.predicate_builder + ) hash.each { |k, v| if k == :joins if Hash === v @@ -52,7 +56,7 @@ module ActiveRecord NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS - - [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: + [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: def normal_values NORMAL_VALUES @@ -79,6 +83,7 @@ module ActiveRecord merge_clauses merge_preloads merge_joins + merge_outer_joins relation end @@ -129,6 +134,29 @@ module ActiveRecord end end + def merge_outer_joins + return if other.left_outer_joins_values.blank? + + if other.klass == relation.klass + relation.left_outer_joins!(*other.left_outer_joins_values) + else + alias_tracker = nil + joins_dependency = other.left_outer_joins_values.map do |join| + case join + when Hash, Symbol, Array + alias_tracker ||= other.alias_tracker + ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, join, alias_tracker + ) + else + join + end + end + + relation.left_outer_joins!(*joins_dependency) + end + end + def merge_multi_values if other.reordering_value # override any order specified in the original relation diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 885c26d7aa..f3286846d2 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -86,6 +86,18 @@ module ActiveRecord expand_from_hash(query).reduce(&:and) end queries.reduce(&:or) + elsif table.aggregated_with?(key) + mapping = table.reflect_on_aggregation(key).mapping + queries = Array.wrap(value).map do |object| + mapping.map do |field_attr, aggregate_attr| + if mapping.size == 1 && !object.respond_to?(aggregate_attr) + build(table.arel_attribute(field_attr), object) + else + build(table.arel_attribute(field_attr), object.send(aggregate_attr)) + end + end.reduce(&:and) + end + queries.reduce(&:or) # FIXME: Deprecate this and provide a public API to force equality elsif (value.is_a?(Range) || value.is_a?(Array)) && table.type(key.to_s).respond_to?(:subtype) diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb index 0255a65bfe..28c7483c95 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -30,7 +30,7 @@ module ActiveRecord end def primary_key - associated_table.association_join_keys.key + associated_table.association_join_primary_key end def convert_to_id(value) diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb index b87b5c36dd..e8e2f2c626 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -29,7 +29,7 @@ module ActiveRecord end def primary_key(value) - associated_table.association_primary_key(base_class(value)) + associated_table.association_join_primary_key(base_class(value)) end def base_class(value) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 749223422f..86882c7ce7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -327,8 +327,8 @@ module ActiveRecord end VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, - :limit, :offset, :joins, :includes, :from, - :readonly, :having]) + :limit, :offset, :joins, :left_outer_joins, + :includes, :from, :readonly, :having]) # Removes an unwanted relation that is already defined on a chain of relations. # This is useful when passing around chains of relations and would like to @@ -375,10 +375,11 @@ module ActiveRecord args.each do |scope| case scope when Symbol + scope = :left_outer_joins if scope == :left_joins if !VALID_UNSCOPING_VALUES.include?(scope) raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." end - set_value(scope, nil) + set_value(scope, DEFAULT_VALUES[scope]) when Hash scope.each do |key, target_value| if key != :where @@ -444,15 +445,13 @@ module ActiveRecord # def left_outer_joins(*args) check_if_method_has_arguments!(__callee__, args) - - args.compact! - args.flatten! - spawn.left_outer_joins!(*args) end alias :left_joins :left_outer_joins def left_outer_joins!(*args) # :nodoc: + args.compact! + args.flatten! self.left_outer_joins_values += args self end @@ -907,7 +906,7 @@ module ActiveRecord protected # Returns a relation value with a given name def get_value(name) # :nodoc: - @values[name] || default_value_for(name) + @values.fetch(name, DEFAULT_VALUES[name]) end # Sets the relation value with the given name @@ -980,6 +979,8 @@ module ActiveRecord case join when Hash, Symbol, Array :association_join + when ActiveRecord::Associations::JoinDependency + :stashed_join else raise ArgumentError, "only Hash, Symbol and Array are allowed" end @@ -1015,7 +1016,7 @@ module ActiveRecord join_nodes = buckets[:join_node].uniq string_joins = buckets[:string_join].map(&:strip).uniq - join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) + join_list = join_nodes + convert_join_strings_to_ast(string_joins) alias_tracker = alias_tracker(join_list, aliases) join_dependency = ActiveRecord::Associations::JoinDependency.new( @@ -1030,7 +1031,7 @@ module ActiveRecord alias_tracker.aliases end - def convert_join_strings_to_ast(table, joins) + def convert_join_strings_to_ast(joins) joins .flatten .reject(&:blank?) @@ -1040,8 +1041,8 @@ module ActiveRecord def build_select(arel) if select_values.any? arel.project(*arel_columns(select_values.uniq)) - elsif @klass.ignored_columns.any? - arel.project(*arel_columns(@klass.column_names)) + elsif klass.ignored_columns.any? + arel.project(*klass.column_names.map { |field| arel_attribute(field) }) else arel.project(table[Arel.star]) end @@ -1121,7 +1122,7 @@ module ActiveRecord def preprocess_order_args(order_args) order_args.map! do |arg| - klass.send(:sanitize_sql_for_order, arg) + klass.sanitize_sql_for_order(arg) end order_args.flatten! @@ -1192,7 +1193,6 @@ module ActiveRecord DEFAULT_VALUES = { create_with: FROZEN_EMPTY_HASH, - readonly: false, where: Relation::WhereClause.empty, having: Relation::WhereClause.empty, from: Relation::FromClause.empty @@ -1201,15 +1201,5 @@ module ActiveRecord Relation::MULTI_VALUE_METHODS.each do |value| DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY end - - Relation::SINGLE_VALUE_METHODS.each do |value| - DEFAULT_VALUES[value] = nil if DEFAULT_VALUES[value].nil? - end - - def default_value_for(name) - DEFAULT_VALUES.fetch(name) do - raise ArgumentError, "unknown relation value #{name.inspect}" - end - end end end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 617d8de8b2..562e04194c 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -69,7 +69,7 @@ module ActiveRecord private def relation_with(values) - result = Relation.create(klass, table, predicate_builder, values) + result = Relation.create(klass, values: values) result.extend(*extending_values) if extending_values.any? result end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 752bb38481..a502713e56 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -47,7 +47,7 @@ module ActiveRecord end def to_h(table_name = nil) - equalities = predicates.grep(Arel::Nodes::Equality) + equalities = equalities(predicates) if table_name equalities = equalities.select do |node| node.left.relation.name == table_name @@ -90,6 +90,20 @@ module ActiveRecord end private + def equalities(predicates) + equalities = [] + + predicates.each do |node| + case node + when Arel::Nodes::Equality + equalities << node + when Arel::Nodes::And + equalities.concat equalities(node.children) + end + end + + equalities + end def predicates_unreferenced_by(other) predicates.reject do |n| @@ -121,7 +135,7 @@ module ActiveRecord end def except_predicates(columns) - self.predicates.reject do |node| + predicates.reject do |node| case node when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 1374785354..92b720068c 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -11,10 +11,9 @@ module ActiveRecord def build(opts, other) case opts when String, Array - parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] + parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))] when Hash attributes = predicate_builder.resolve_column_aliases(opts) - attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) attributes.stringify_keys! parts = predicate_builder.build_from_hash(attributes) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 21f8bc7cb2..c6c268855e 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -5,80 +5,135 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - private - - # Accepts an array or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a WHERE clause. - # - # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" - # - # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") - # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition) # :doc: - return nil if condition.blank? - - case condition - when Array; sanitize_sql_array(condition) - else condition - end + # Accepts an array or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a WHERE clause. + # + # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_for_conditions(condition) + return nil if condition.blank? + + case condition + when Array; sanitize_sql_array(condition) + else condition end - alias :sanitize_sql :sanitize_sql_for_conditions - - # Accepts an array, hash, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for a SET clause. - # - # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) - # # => "name=NULL and group_id=4" - # - # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) - # # => "name=NULL and group_id=4" - # - # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 }) - # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" - # - # sanitize_sql_for_assignment("name=NULL and group_id='4'") - # # => "name=NULL and group_id='4'" - def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc: - case assignments - when Array; sanitize_sql_array(assignments) - when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) - else assignments - end + end + alias :sanitize_sql :sanitize_sql_for_conditions + + # Accepts an array, hash, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a SET clause. + # + # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) + # # => "name=NULL and group_id=4" + # + # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) + # # => "name=NULL and group_id=4" + # + # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 }) + # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" + def sanitize_sql_for_assignment(assignments, default_table_name = table_name) + case assignments + when Array; sanitize_sql_array(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) + else assignments end - - # Accepts an array, or string of SQL conditions and sanitizes - # them into a valid SQL fragment for an ORDER clause. - # - # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) - # # => "field(id, 1,3,2)" - # - # sanitize_sql_for_order("id ASC") - # # => "id ASC" - def sanitize_sql_for_order(condition) # :doc: - if condition.is_a?(Array) && condition.first.to_s.include?("?") - enforce_raw_sql_whitelist([condition.first], - whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST - ) - - # Ensure we aren't dealing with a subclass of String that might - # override methods we use (eg. Arel::Nodes::SqlLiteral). - if condition.first.kind_of?(String) && !condition.first.instance_of?(String) - condition = [String.new(condition.first), *condition[1..-1]] - end - - Arel.sql(sanitize_sql_array(condition)) - else - condition + end + + # Accepts an array, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for an ORDER clause. + # + # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) + # # => "field(id, 1,3,2)" + # + # sanitize_sql_for_order("id ASC") + # # => "id ASC" + def sanitize_sql_for_order(condition) + if condition.is_a?(Array) && condition.first.to_s.include?("?") + enforce_raw_sql_whitelist([condition.first], + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + + # Ensure we aren't dealing with a subclass of String that might + # override methods we use (eg. Arel::Nodes::SqlLiteral). + if condition.first.kind_of?(String) && !condition.first.instance_of?(String) + condition = [String.new(condition.first), *condition[1..-1]] end + + Arel.sql(sanitize_sql_array(condition)) + else + condition end + end + + # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. + # + # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") + # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" + def sanitize_sql_hash_for_assignment(attrs, table) + c = connection + attrs.map do |attr, value| + type = type_for_attribute(attr) + value = type.serialize(type.cast(value)) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" + end.join(", ") + end + + # Sanitizes a +string+ so that it is safe to use within an SQL + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end + end + private # Accepts a hash of SQL conditions and replaces those attributes # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] # relationship with their expanded aggregate attribute values. @@ -100,10 +155,12 @@ module ActiveRecord if aggregation = reflect_on_aggregation(attr.to_sym) mapping = aggregation.mapping mapping.each do |field_attr, aggregate_attr| - if mapping.size == 1 && !value.respond_to?(aggregate_attr) - expanded_attrs[field_attr] = value + expanded_attrs[field_attr] = if value.is_a?(Array) + value.map { |it| it.send(aggregate_attr) } + elsif mapping.size == 1 && !value.respond_to?(aggregate_attr) + value else - expanded_attrs[field_attr] = value.send(aggregate_attr) + value.send(aggregate_attr) end end else @@ -112,62 +169,7 @@ module ActiveRecord end expanded_attrs end - - # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. - # - # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") - # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" - def sanitize_sql_hash_for_assignment(attrs, table) # :doc: - c = connection - attrs.map do |attr, value| - type = type_for_attribute(attr.to_s) - value = type.serialize(type.cast(value)) - "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" - end.join(", ") - end - - # Sanitizes a +string+ so that it is safe to use within an SQL - # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". - # - # sanitize_sql_like("100%") - # # => "100\\%" - # - # sanitize_sql_like("snake_cased_string") - # # => "snake\\_cased\\_string" - # - # sanitize_sql_like("100%", "!") - # # => "100!%" - # - # sanitize_sql_like("snake_cased_string", "!") - # # => "snake!_cased!_string" - def sanitize_sql_like(string, escape_character = "\\") # :doc: - pattern = Regexp.union(escape_character, "%", "_") - string.gsub(pattern) { |x| [escape_character, x].join } - end - - # Accepts an array of conditions. The array has each value - # sanitized and interpolated into the SQL statement. - # - # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) - # # => "name='foo''bar' and group_id=4" - # - # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) - # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_array(ary) # :doc: - statement, *values = ary - if values.first.is_a?(Hash) && /:\w+/.match?(statement) - replace_named_bind_variables(statement, values.first) - elsif statement.include?("?") - replace_bind_variables(statement, values) - elsif statement.blank? - statement - else - statement % values.collect { |value| connection.quote_string(value.to_s) } - end - end + deprecate :expand_hash_conditions_for_aggregates def replace_bind_variables(statement, values) raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 1e121f2a09..216359867c 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -55,7 +55,7 @@ module ActiveRecord end ActiveRecord::InternalMetadata.create_table - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment end private diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 16ccba6b6c..b8d848b999 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -44,7 +44,7 @@ module ActiveRecord def initialize(connection, options = {}) @connection = connection - @version = Migrator::current_version rescue nil + @version = connection.migration_context.current_version rescue nil @options = options end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 202b82fa61..6dbc977f9a 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -135,7 +135,7 @@ module ActiveRecord end def store_accessor_for(store_attribute) - type_for_attribute(store_attribute.to_s).accessor + type_for_attribute(store_attribute).accessor end class HashAccessor # :nodoc: diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index a5187efc84..d3b9171159 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -2,8 +2,7 @@ module ActiveRecord class TableMetadata # :nodoc: - delegate :foreign_type, :foreign_key, :join_keys, :join_foreign_key, to: :association, prefix: true - delegate :association_primary_key, to: :association + delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true def initialize(klass, arel_table, association = nil) @klass = klass @@ -31,7 +30,7 @@ module ActiveRecord def type(column_name) if klass - klass.type_for_attribute(column_name.to_s) + klass.type_for_attribute(column_name) else Type.default_value end @@ -66,6 +65,14 @@ module ActiveRecord association && association.polymorphic? end + def aggregated_with?(aggregation_name) + klass && reflect_on_aggregation(aggregation_name) + end + + def reflect_on_aggregation(aggregation_name) + klass.reflect_on_aggregation(aggregation_name) + end + # TODO Change this to private once we've dropped Ruby 2.2 support. # Workaround for Ruby 2.2 "private attribute?" warning. protected diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 4657e51e6d..d8e0cd1e30 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -54,10 +54,10 @@ module ActiveRecord def check_protected_environments! unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] - current = ActiveRecord::Migrator.current_environment - stored = ActiveRecord::Migrator.last_stored_environment + current = ActiveRecord::Base.connection.migration_context.current_environment + stored = ActiveRecord::Base.connection.migration_context.last_stored_environment - if ActiveRecord::Migrator.protected_environment? + if ActiveRecord::Base.connection.migration_context.protected_environment? raise ActiveRecord::ProtectedEnvironmentError.new(stored) end @@ -117,9 +117,9 @@ module ActiveRecord def create(*arguments) configuration = arguments.first class_for_adapter(configuration["adapter"]).new(*arguments).create - $stdout.puts "Created database '#{configuration['database']}'" + $stdout.puts "Created database '#{configuration['database']}'" if verbose? rescue DatabaseAlreadyExists - $stderr.puts "Database '#{configuration['database']}' already exists" + $stderr.puts "Database '#{configuration['database']}' already exists" if verbose? rescue Exception => error $stderr.puts error $stderr.puts "Couldn't create database for #{configuration.inspect}" @@ -144,7 +144,7 @@ module ActiveRecord def drop(*arguments) configuration = arguments.first class_for_adapter(configuration["adapter"]).new(*arguments).drop - $stdout.puts "Dropped database '#{configuration['database']}'" + $stdout.puts "Dropped database '#{configuration['database']}'" if verbose? rescue ActiveRecord::NoDatabaseError $stderr.puts "Database '#{configuration['database']}' does not exist" rescue Exception => error @@ -163,13 +163,17 @@ module ActiveRecord } end + def verbose? + ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true + end + def migrate check_target_version - verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true + verbose = verbose? scope = ENV["SCOPE"] verbose_was, Migration.verbose = Migration.verbose, verbose - Migrator.migrate(migrations_paths, target_version) do |migration| + Base.connection.migration_context.migrate(target_version) do |migration| scope.blank? || scope == migration.scope end ActiveRecord::Base.clear_cache! diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb index d51350ba83..c5cfdba8b1 100644 --- a/activerecord/lib/active_record/type_caster/map.rb +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -9,7 +9,7 @@ module ActiveRecord def type_cast_for_database(attr_name, value) return value if value.is_a?(Arel::Nodes::BindParam) - type = types.type_for_attribute(attr_name.to_s) + type = types.type_for_attribute(attr_name) type.serialize(value) end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 9aaa2852d0..0092146e09 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -78,7 +78,7 @@ module ActiveRecord idx_name = "accounts_idx" indexes = @connection.indexes("accounts") - assert indexes.empty? + assert_empty indexes @connection.add_index :accounts, :firm_id, name: idx_name indexes = @connection.indexes("accounts") @@ -368,16 +368,16 @@ module ActiveRecord unless in_memory_db? test "transaction state is reset after a reconnect" do @connection.begin_transaction - assert @connection.transaction_open? + assert_predicate @connection, :transaction_open? @connection.reconnect! - assert !@connection.transaction_open? + assert_not_predicate @connection, :transaction_open? end test "transaction state is reset after a disconnect" do @connection.begin_transaction - assert @connection.transaction_open? + assert_predicate @connection, :transaction_open? @connection.disconnect! - assert !@connection.transaction_open? + assert_not_predicate @connection, :transaction_open? end end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 4e73c557ed..9ae2c42368 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -68,14 +68,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def (ActiveRecord::Base.connection).data_source_exists?(*); false; end %w(SPATIAL FULLTEXT UNIQUE).each do |type| - expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB" + expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))" actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| t.index :last_name, type: type end assert_equal expected, actual end - expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB" + expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))" actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t| t.index :last_name, length: 10, using: :btree end @@ -108,7 +108,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase def test_create_mysql_database_with_encoding assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1") - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, charset: :big5, collation: :big5_chinese_ci) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin") end def test_recreate_mysql_database_with_encoding @@ -160,7 +160,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) - expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" + expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query" actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t| t.index :zip end diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index fd5f712f1a..aa870349be 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -14,8 +14,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase end def test_case_sensitive - assert !CollationTest.columns_hash["string_ci_column"].case_sensitive? - assert CollationTest.columns_hash["string_cs_column"].case_sensitive? + assert_not_predicate CollationTest.columns_hash["string_ci_column"], :case_sensitive? + assert_predicate CollationTest.columns_hash["string_cs_column"], :case_sensitive? end def test_case_insensitive_comparison_for_ci_column diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 13b4096671..726f58d58e 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -40,29 +40,29 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_no_automatic_reconnection_after_timeout - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 - assert !@connection.active? + assert_not_predicate @connection, :active? ensure # Repair all fixture connections so other tests won't break. @fixture_connections.each(&:verify!) end def test_successful_reconnection_after_timeout_with_manual_reconnect - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 @connection.reconnect! - assert @connection.active? + assert_predicate @connection, :active? end def test_successful_reconnection_after_timeout_with_verify - assert @connection.active? + assert_predicate @connection, :active? @connection.update("set @@wait_timeout=1") sleep 2 @connection.verify! - assert @connection.active? + assert_predicate @connection, :active? end def test_execute_after_disconnect diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb index 108bec832c..832f5d61d1 100644 --- a/activerecord/test/cases/adapters/mysql2/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -13,11 +13,11 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase def test_should_not_be_unsigned column = EnumTest.columns_hash["enum_column"] - assert_not column.unsigned? + assert_not_predicate column, :unsigned? end def test_should_not_be_bigint column = EnumTest.columns_hash["enum_column"] - assert_not column.bigint? + assert_not_predicate column, :bigint? end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 62abd694bb..d7d9a2d732 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -36,7 +36,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase assert connection.column_exists?(table_name, :key, :string) end ensure - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment end private diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb index 9f6bd38a8c..1c92df940f 100644 --- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -42,3 +42,78 @@ class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase assert_match %r{COLLATE=utf8mb4_bin}, options end end + +class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper + self.use_transactional_tests = false + + def setup + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Migration.verbose = false + end + + def teardown + ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true + ActiveRecord::Migration.verbose = @verbose_was + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + test "schema dump includes ENGINE=InnoDB if not provided" do + ActiveRecord::Base.connection.create_table "mysql_table_options", force: true + + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=InnoDB}, options + end + + test "schema dump includes ENGINE=InnoDB in legacy migrations" do + migration = Class.new(ActiveRecord::Migration[5.1]) do + def migrate(x) + create_table "mysql_table_options", force: true + end + end.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + output = dump_table_schema("mysql_table_options") + options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options] + assert_match %r{ENGINE=InnoDB}, options + end +end + +class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase + self.use_transactional_tests = false + + def setup + @logger_was = ActiveRecord::Base.logger + @log = StringIO.new + @verbose_was = ActiveRecord::Migration.verbose + ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log) + ActiveRecord::Migration.verbose = false + end + + def teardown + ActiveRecord::Base.logger = @logger_was + ActiveRecord::Migration.verbose = @verbose_was + ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + test "new migrations do not contain default ENGINE=InnoDB option" do + ActiveRecord::Base.connection.create_table "mysql_table_options", force: true + + assert_no_match %r{ENGINE=InnoDB}, @log.string + end + + test "legacy migrations contain default ENGINE=InnoDB option" do + migration = Class.new(ActiveRecord::Migration[5.1]) do + def migrate(x) + create_table "mysql_table_options", force: true + end + end.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert_match %r{ENGINE=InnoDB}, @log.string + end +end diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index cb183cc54c..f921515c10 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -13,6 +13,7 @@ module ActiveRecord setup do @abort, Thread.abort_on_exception = Thread.abort_on_exception, false + Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception) @connection = ActiveRecord::Base.connection @connection.clear_cache! @@ -31,6 +32,7 @@ module ActiveRecord @connection.drop_table "samples", if_exists: true Thread.abort_on_exception = @abort + Thread.report_on_exception = @original_report_on_exception if Thread.respond_to?(:report_on_exception) end test "raises Deadlocked when a deadlock is encountered" do diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index b01f5d7f5a..97da96003d 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -54,7 +54,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase end @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column| - assert column.unsigned? + assert_predicate column, :unsigned? end end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 99c53dadeb..308ad1d854 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -62,6 +62,12 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops)) assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops }) + expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC NULLS LAST, "first_name" ASC)) + assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: "DESC NULLS LAST", first_name: :asc }) + + expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST)) + assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST") + assert_raise ArgumentError do add_index(:people, :last_name, algorithm: :copy) end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 0e9e86f425..58fa7532a2 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -39,12 +39,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase def test_column assert_equal :string, @column.type assert_equal "character varying(255)", @column.sql_type - assert @column.array? - assert_not @type.binary? + assert_predicate @column, :array? + assert_not_predicate @type, :binary? ratings_column = PgArray.columns_hash["ratings"] assert_equal :integer, ratings_column.type - assert ratings_column.array? + assert_predicate ratings_column, :array? end def test_not_compatible_with_serialize_array @@ -109,7 +109,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase assert_equal :text, column.type assert_equal [], PgArray.column_defaults["snippets"] - assert column.array? + assert_predicate column, :array? end def test_change_column_cant_make_non_array_column_to_array @@ -228,7 +228,9 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase def test_insert_fixtures tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"] - @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays") + assert_deprecated do + @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays") + end assert_equal(PgArray.last.tags, tag_values) end @@ -255,7 +257,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x = PgArray.create!(tags: tags) x.reload - refute x.changed? + assert_not_predicate x, :changed? end def test_quoting_non_standard_delimiters @@ -277,7 +279,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x.reload assert_equal %w(one two three), x.tags - assert_not x.changed? + assert_not_predicate x, :changed? end def test_mutate_value_in_array @@ -288,7 +290,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase x.reload assert_equal [{ "a" => "c" }, { "b" => "b" }], x.hstores - assert_not x.changed? + assert_not_predicate x, :changed? end def test_datetime_with_timezone_awareness diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index df04299569..c8e728bbb6 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -29,20 +29,20 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlBitString.columns_hash["a_bit"] assert_equal :bit, column.type assert_equal "bit(8)", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlBitString.type_for_attribute("a_bit") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_bit_string_varying_column column = PostgresqlBitString.columns_hash["a_bit_varying"] assert_equal :bit_varying, column.type assert_equal "bit varying(4)", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlBitString.type_for_attribute("a_bit_varying") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index a6bee113ff..64bb6906cd 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -75,7 +75,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_equal(data, record.payload) end @@ -101,14 +101,14 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log")) assert(data.size > 1) record = ByteaDataType.create(payload: data) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) - assert_not record.new_record? + assert_not_predicate record, :new_record? assert_nil(record.payload) assert_nil(ByteaDataType.where(id: record.id).first.payload) end diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb index adf461a9cc..6dba4f3e14 100644 --- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -33,7 +33,7 @@ module ActiveRecord connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp column = connection.columns(:strings).find { |c| c.name == "somedate" } assert_equal :datetime, column.type - assert column.array? + assert_predicate column, :array? end end end diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index a25f102bad..9eb0b7d99c 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -32,10 +32,10 @@ class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase column = Citext.columns_hash["cival"] assert_equal :citext, column.type assert_equal "citext", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = Citext.type_for_attribute("cival") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_change_table_supports_json diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 5da95f7e2c..b0ce2694a3 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -51,10 +51,10 @@ class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlComposite.columns_hash["address"] assert_nil column.type assert_equal "full_address", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlComposite.type_for_attribute("address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_composite_mapping @@ -113,10 +113,10 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlComposite.columns_hash["address"] assert_equal :full_address, column.type assert_equal "full_address", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlComposite.type_for_attribute("address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_composite_mapping diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 81358b8fc4..d1b3c434e1 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -157,7 +157,7 @@ module ActiveRecord original_connection_pid = @connection.query("select pg_backend_pid()") # Sanity check. - assert @connection.active? + assert_predicate @connection, :active? if @connection.send(:postgresql_version) >= 90200 secondary_connection = ActiveRecord::Base.connection_pool.checkout @@ -176,7 +176,7 @@ module ActiveRecord @connection.verify! - assert @connection.active? + assert_predicate @connection, :active? # If we get no exception here, then either we re-connected successfully, or # we never actually got disconnected. diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 9c3817e2ad..eeaad94c27 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -30,10 +30,10 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlDomain.columns_hash["price"] assert_equal :decimal, column.type assert_equal "custom_money", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlDomain.type_for_attribute("price") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_domain_acts_like_basetype @@ -44,6 +44,6 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase record.price = "34.15" record.save! - assert_equal BigDecimal.new("34.15"), record.reload.price + assert_equal BigDecimal("34.15"), record.reload.price end end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 3d3cbe11a3..6789ff63e7 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -32,10 +32,10 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlEnum.columns_hash["current_mood"] assert_equal :enum, column.type assert_equal "mood", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlEnum.type_for_attribute("current_mood") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_enum_defaults @@ -73,7 +73,7 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase @connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');" stderr_output = capture(:stderr) { PostgresqlEnum.first } - assert stderr_output.blank? + assert_predicate stderr_output, :blank? end def test_enum_type_cast diff --git a/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb new file mode 100644 index 0000000000..4fa315ad23 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "cases/helper" +require "models/professor" + +if ActiveRecord::Base.connection.supports_foreign_tables? + class ForeignTableTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + class ForeignProfessor < ActiveRecord::Base + self.table_name = "foreign_professors" + end + + class ForeignProfessorWithPk < ForeignProfessor + self.primary_key = "id" + end + + def setup + @professor = Professor.create(name: "Nicola") + + @connection = ActiveRecord::Base.connection + enable_extension!("postgres_fdw", @connection) + + foreign_db_config = ARTest.connection_config["arunit2"] + @connection.execute <<-SQL + CREATE SERVER foreign_server + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '#{foreign_db_config["database"]}') + SQL + + @connection.execute <<-SQL + CREATE USER MAPPING FOR CURRENT_USER + SERVER foreign_server + SQL + + @connection.execute <<-SQL + CREATE FOREIGN TABLE foreign_professors ( + id int, + name character varying NOT NULL + ) SERVER foreign_server OPTIONS ( + table_name 'professors' + ) + SQL + end + + def teardown + disable_extension!("postgres_fdw", @connection) + @connection.execute <<-SQL + DROP SERVER IF EXISTS foreign_server CASCADE + SQL + end + + def test_table_exists + table_name = ForeignProfessor.table_name + assert_not ActiveRecord::Base.connection.table_exists?(table_name) + end + + def test_foreign_tables_are_valid_data_sources + table_name = ForeignProfessor.table_name + assert @connection.data_source_exists?(table_name), "'#{table_name}' should be a data source" + end + + def test_foreign_tables + assert_equal ["foreign_professors"], @connection.foreign_tables + end + + def test_foreign_table_exists + assert @connection.foreign_table_exists?("foreign_professors") + assert @connection.foreign_table_exists?(:foreign_professors) + assert_not @connection.foreign_table_exists?("nonexistingtable") + assert_not @connection.foreign_table_exists?("'") + assert_not @connection.foreign_table_exists?(nil) + end + + def test_attribute_names + assert_equal ["id", "name"], ForeignProfessor.attribute_names + end + + def test_attributes + professor = ForeignProfessorWithPk.find(@professor.id) + assert_equal @professor.attributes, professor.attributes + end + + def test_does_not_have_a_primary_key + assert_nil ForeignProfessor.primary_key + end + + def test_insert_record + # Explicit `id` here to avoid complex configurations to implicitly work with remote table + ForeignProfessorWithPk.create!(id: 100, name: "Leonardo") + + professor = ForeignProfessorWithPk.last + assert_equal "Leonardo", professor.name + end + + def test_update_record + professor = ForeignProfessorWithPk.find(@professor.id) + professor.name = "Albert" + professor.save! + professor.reload + assert_equal "Albert", professor.name + end + + def test_delete_record + professor = ForeignProfessorWithPk.find(@professor.id) + assert_difference("ForeignProfessor.count", -1) { professor.destroy } + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index c6f1e1727f..95dee3bf44 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -22,10 +22,10 @@ class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase column = Tsvector.columns_hash["text_vector"] assert_equal :tsvector, column.type assert_equal "tsvector", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = Tsvector.type_for_attribute("text_vector") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_update_tsvector diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index e1ba00e07b..8c6f046553 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -39,10 +39,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlPoint.columns_hash["x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlPoint.type_for_attribute("x") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default @@ -79,7 +79,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase p.reload assert_equal ActiveRecord::Point.new(10.0, 25.0), p.x - assert_not p.changed? + assert_not_predicate p, :changed? end def test_array_assignment @@ -117,10 +117,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlPoint.columns_hash["legacy_x"] assert_equal :point, column.type assert_equal "point", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlPoint.type_for_attribute("legacy_x") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_legacy_default @@ -157,7 +157,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase p.reload assert_equal [10.0, 25.0], p.legacy_x - assert_not p.changed? + assert_not_predicate p, :changed? end end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index f09e34b5f2..4b061a9375 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -40,7 +40,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase end def test_hstore_included_in_extensions - assert @connection.respond_to?(:extensions), "connection should have a list of extensions" + assert_respond_to @connection, :extensions assert_includes @connection.extensions, "hstore", "extension list should include hstore" end @@ -58,9 +58,9 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase def test_column assert_equal :hstore, @column.type assert_equal "hstore", @column.sql_type - assert_not @column.array? + assert_not_predicate @column, :array? - assert_not @type.binary? + assert_not_predicate @type, :binary? end def test_default @@ -165,7 +165,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase hstore.reload assert_equal "four", hstore.settings["three"] - assert_not hstore.changed? + assert_not_predicate hstore, :changed? end def test_dirty_from_user_equal @@ -174,7 +174,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase hstore.settings = { "key" => "value", "alongkey" => "anything" } assert_equal settings, hstore.settings - refute hstore.changed? + assert_not_predicate hstore, :changed? end def test_hstore_dirty_from_database_equal @@ -184,7 +184,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase assert_equal settings, hstore.settings hstore.settings = settings - refute hstore.changed? + assert_not_predicate hstore, :changed? end def test_gen1 diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index eca29f2892..8349ee6ee2 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -31,10 +31,10 @@ class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase column = Ltree.columns_hash["path"] assert_equal :ltree, column.type assert_equal "ltree", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = Ltree.type_for_attribute("path") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_write diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index 563f0bbfae..be3590e8dd 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -26,15 +26,15 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase assert_equal :money, column.type assert_equal "money", column.sql_type assert_equal 2, column.scale - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlMoney.type_for_attribute("wealth") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_default - assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults["depth"] - assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth + assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"] + assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth end def test_money_values @@ -65,7 +65,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase money = PostgresqlMoney.create(wealth: "987.65".dup) assert_equal 987.65, money.wealth - new_value = BigDecimal.new("123.45") + new_value = BigDecimal("123.45") money.wealth = new_value money.save! money.reload diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index f461544a85..736570451b 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -24,30 +24,30 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlNetworkAddress.columns_hash["cidr_address"] assert_equal :cidr, column.type assert_equal "cidr", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("cidr_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_inet_column column = PostgresqlNetworkAddress.columns_hash["inet_address"] assert_equal :inet, column.type assert_equal "inet", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("inet_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_macaddr_column column = PostgresqlNetworkAddress.columns_hash["mac_address"] assert_equal :macaddr, column.type assert_equal "macaddr", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = PostgresqlNetworkAddress.type_for_attribute("mac_address") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_network_types diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index a75fdef698..261c24634e 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -134,10 +134,10 @@ _SQL end def test_numrange_values - assert_equal BigDecimal.new("0.1")..BigDecimal.new("0.2"), @first_range.num_range - assert_equal BigDecimal.new("0.1")...BigDecimal.new("0.2"), @second_range.num_range - assert_equal BigDecimal.new("0.1")...BigDecimal.new("Infinity"), @third_range.num_range - assert_equal BigDecimal.new("-Infinity")...BigDecimal.new("Infinity"), @fourth_range.num_range + assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range + assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range + assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range + assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range assert_nil @empty_range.num_range end @@ -285,14 +285,14 @@ _SQL def test_create_numrange assert_equal_round_trip(@new_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("1")) + BigDecimal("0.5")...BigDecimal("1")) end def test_update_numrange assert_equal_round_trip(@first_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("1")) + BigDecimal("0.5")...BigDecimal("1")) assert_nil_round_trip(@first_range, :num_range, - BigDecimal.new("0.5")...BigDecimal.new("0.5")) + BigDecimal("0.5")...BigDecimal("0.5")) end def test_create_daterange @@ -358,6 +358,18 @@ _SQL end end + def test_infinity_values + PostgresqlRange.create!(int4_range: 1..Float::INFINITY, + int8_range: -Float::INFINITY..0, + float_range: -Float::INFINITY..Float::INFINITY) + + record = PostgresqlRange.first + + assert_equal(1...Float::INFINITY, record.int4_range) + assert_equal(-Float::INFINITY...1, record.int8_range) + assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range) + end + private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 1126908761..7ad03c194f 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -532,6 +532,34 @@ class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase end end +class SchemaIndexNullsOrderTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + setup do + @connection = ActiveRecord::Base.connection + @connection.create_table "trains" do |t| + t.string :name + t.text :description + end + end + + teardown do + @connection.drop_table "trains", if_exists: true + end + + def test_nulls_order_is_dumped + @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name NULLS FIRST, description)" + output = dump_table_schema "trains" + assert_match(/order: \{ name: "NULLS FIRST" \}/, output) + end + + def test_non_default_order_with_nulls_is_dumped + @connection.execute "CREATE INDEX trains_name_and_desc ON trains USING btree(name DESC NULLS LAST, description)" + output = dump_table_schema "trains" + assert_match(/order: \{ name: "DESC NULLS LAST" \}/, output) + end +end + class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection @@ -566,7 +594,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa end def test_decimal_defaults_in_new_schema_when_overriding_domain - assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed" + assert_equal BigDecimal("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed" end def test_bpchar_defaults_in_new_schema_when_overriding_domain diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb index 6a99323be5..83ea86be6d 100644 --- a/activerecord/test/cases/adapters/postgresql/serial_test.rb +++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb @@ -24,14 +24,14 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlSerial.columns_hash["seq"] assert_equal :integer, column.type assert_equal "integer", column.sql_type - assert column.serial? + assert_predicate column, :serial? end def test_not_serial_column column = PostgresqlSerial.columns_hash["serials_id"] assert_equal :integer, column.type assert_equal "integer", column.sql_type - assert_not column.serial? + assert_not_predicate column, :serial? end def test_schema_dump_with_shorthand @@ -66,14 +66,14 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase column = PostgresqlBigSerial.columns_hash["seq"] assert_equal :integer, column.type assert_equal "bigint", column.sql_type - assert column.serial? + assert_predicate column, :serial? end def test_not_bigserial_column column = PostgresqlBigSerial.columns_hash["serials_id"] assert_equal :integer, column.type assert_equal "bigint", column.sql_type - assert_not column.serial? + assert_not_predicate column, :serial? end def test_schema_dump_with_shorthand @@ -111,7 +111,7 @@ module SequenceNameDetectionTestCases columns = @connection.columns(:foo) columns.each do |column| assert_equal :integer, column.type - assert column.serial? + assert_predicate column, :serial? end end @@ -142,7 +142,7 @@ module SequenceNameDetectionTestCases columns = @connection.columns(@table_name) columns.each do |column| assert_equal :integer, column.type - assert column.serial? + assert_predicate column, :serial? end end diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index c24dfeb345..9821b103df 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -14,6 +14,7 @@ module ActiveRecord setup do @abort, Thread.abort_on_exception = Thread.abort_on_exception, false + Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception) @connection = ActiveRecord::Base.connection @@ -31,6 +32,7 @@ module ActiveRecord @connection.drop_table "samples", if_exists: true Thread.abort_on_exception = @abort + Thread.report_on_exception = @original_report_on_exception if Thread.respond_to?(:report_on_exception) end test "raises SerializationFailure when a serialization failure occurs" do diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index c24e0cb330..71d07e2f4c 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -82,7 +82,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase UUIDType.reset_column_information column = UUIDType.columns_hash["thingy"] - assert column.array? + assert_predicate column, :array? assert_equal "{}", column.default schema = dump_table_schema "uuid_data_type" @@ -93,10 +93,10 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type assert_equal "uuid", column.sql_type - assert_not column.array? + assert_not_predicate column, :array? type = UUIDType.type_for_attribute("guid") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_treat_blank_uuid_as_nil @@ -178,7 +178,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase duplicate = klass.new(guid: record.guid) assert record.guid.present? # Ensure we actually are testing a UUID - assert_not duplicate.valid? + assert_not_predicate duplicate, :valid? end end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index de422fad23..6fdb353368 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -38,7 +38,7 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase end def test_type_cast_bigdecimal - bd = BigDecimal.new "10.0" + bd = BigDecimal "10.0" assert_equal bd.to_f, @conn.type_cast(bd) end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 1f057fe5c6..7e0ce3a28e 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -360,16 +360,126 @@ module ActiveRecord end end + class Barcode < ActiveRecord::Base + self.primary_key = "code" + end + + def test_copy_table_with_existing_records_have_custom_primary_key + connection = Barcode.connection + connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t| + t.text :other_attr + end + code = "214fe0c2-dd47-46df-b53b-66090b3c1d40" + Barcode.create!(code: code, other_attr: "xxx") + + connection.remove_column("barcodes", "other_attr") + + assert_equal code, Barcode.first.id + ensure + Barcode.reset_column_information + end + + def test_copy_table_with_composite_primary_keys + connection = Barcode.connection + connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t| + t.string :region + t.string :code + t.text :other_attr + end + region = "US" + code = "214fe0c2-dd47-46df-b53b-66090b3c1d40" + Barcode.create!(region: region, code: code, other_attr: "xxx") + + connection.remove_column("barcodes", "other_attr") + + assert_equal ["region", "code"], connection.primary_keys("barcodes") + + barcode = Barcode.first + assert_equal region, barcode.region + assert_equal code, barcode.code + ensure + Barcode.reset_column_information + end + + def test_custom_primary_key_in_create_table + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.primary_key :id, :string + end + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_custom_primary_key_in_change_table + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.integer :dummy + end + connection.change_table :barcodes do |t| + t.primary_key :id, :string + end + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_add_column_with_custom_primary_key + connection = Barcode.connection + connection.create_table :barcodes, id: false, force: true do |t| + t.integer :dummy + end + connection.add_column :barcodes, :id, :string, primary_key: true + + assert_equal "id", connection.primary_key("barcodes") + + custom_pk = Barcode.columns_hash["id"] + + assert_equal :string, custom_pk.type + assert_not custom_pk.null + ensure + Barcode.reset_column_information + end + + def test_remove_column_preserves_partial_indexes + connection = Barcode.connection + connection.create_table :barcodes, force: true do |t| + t.string :code + t.string :region + t.boolean :bool_attr + + t.index :code, unique: true, where: :bool_attr, name: "partial" + end + connection.remove_column :barcodes, :region + + index = connection.indexes("barcodes").find { |idx| idx.name == "partial" } + assert_equal "bool_attr", index.where + ensure + Barcode.reset_column_information + end + def test_supports_extensions assert_not @conn.supports_extensions?, "does not support extensions" end def test_respond_to_enable_extension - assert @conn.respond_to?(:enable_extension) + assert_respond_to @conn, :enable_extension end def test_respond_to_disable_extension - assert @conn.respond_to?(:disable_extension) + assert_respond_to @conn, :disable_extension end def test_statement_closed @@ -390,6 +500,10 @@ module ActiveRecord end end + def test_deprecate_valid_alter_table_type + assert_deprecated { @conn.valid_alter_table_type?(:string) } + end + private def assert_logged(logs) diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 7f654ec6f6..fbdf2ada4b 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -27,7 +27,7 @@ class AggregationsTest < ActiveRecord::TestCase def test_immutable_value_objects customers(:david).balance = Money.new(100) - assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } } + assert_raise(frozen_error_class) { customers(:david).balance.instance_eval { @amount = 20 } } end def test_inferred_mapping diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 83974f327e..140d7cbcae 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -48,7 +48,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } - assert_equal 7, ActiveRecord::Migrator::current_version + assert_equal 7, @connection.migration_context.current_version end def test_schema_define_w_table_name_prefix @@ -64,7 +64,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase t.column :flavor, :string end end - assert_equal 7, ActiveRecord::Migrator::current_version + assert_equal 7, @connection.migration_context.current_version ensure ActiveRecord::Base.table_name_prefix = old_table_name_prefix ActiveRecord::SchemaMigration.table_name = table_name diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 0f7a249bf3..9e6d94191b 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -79,7 +79,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end account = model.new - assert account.valid? + assert_predicate account, :valid? ensure ActiveRecord::Base.belongs_to_required_by_default = original_value end @@ -95,7 +95,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end account = model.new - assert_not account.valid? + assert_not_predicate account, :valid? assert_equal [{ error: :blank }], account.errors.details[:company] ensure ActiveRecord::Base.belongs_to_required_by_default = original_value @@ -112,7 +112,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end account = model.new - assert_not account.valid? + assert_not_predicate account, :valid? assert_equal [{ error: :blank }], account.errors.details[:company] ensure ActiveRecord::Base.belongs_to_required_by_default = original_value @@ -246,14 +246,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first - assert citibank_result.association(:firm_with_primary_key).loaded? + assert_predicate citibank_result.association(:firm_with_primary_key), :loaded? end def test_eager_loading_with_primary_key_as_symbol Firm.create("name" => "Apple") Client.create("name" => "Citibank", :firm_name => "Apple") citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first - assert citibank_result.association(:firm_with_primary_key_symbols).loaded? + assert_predicate citibank_result.association(:firm_with_primary_key_symbols), :loaded? end def test_creating_the_belonging_object @@ -320,7 +320,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase client = Client.create!(name: "Jimmy") account = client.create_account!(credit_limit: 10) assert_equal account, client.account - assert account.persisted? + assert_predicate account, :persisted? client.save client.reload assert_equal account, client.account @@ -330,7 +330,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase client = Client.create!(name: "Jimmy") assert_raise(ActiveRecord::RecordInvalid) { client.create_account! } assert_not_nil client.account - assert client.account.new_record? + assert_predicate client.account, :new_record? end def test_reloading_the_belonging_object @@ -627,10 +627,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm = firm - assert !final_cut.persisted? + assert_not_predicate final_cut, :persisted? assert final_cut.save - assert final_cut.persisted? - assert firm.persisted? + assert_predicate final_cut, :persisted? + assert_predicate firm, :persisted? assert_equal firm, final_cut.firm final_cut.association(:firm).reload assert_equal firm, final_cut.firm @@ -640,10 +640,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm_with_primary_key = firm - assert !final_cut.persisted? + assert_not_predicate final_cut, :persisted? assert final_cut.save - assert final_cut.persisted? - assert firm.persisted? + assert_predicate final_cut, :persisted? + assert_predicate firm, :persisted? assert_equal firm, final_cut.firm_with_primary_key final_cut.association(:firm_with_primary_key).reload assert_equal firm, final_cut.firm_with_primary_key @@ -790,7 +790,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_cant_save_readonly_association assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! } - assert companies(:first_client).readonly_firm.readonly? + assert_predicate companies(:first_client).readonly_firm, :readonly? end def test_polymorphic_assignment_foreign_key_type_string @@ -949,15 +949,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase firm_proxy = client.send(:association_instance_get, :firm) firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition) - assert !firm_proxy.stale_target? - assert !firm_with_condition_proxy.stale_target? + assert_not_predicate firm_proxy, :stale_target? + assert_not_predicate firm_with_condition_proxy, :stale_target? assert_equal companies(:first_firm), client.firm assert_equal companies(:first_firm), client.firm_with_condition client.client_of = companies(:another_firm).id - assert firm_proxy.stale_target? - assert firm_with_condition_proxy.stale_target? + assert_predicate firm_proxy, :stale_target? + assert_predicate firm_with_condition_proxy, :stale_target? assert_equal companies(:another_firm), client.firm assert_equal companies(:another_firm), client.firm_with_condition end @@ -968,12 +968,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable proxy = sponsor.send(:association_instance_get, :sponsorable) - assert !proxy.stale_target? + assert_not_predicate proxy, :stale_target? assert_equal members(:groucho), sponsor.sponsorable sponsor.sponsorable_id = members(:some_other_guy).id - assert proxy.stale_target? + assert_predicate proxy, :stale_target? assert_equal members(:some_other_guy), sponsor.sponsorable end @@ -983,12 +983,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable proxy = sponsor.send(:association_instance_get, :sponsorable) - assert !proxy.stale_target? + assert_not_predicate proxy, :stale_target? assert_equal members(:groucho), sponsor.sponsorable sponsor.sponsorable_type = "Firm" - assert proxy.stale_target? + assert_predicate proxy, :stale_target? assert_equal companies(:first_firm), sponsor.sponsorable end @@ -1122,7 +1122,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase comment.post_id = 9223372036854775808 # out of range in the bigint assert_nil comment.post - assert_not comment.valid? + assert_not_predicate comment, :valid? assert_equal [{ error: :blank }], comment.errors.details[:post] end @@ -1142,7 +1142,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase citibank.firm_id = apple.id.to_s - assert !citibank.association(:firm).stale_target? + assert_not_predicate citibank.association(:firm), :stale_target? end def test_reflect_the_most_recent_change diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index e096cd4a0b..25d55dc4c9 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -15,7 +15,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase @david = authors(:david) @thinking = posts(:thinking) @authorless = posts(:authorless) - assert @david.post_log.empty? + assert_empty @david.post_log end def test_adding_macro_callbacks @@ -96,7 +96,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase def test_has_and_belongs_to_many_add_callback david = developers(:david) ar = projects(:active_record) - assert ar.developers_log.empty? + assert_empty ar.developers_log ar.developers_with_callbacks << david assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log ar.developers_with_callbacks << david @@ -122,12 +122,12 @@ class AssociationCallbacksTest < ActiveRecord::TestCase assert_equal alice, dev assert_not_nil new_dev assert new_dev, "record should not have been saved" - assert_not alice.new_record? + assert_not_predicate alice, :new_record? end def test_has_and_belongs_to_many_after_add_called_after_save ar = projects(:active_record) - assert ar.developers_log.empty? + assert_empty ar.developers_log alice = Developer.new(name: "alice") ar.developers_with_callbacks << alice assert_equal "after_adding#{alice.id}", ar.developers_log.last @@ -143,7 +143,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase david = developers(:david) jamis = developers(:jamis) activerecord = projects(:active_record) - assert activerecord.developers_log.empty? + assert_empty activerecord.developers_log activerecord.developers_with_callbacks.delete(david) assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log @@ -154,7 +154,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear activerecord = projects(:active_record) - assert activerecord.developers_log.empty? + assert_empty activerecord.developers_log if activerecord.developers_with_callbacks.size == 0 activerecord.developers << developers(:david) activerecord.developers << developers(:jamis) @@ -163,7 +163,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end activerecord.developers_with_callbacks.flat_map { |d| ["before_removing#{d.id}", "after_removing#{d.id}"] }.sort assert activerecord.developers_with_callbacks.clear - assert_predicate activerecord.developers_log, :empty? + assert_empty activerecord.developers_log end def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent @@ -183,7 +183,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase @david.unchangeable_posts << @authorless rescue Exception end - assert @david.post_log.empty? + assert_empty @david.post_log assert_not_includes @david.unchangeable_posts, @authorless @david.reload assert_not_includes @david.unchangeable_posts, @authorless diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 829e12fbc8..e717621928 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -136,7 +136,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first + author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 9a042c74db..cb07ca5cd3 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -284,7 +284,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_from_an_association_that_has_a_hash_of_conditions - assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty? + assert_not_empty Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts end def test_loading_with_no_associations @@ -787,7 +787,7 @@ class EagerAssociationTest < ActiveRecord::TestCase Tagging.create!(taggable_type: "Post", taggable_id: post2.id, tag: tag) tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id) - assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title)) + assert_equal tag_with_includes.ordered_taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title) end def test_eager_has_many_through_multiple_with_order @@ -1073,7 +1073,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_load_with_sti_sharing_association - assert_queries(2) do #should not do 1 query per subclass + assert_queries(2) do # should not do 1 query per subclass Comment.includes(:post).to_a end end @@ -1444,51 +1444,51 @@ class EagerAssociationTest < ActiveRecord::TestCase test "preloading readonly association" do # has-one firm = Firm.where(id: "1").preload(:readonly_account).first! - assert firm.readonly_account.readonly? + assert_predicate firm.readonly_account, :readonly? # has_and_belongs_to_many project = Project.where(id: "2").preload(:readonly_developers).first! - assert project.readonly_developers.first.readonly? + assert_predicate project.readonly_developers.first, :readonly? # has-many :through david = Author.where(id: "1").preload(:readonly_comments).first! - assert david.readonly_comments.first.readonly? + assert_predicate david.readonly_comments.first, :readonly? end test "eager-loading non-readonly association" do # has_one firm = Firm.where(id: "1").eager_load(:account).first! - assert_not firm.account.readonly? + assert_not_predicate firm.account, :readonly? # has_and_belongs_to_many project = Project.where(id: "2").eager_load(:developers).first! - assert_not project.developers.first.readonly? + assert_not_predicate project.developers.first, :readonly? # has_many :through david = Author.where(id: "1").eager_load(:comments).first! - assert_not david.comments.first.readonly? + assert_not_predicate david.comments.first, :readonly? # belongs_to post = Post.where(id: "1").eager_load(:author).first! - assert_not post.author.readonly? + assert_not_predicate post.author, :readonly? end test "eager-loading readonly association" do # has-one firm = Firm.where(id: "1").eager_load(:readonly_account).first! - assert firm.readonly_account.readonly? + assert_predicate firm.readonly_account, :readonly? # has_and_belongs_to_many project = Project.where(id: "2").eager_load(:readonly_developers).first! - assert project.readonly_developers.first.readonly? + assert_predicate project.readonly_developers.first, :readonly? # has-many :through david = Author.where(id: "1").eager_load(:readonly_comments).first! - assert david.readonly_comments.first.readonly? + assert_predicate david.readonly_comments.first, :readonly? # belongs_to post = Post.where(id: "1").eager_load(:readonly_author).first! - assert post.readonly_author.readonly? + assert_predicate post.readonly_author, :readonly? end test "preloading a polymorphic association with references to the associated table" do 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 c817d7267b..d9e05cf7e2 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 @@ -180,11 +180,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_has_and_belongs_to_many david = Developer.find(1) - assert !david.projects.empty? + assert_not_empty david.projects assert_equal 2, david.projects.size active_record = Project.find(1) - assert !active_record.developers.empty? + assert_not_empty active_record.developers assert_equal 3, active_record.developers.size assert_includes active_record.developers, david end @@ -262,10 +262,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase no_of_projects = Project.count aredridel = Developer.new("name" => "Aredridel") aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")]) - assert !aredridel.persisted? - assert !p.persisted? + assert_not_predicate aredridel, :persisted? + assert_not_predicate p, :persisted? assert aredridel.save - assert aredridel.persisted? + assert_predicate aredridel, :persisted? assert_equal no_of_devels + 1, Developer.count assert_equal no_of_projects + 1, Project.count assert_equal 2, aredridel.projects.size @@ -311,14 +311,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_build devel = Developer.find(1) proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") } - assert !devel.projects.loaded? + assert_not_predicate devel.projects, :loaded? assert_equal devel.projects.last, proj - assert devel.projects.loaded? + assert_predicate devel.projects, :loaded? - assert !proj.persisted? + assert_not_predicate proj, :persisted? devel.save - assert proj.persisted? + assert_predicate proj, :persisted? assert_equal devel.projects.last, proj assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end @@ -326,14 +326,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_new_aliased_to_build devel = Developer.find(1) proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") } - assert !devel.projects.loaded? + assert_not_predicate devel.projects, :loaded? assert_equal devel.projects.last, proj - assert devel.projects.loaded? + assert_predicate devel.projects, :loaded? - assert !proj.persisted? + assert_not_predicate proj, :persisted? devel.save - assert proj.persisted? + assert_predicate proj, :persisted? assert_equal devel.projects.last, proj assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end @@ -343,10 +343,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase devel.projects.build(name: "Make bed") proj2 = devel.projects.build(name: "Lie in it") assert_equal devel.projects.last, proj2 - assert !proj2.persisted? + assert_not_predicate proj2, :persisted? devel.save - assert devel.persisted? - assert proj2.persisted? + assert_predicate devel, :persisted? + assert_predicate proj2, :persisted? assert_equal devel.projects.last, proj2 assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated end @@ -354,12 +354,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_create devel = Developer.find(1) proj = devel.projects.create("name" => "Projekt") - assert !devel.projects.loaded? + assert_not_predicate devel.projects, :loaded? assert_equal devel.projects.last, proj - assert !devel.projects.loaded? + assert_not_predicate devel.projects, :loaded? - assert proj.persisted? + assert_predicate proj, :persisted? assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end @@ -367,14 +367,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase # in Oracle '' is saved as null therefore need to save ' ' in not null column post = categories(:general).post_with_conditions.build(body: " ") - assert post.save - assert_equal "Yet Another Testing Title", post.title + assert post.save + assert_equal "Yet Another Testing Title", post.title # in Oracle '' is saved as null therefore need to save ' ' in not null column another_post = categories(:general).post_with_conditions.create(body: " ") - assert another_post.persisted? - assert_equal "Yet Another Testing Title", another_post.title + assert_predicate another_post, :persisted? + assert_equal "Yet Another Testing Title", another_post.title end def test_distinct_after_the_fact @@ -441,10 +441,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_removing_associations_on_destroy david = DeveloperWithBeforeDestroyRaise.find(1) - assert !david.projects.empty? + assert_not_empty david.projects david.destroy - assert david.projects.empty? - assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty? + assert_empty david.projects + assert_empty DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1") end def test_destroying @@ -459,7 +459,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id} AND project_id = #{project.id}") - assert join_records.empty? + assert_empty join_records assert_equal 1, david.reload.projects.size assert_equal 1, david.projects.reload.size @@ -475,7 +475,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}") - assert join_records.empty? + assert_empty join_records assert_equal 0, david.reload.projects.size assert_equal 0, david.projects.reload.size @@ -484,23 +484,23 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_destroy_all david = Developer.find(1) david.projects.reload - assert !david.projects.empty? + assert_not_empty david.projects assert_no_difference "Project.count" do david.projects.destroy_all end join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}") - assert join_records.empty? + assert_empty join_records - assert david.projects.empty? - assert david.projects.reload.empty? + assert_empty david.projects + assert_empty david.projects.reload end def test_destroy_associations_destroys_multiple_associations george = parrots(:george) - assert !george.pirates.empty? - assert !george.treasures.empty? + assert_not_empty george.pirates + assert_not_empty george.treasures assert_no_difference "Pirate.count" do assert_no_difference "Treasure.count" do @@ -509,12 +509,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}") - assert join_records.empty? - assert george.pirates.reload.empty? + assert_empty join_records + assert_empty george.pirates.reload join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}") - assert join_records.empty? - assert george.treasures.reload.empty? + assert_empty join_records + assert_empty george.treasures.reload end def test_associations_with_conditions @@ -547,7 +547,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer = project.developers.first assert_no_queries(ignore_none: false) do - assert project.developers.loaded? + assert_predicate project.developers, :loaded? assert_includes project.developers, developer end end @@ -557,18 +557,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer = project.developers.first project.reload - assert ! project.developers.loaded? + assert_not_predicate project.developers, :loaded? assert_queries(1) do assert_includes project.developers, developer end - assert ! project.developers.loaded? + assert_not_predicate project.developers, :loaded? end def test_include_returns_false_for_non_matching_record_to_verify_scoping project = projects(:active_record) developer = Developer.create name: "Bryan", salary: 50_000 - assert ! project.developers.loaded? + assert_not_predicate project.developers, :loaded? assert ! project.developers.include?(developer) end @@ -763,9 +763,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_unloaded_associations_does_not_load_them developer = developers(:david) - assert !developer.projects.loaded? + assert_not_predicate developer.projects, :loaded? assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort - assert !developer.projects.loaded? + assert_not_predicate developer.projects, :loaded? end def test_assign_ids diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 4ca11af791..9ee42cef0f 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -57,7 +57,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase def test_custom_primary_key_on_new_record_should_fetch_with_query subscriber = Subscriber.new(nick: "webster132") - assert !subscriber.subscriptions.loaded? + assert_not_predicate subscriber.subscriptions, :loaded? assert_queries 1 do assert_equal 2, subscriber.subscriptions.size @@ -68,7 +68,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase def test_association_primary_key_on_new_record_should_fetch_with_query author = Author.new(name: "David") - assert !author.essays.loaded? + assert_not_predicate author.essays, :loaded? assert_queries 1 do assert_equal 1, author.essays.size @@ -103,7 +103,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase def test_blank_custom_primary_key_on_new_record_should_not_run_queries author = Author.new - assert !author.essays.loaded? + assert_not_predicate author.essays, :loaded? assert_queries 0 do assert_equal 0, author.essays.size @@ -201,7 +201,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase part.reload assert_nil part.ship - assert !part.updated_at_changed? + assert_not_predicate part, :updated_at_changed? end def test_create_from_association_should_respect_default_scope @@ -444,7 +444,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase new_clients << company.clients_of_firm.build(name: "Another Client III") end - assert_not company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? assert_queries(1) do assert_same new_clients[0], company.clients_of_firm.third assert_same new_clients[1], company.clients_of_firm.fourth @@ -464,7 +464,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase new_clients << company.clients_of_firm.build(name: "Another Client III") end - assert_not company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? assert_queries(1) do assert_same new_clients[0], company.clients_of_firm.third! assert_same new_clients[1], company.clients_of_firm.fourth! @@ -587,14 +587,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # taking from unloaded Relation bob = klass.find(authors(:bob).id) new_post = bob.posts.build - assert_not bob.posts.loaded? + assert_not_predicate bob.posts, :loaded? assert_equal [posts(:misc_by_bob)], bob.posts.take(1) assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3) # taking from loaded Relation bob.posts.load - assert bob.posts.loaded? + assert_predicate bob.posts, :loaded? assert_equal [posts(:misc_by_bob)], bob.posts.take(1) assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2) assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3) @@ -713,13 +713,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_find_each firm = companies(:first_firm) - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries(4) do firm.clients.find_each(batch_size: 1) { |c| assert_equal firm.id, c.firm_id } end - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_find_each_with_conditions @@ -732,13 +732,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_find_in_batches firm = companies(:first_firm) - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries(2) do firm.clients.find_in_batches(batch_size: 2) do |clients| @@ -746,7 +746,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_find_all_sanitized @@ -955,20 +955,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_new_aliased_to_build company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") } - assert !company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name - assert !new_client.persisted? + assert_not_predicate new_client, :persisted? assert_equal new_client, company.clients_of_firm.last end def test_build company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") } - assert !company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name - assert !new_client.persisted? + assert_not_predicate new_client, :persisted? assert_equal new_client, company.clients_of_firm.last end @@ -982,9 +982,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_collection_not_empty_after_building company = companies(:first_firm) - assert_predicate company.contracts, :empty? + assert_empty company.contracts company.contracts.build - assert_not_predicate company.contracts, :empty? + assert_not_empty company.contracts end def test_collection_size_twice_for_regressions @@ -1008,7 +1008,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_followed_by_save_does_not_load_target companies(:first_firm).clients_of_firm.build("name" => "Another Client") assert companies(:first_firm).save - assert !companies(:first_firm).clients_of_firm.loaded? + assert_not_predicate companies(:first_firm).clients_of_firm, :loaded? end def test_build_without_loading_association @@ -1028,10 +1028,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_build_via_block company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } } - assert !company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? assert_equal "Another Client", new_client.name - assert !new_client.persisted? + assert_not_predicate new_client, :persisted? assert_equal new_client, company.clients_of_firm.last end @@ -1069,7 +1069,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_predicate companies(:first_firm).clients_of_firm, :loaded? new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert new_client.persisted? + assert_predicate new_client, :persisted? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last end @@ -1082,7 +1082,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_followed_by_save_does_not_load_target companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert companies(:first_firm).save - assert !companies(:first_firm).clients_of_firm.loaded? + assert_not_predicate companies(:first_firm).clients_of_firm, :loaded? end def test_deleting @@ -1108,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase # option is not given on the association. ship = Ship.create(name: "Countless", treasures_count: 10) - assert_not Ship.reflect_on_association(:treasures).has_cached_counter? + assert_not_predicate Ship.reflect_on_association(:treasures), :has_cached_counter? # Count should come from sql count() of treasures rather than treasures_count attribute assert_equal ship.treasures.size, 0 @@ -1199,7 +1199,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_empty_with_counter_cache post = posts(:welcome) assert_queries(0) do - assert_not post.comments.empty? + assert_not_empty post.comments end end @@ -1441,13 +1441,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_creation_respects_hash_condition ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build - assert ms_client.save - assert_equal "Microsoft", ms_client.name + assert ms_client.save + assert_equal "Microsoft", ms_client.name another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create - assert another_ms_client.persisted? - assert_equal "Microsoft", another_ms_client.name + assert_predicate another_ms_client, :persisted? + assert_equal "Microsoft", another_ms_client.name end def test_clearing_without_initial_access @@ -1570,7 +1570,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = companies(:first_firm) assert_equal 3, firm.clients.size firm.destroy - assert Client.all.merge!(where: "firm_id=#{firm.id}").to_a.empty? + assert_empty Client.all.merge!(where: "firm_id=#{firm.id}").to_a end def test_dependence_for_associations_with_hash_condition @@ -1633,7 +1633,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = RestrictedWithExceptionFirm.create!(name: "restrict") firm.companies.create(name: "child") - assert !firm.companies.empty? + assert_not_empty firm.companies assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } assert RestrictedWithExceptionFirm.exists?(name: "restrict") assert firm.companies.exists?(name: "child") @@ -1643,11 +1643,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = RestrictedWithErrorFirm.create!(name: "restrict") firm.companies.create(name: "child") - assert !firm.companies.empty? + assert_not_empty firm.companies firm.destroy - assert !firm.errors.empty? + assert_not_empty firm.errors assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(name: "restrict") @@ -1660,11 +1660,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm = RestrictedWithErrorFirm.create!(name: "restrict") firm.companies.create(name: "child") - assert !firm.companies.empty? + assert_not_empty firm.companies firm.destroy - assert !firm.errors.empty? + assert_not_empty firm.errors assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(name: "restrict") @@ -1716,8 +1716,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase account = Account.new orig_accounts = firm.accounts.to_a - assert !account.valid? - assert !orig_accounts.empty? + assert_not_predicate account, :valid? + assert_not_empty orig_accounts error = assert_raise ActiveRecord::RecordNotSaved do firm.accounts = [account] end @@ -1775,9 +1775,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_unloaded_associations_does_not_load_them company = companies(:first_firm) - assert !company.clients.loaded? + assert_not_predicate company.clients, :loaded? assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids - assert !company.clients.loaded? + assert_not_predicate company.clients, :loaded? end def test_counter_cache_on_unloaded_association @@ -1859,7 +1859,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase client = firm.clients.first assert_no_queries do - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? assert_equal true, firm.clients.include?(client) end end @@ -1869,18 +1869,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase client = firm.clients.first firm.reload - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries(1) do assert_equal true, firm.clients.include?(client) end - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_include_returns_false_for_non_matching_record_to_verify_scoping firm = companies(:first_firm) client = Client.create!(name: "Not Associated") - assert ! firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_equal false, firm.clients.include?(client) end @@ -1889,13 +1889,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.first firm.clients.second firm.clients.last - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query firm = companies(:first_firm) firm.clients.load_target - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? assert_no_queries(ignore_none: false) do firm.clients.first @@ -1908,7 +1908,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_or_last_on_existing_record_with_build_should_load_association firm = companies(:first_firm) firm.clients.build(name: "Foo") - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries 1 do firm.clients.first @@ -1916,13 +1916,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.last end - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? end def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association firm = companies(:first_firm) firm.clients.create(name: "Foo") - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries 3 do firm.clients.first @@ -1930,7 +1930,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.last end - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_first_nth_or_last_on_new_record_should_not_run_queries @@ -1946,14 +1946,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_calling_first_or_last_with_integer_on_association_should_not_load_association firm = companies(:first_firm) firm.clients.create(name: "Foo") - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? assert_queries 2 do firm.clients.first(2) firm.clients.last(2) end - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_many_should_count_instead_of_loading_association @@ -1961,7 +1961,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_queries(1) do firm.clients.many? # use count query end - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_many_on_loaded_association_should_not_use_query @@ -1976,22 +1976,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.expects(:size).never firm.clients.many? { true } end - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? end def test_calling_many_should_return_false_if_none_or_one firm = companies(:another_firm) - assert !firm.clients_like_ms.many? + assert_not_predicate firm.clients_like_ms, :many? assert_equal 0, firm.clients_like_ms.size firm = companies(:first_firm) - assert !firm.limited_clients.many? + assert_not_predicate firm.limited_clients, :many? assert_equal 1, firm.limited_clients.size end def test_calling_many_should_return_true_if_more_than_one firm = companies(:first_firm) - assert firm.clients.many? + assert_predicate firm.clients, :many? assert_equal 3, firm.clients.size end @@ -2000,7 +2000,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_queries(1) do firm.clients.none? # use count query end - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_none_on_loaded_association_should_not_use_query @@ -2015,18 +2015,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.expects(:size).never firm.clients.none? { true } end - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? end def test_calling_none_should_return_true_if_none firm = companies(:another_firm) - assert firm.clients_like_ms.none? + assert_predicate firm.clients_like_ms, :none? assert_equal 0, firm.clients_like_ms.size end def test_calling_none_should_return_false_if_any firm = companies(:first_firm) - assert !firm.limited_clients.none? + assert_not_predicate firm.limited_clients, :none? assert_equal 1, firm.limited_clients.size end @@ -2035,7 +2035,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_queries(1) do firm.clients.one? # use count query end - assert !firm.clients.loaded? + assert_not_predicate firm.clients, :loaded? end def test_calling_one_on_loaded_association_should_not_use_query @@ -2050,24 +2050,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase firm.clients.expects(:size).never firm.clients.one? { true } end - assert firm.clients.loaded? + assert_predicate firm.clients, :loaded? end def test_calling_one_should_return_false_if_zero firm = companies(:another_firm) - assert ! firm.clients_like_ms.one? + assert_not_predicate firm.clients_like_ms, :one? assert_equal 0, firm.clients_like_ms.size end def test_calling_one_should_return_true_if_one firm = companies(:first_firm) - assert firm.limited_clients.one? + assert_predicate firm.limited_clients, :one? assert_equal 1, firm.limited_clients.size end def test_calling_one_should_return_false_if_more_than_one firm = companies(:first_firm) - assert ! firm.clients.one? + assert_not_predicate firm.clients, :one? assert_equal 3, firm.clients.size end @@ -2287,7 +2287,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase post = posts(:welcome) assert post.taggings_with_delete_all.count > 0 - assert !post.taggings_with_delete_all.loaded? + assert_not_predicate post.taggings_with_delete_all, :loaded? # 2 queries: one DELETE and another to update the counter cache assert_queries(2) do @@ -2309,7 +2309,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "collection proxy respects default scope" do author = authors(:mary) - assert !author.first_posts.exists? + assert_not_predicate author.first_posts, :exists? end test "association with extend option" do @@ -2350,7 +2350,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase test "does not duplicate associations when used with natural primary keys" do speedometer = Speedometer.create!(id: "4") - speedometer.minivans.create!(minivan_id: "a-van-red" , name: "a van", color: "red") + speedometer.minivans.create!(minivan_id: "a-van-red", name: "a van", color: "red") assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}" assert_equal 1, speedometer.reload.minivans.to_a.size @@ -2428,7 +2428,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase pirate = FamousPirate.new pirate.famous_ships << ship = FamousShip.new - assert pirate.valid? + assert_predicate pirate, :valid? assert_not pirate.valid?(:conference) assert_equal "can't be blank", ship.errors[:name].first end @@ -2509,6 +2509,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_same car, new_bulb.car end + test "reattach to new objects replaces inverse association and foreign key" do + bulb = Bulb.create!(car: Car.create!) + assert bulb.car_id + car = Car.new + car.bulbs << bulb + assert_equal car, bulb.car + assert_nil bulb.car_id + end + test "in memory replacement maintains order" do first_bulb = Bulb.create! second_bulb = Bulb.create! @@ -2520,6 +2529,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [first_bulb, second_bulb], car.bulbs end + test "association size calculation works with default scoped selects when not previously fetched" do + firm = Firm.create!(name: "Firm") + 5.times { firm.developers_with_select << Developer.create!(name: "Developer") } + + same_firm = Firm.find(firm.id) + assert_equal 5, same_firm.developers_with_select.size + end + test "prevent double insertion of new object when the parent association loaded in the after save callback" do reset_callbacks(:save, Bulb) do Bulb.after_save { |record| record.car.bulbs.load } 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 046020e310..25118b26f5 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -353,10 +353,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(1) do - assert posts(:welcome).people.empty? + assert_empty posts(:welcome).people end - assert posts(:welcome).reload.people.reload.empty? + assert_empty posts(:welcome).reload.people.reload end def test_destroy_association @@ -366,8 +366,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - assert posts(:welcome).reload.people.empty? - assert posts(:welcome).people.reload.empty? + assert_empty posts(:welcome).reload.people + assert_empty posts(:welcome).people.reload end def test_destroy_all @@ -377,8 +377,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - assert posts(:welcome).reload.people.empty? - assert posts(:welcome).people.reload.empty? + assert_empty posts(:welcome).reload.people + assert_empty posts(:welcome).people.reload end def test_should_raise_exception_for_destroying_mismatching_records @@ -538,6 +538,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_update_counter_caches_on_destroy_with_indestructible_through_record + post = posts(:welcome) + tag = post.indestructible_tags.create!(name: "doomed") + post.update_columns(indestructible_tags_count: post.indestructible_tags.count) + + assert_no_difference "post.reload.indestructible_tags_count" do + posts(:welcome).indestructible_tags.destroy(tag) + end + end + def test_replace_association assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload } @@ -675,10 +685,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end assert_queries(0) do - assert posts(:welcome).people.empty? + assert_empty posts(:welcome).people end - assert posts(:welcome).reload.people.reload.empty? + assert_empty posts(:welcome).reload.people.reload end def test_association_callback_ordering @@ -760,9 +770,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_get_ids_for_unloaded_associations_does_not_load_them person = people(:michael) - assert !person.posts.loaded? + assert_not_predicate person.posts, :loaded? assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort - assert !person.posts.loaded? + assert_not_predicate person.posts, :loaded? end def test_association_proxy_transaction_method_starts_transaction_in_association_class @@ -852,7 +862,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase category = author.named_categories.create(name: "Primary") author.named_categories.delete(category) assert !Categorization.exists?(author_id: author.id, named_category_name: category.name) - assert author.named_categories.reload.empty? + assert_empty author.named_categories.reload end def test_collection_singular_ids_getter_with_string_primary_keys @@ -869,6 +879,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [dev], company.developers end + def test_collection_singular_ids_setter_with_required_type_cast + company = companies(:rails_core) + dev = Developer.first + + company.developer_ids = [dev.id.to_s] + assert_equal [dev], company.developers + end + def test_collection_singular_ids_setter_with_string_primary_keys assert_nothing_raised do book = books(:awdr) @@ -926,8 +944,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_through_association_readonly_should_be_false - assert !people(:michael).posts.first.readonly? - assert !people(:michael).posts.to_a.first.readonly? + assert_not_predicate people(:michael).posts.first, :readonly? + assert_not_predicate people(:michael).posts.to_a.first, :readonly? end def test_can_update_through_association @@ -1016,12 +1034,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post.author_categorizations proxy = post.send(:association_instance_get, :author_categorizations) - assert !proxy.stale_target? + assert_not_predicate proxy, :stale_target? assert_equal authors(:mary).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id) post.author_id = authors(:david).id - assert proxy.stale_target? + assert_predicate proxy, :stale_target? assert_equal authors(:david).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id) end @@ -1038,7 +1056,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_includes post.author_addresses, address post.author_addresses.delete(address) - assert post[:author_count].nil? + assert_predicate post[:author_count], :nil? end def test_primary_key_option_on_source @@ -1300,6 +1318,70 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_has_many_through_update_ids_with_conditions + author = Author.create!(name: "Bill") + category = categories(:general) + + author.update( + special_categories_with_condition_ids: [category.id], + nonspecial_categories_with_condition_ids: [category.id] + ) + + assert_equal [category.id], author.special_categories_with_condition_ids + assert_equal [category.id], author.nonspecial_categories_with_condition_ids + + author.update(nonspecial_categories_with_condition_ids: []) + author.reload + + assert_equal [category.id], author.special_categories_with_condition_ids + assert_equal [], author.nonspecial_categories_with_condition_ids + end + + def test_single_has_many_through_association_with_unpersisted_parent_instance + post_with_single_has_many_through = Class.new(Post) do + def self.name; "PostWithSingleHasManyThrough"; end + has_many :subscriptions, through: :author + end + post = post_with_single_has_many_through.new + + post.author = authors(:mary) + book1 = Book.create!(name: "essays on single has many through associations 1") + post.author.books << book1 + subscription1 = Subscription.first + book1.subscriptions << subscription1 + assert_equal [subscription1], post.subscriptions.to_a + + post.author = authors(:bob) + book2 = Book.create!(name: "essays on single has many through associations 2") + post.author.books << book2 + subscription2 = Subscription.second + book2.subscriptions << subscription2 + assert_equal [subscription2], post.subscriptions.to_a + end + + def test_nested_has_many_through_association_with_unpersisted_parent_instance + post_with_nested_has_many_through = Class.new(Post) do + def self.name; "PostWithNestedHasManyThrough"; end + has_many :books, through: :author + has_many :subscriptions, through: :books + end + post = post_with_nested_has_many_through.new + + post.author = authors(:mary) + book1 = Book.create!(name: "essays on nested has many through associations 1") + post.author.books << book1 + subscription1 = Subscription.first + book1.subscriptions << subscription1 + assert_equal [subscription1], post.subscriptions.to_a + + post.author = authors(:bob) + book2 = Book.create!(name: "essays on nested has many through associations 2") + post.author.books << book2 + subscription2 = Subscription.second + book2.subscriptions << subscription2 + assert_equal [subscription2], post.subscriptions.to_a + end + private def make_model(name) Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index ec5d95080b..1a213ef7e4 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -114,8 +114,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase developer = Developer.create!(name: "Someone") ship = Ship.create!(name: "Planet Caravan", developer: developer) ship.destroy - assert !ship.persisted? - assert !developer.persisted? + assert_not_predicate ship, :persisted? + assert_not_predicate developer, :persisted? end def test_natural_assignment_to_nil_after_destroy @@ -186,7 +186,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } assert RestrictedWithExceptionFirm.exists?(name: "restrict") - assert firm.account.present? + assert_predicate firm.account, :present? end def test_restrict_with_error @@ -197,10 +197,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase firm.destroy - assert !firm.errors.empty? + assert_not_empty firm.errors assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(name: "restrict") - assert firm.account.present? + assert_predicate firm.account, :present? end def test_restrict_with_error_with_locale @@ -213,10 +213,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase firm.destroy - assert !firm.errors.empty? + assert_not_empty firm.errors assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first assert RestrictedWithErrorFirm.exists?(name: "restrict") - assert firm.account.present? + assert_predicate firm.account, :present? ensure I18n.backend.reload! end @@ -377,7 +377,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_assignment_before_child_saved firm = Firm.find(1) firm.account = a = Account.new("credit_limit" => 1000) - assert a.persisted? + assert_predicate a, :persisted? assert_equal a, firm.account assert_equal a, firm.account firm.association(:account).reload @@ -395,7 +395,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_cant_save_readonly_association assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! } - assert companies(:first_firm).readonly_account.readonly? + assert_predicate companies(:first_firm).readonly_account, :readonly? end def test_has_one_proxy_should_not_respond_to_private_methods @@ -433,7 +433,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_create_respects_hash_condition account = companies(:first_firm).create_account_limit_500_with_hash_conditions - assert account.persisted? + assert_predicate account, :persisted? assert_equal 500, account.credit_limit end @@ -450,7 +450,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase new_ship = pirate.create_ship assert_not_equal ships(:black_pearl), new_ship assert_equal new_ship, pirate.ship - assert new_ship.new_record? + assert_predicate new_ship, :new_record? assert_nil orig_ship.pirate_id assert !orig_ship.changed? # check it was saved end @@ -460,8 +460,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase orig_ship = pirate.dependent_ship new_ship = pirate.create_dependent_ship - assert new_ship.new_record? - assert orig_ship.destroyed? + assert_predicate new_ship, :new_record? + assert_predicate orig_ship, :destroyed? end def test_creation_failure_due_to_new_record_should_raise_error @@ -481,7 +481,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase pirate = pirates(:blackbeard) pirate.ship.name = nil - assert !pirate.ship.valid? + assert_not_predicate pirate.ship, :valid? error = assert_raise(ActiveRecord::RecordNotSaved) do pirate.ship = ships(:interceptor) end @@ -588,7 +588,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase ship.save! ship.name = "new name" - assert ship.changed? + assert_predicate ship, :changed? assert_queries(1) do # One query for updating name, not triggering query for updating pirate_id pirate.ship = ship diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index fe24c465b2..9964f084ac 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -42,6 +42,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_not_nil new_member.club end + def test_creating_association_builds_through_record + new_member = Member.create(name: "Chris") + new_club = new_member.association(:club).build + assert new_member.current_membership + assert_equal new_club, new_member.club + assert_predicate new_club, :new_record? + assert_predicate new_member.current_membership, :new_record? + assert new_member.save + assert_predicate new_club, :persisted? + assert_predicate new_member.current_membership, :persisted? + end + def test_creating_association_builds_through_record_for_new new_member = Member.new(name: "Jane") new_member.club = clubs(:moustache_club) @@ -100,7 +112,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_eager_loading - members = assert_queries(3) do #base table, through table, clubs table + members = assert_queries(3) do # base table, through table, clubs table Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size @@ -108,7 +120,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase end def test_has_one_through_eager_loading_through_polymorphic - members = assert_queries(3) do #base table, through table, clubs table + members = assert_queries(3) do # base table, through table, clubs table Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a end assert_equal 1, members.size @@ -139,7 +151,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eagerloading members = assert_queries(1) do - Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].club } @@ -147,7 +159,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -156,7 +168,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save! members = assert_queries(1) do - Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback + Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a # force fallback end assert_equal 1, members.size assert_not_nil assert_no_queries { members[0].sponsor_club } @@ -229,7 +241,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase MemberDetail.all.merge!(includes: :member_type).to_a end @new_detail = @member_details[0] - assert @new_detail.send(:association, :member_type).loaded? + assert_predicate @new_detail.send(:association, :member_type), :loaded? assert_no_queries { @new_detail.member_type } end @@ -317,12 +329,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase minivan.dashboard proxy = minivan.send(:association_instance_get, :dashboard) - assert !proxy.stale_target? + assert_not_predicate proxy, :stale_target? assert_equal dashboards(:cool_first), minivan.dashboard minivan.speedometer_id = speedometers(:second).id - assert proxy.stale_target? + assert_predicate proxy, :stale_target? assert_equal dashboards(:second), minivan.dashboard end @@ -334,7 +346,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase minivan.speedometer_id = speedometers(:second).id - assert proxy.stale_target? + assert_predicate proxy, :stale_target? assert_equal dashboards(:second), minivan.dashboard end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 7be875fec6..ca0620dc3b 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -115,19 +115,19 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase scope = Post.joins(:special_comments).where(id: posts(:sti_comments).id) # The join should match SpecialComment and its subclasses only - assert scope.where("comments.type" => "Comment").empty? - assert !scope.where("comments.type" => "SpecialComment").empty? - assert !scope.where("comments.type" => "SubSpecialComment").empty? + assert_empty scope.where("comments.type" => "Comment") + assert_not_empty scope.where("comments.type" => "SpecialComment") + assert_not_empty scope.where("comments.type" => "SubSpecialComment") end def test_find_with_conditions_on_reflection - assert !posts(:welcome).comments.empty? + assert_not_empty posts(:welcome).comments assert Post.joins(:nonexistent_comments).where(id: posts(:welcome).id).empty? # [sic!] end def test_find_with_conditions_on_through_reflection - assert !posts(:welcome).tags.empty? - assert Post.joins(:misc_tags).where(id: posts(:welcome).id).empty? + assert_not_empty posts(:welcome).tags + assert_empty Post.joins(:misc_tags).where(id: posts(:welcome).id) end test "the default scope of the target is applied when joining associations" do diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index c0d328ca8a..bad1bcdb67 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -129,7 +129,7 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase def test_polymorphic_has_one_should_find_inverse_automatically man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse) - assert man_reflection.has_inverse? + assert_predicate man_reflection, :has_inverse? end end @@ -150,22 +150,22 @@ class InverseAssociationTests < ActiveRecord::TestCase def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse has_one_with_inverse_ref = Man.reflect_on_association(:face) - assert has_one_with_inverse_ref.has_inverse? + assert_predicate has_one_with_inverse_ref, :has_inverse? has_many_with_inverse_ref = Man.reflect_on_association(:interests) - assert has_many_with_inverse_ref.has_inverse? + assert_predicate has_many_with_inverse_ref, :has_inverse? belongs_to_with_inverse_ref = Face.reflect_on_association(:man) - assert belongs_to_with_inverse_ref.has_inverse? + assert_predicate belongs_to_with_inverse_ref, :has_inverse? has_one_without_inverse_ref = Club.reflect_on_association(:sponsor) - assert !has_one_without_inverse_ref.has_inverse? + assert_not_predicate has_one_without_inverse_ref, :has_inverse? has_many_without_inverse_ref = Club.reflect_on_association(:memberships) - assert !has_many_without_inverse_ref.has_inverse? + assert_not_predicate has_many_without_inverse_ref, :has_inverse? belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club) - assert !belongs_to_without_inverse_ref.has_inverse? + assert_not_predicate belongs_to_without_inverse_ref, :has_inverse? end def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of @@ -464,7 +464,7 @@ class InverseHasManyTests < ActiveRecord::TestCase interest = Interest.create!(man: man) man.interests.find(interest.id) - assert_not man.interests.loaded? + assert_not_predicate man.interests, :loaded? end def test_raise_record_not_found_error_when_invalid_ids_are_passed @@ -504,16 +504,16 @@ class InverseHasManyTests < ActiveRecord::TestCase i.man.name = "Charles" assert_equal i.man.name, man.name - assert !man.persisted? + assert_not_predicate man, :persisted? end def test_inverse_instance_should_be_set_before_find_callbacks_are_run reset_callbacks(Interest, :find) do Interest.after_find { raise unless association(:man).loaded? && man.present? } - assert Man.first.interests.reload.any? - assert Man.includes(:interests).first.interests.any? - assert Man.joins(:interests).includes(:interests).first.interests.any? + assert_predicate Man.first.interests.reload, :any? + assert_predicate Man.includes(:interests).first.interests, :any? + assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any? end end @@ -521,9 +521,9 @@ class InverseHasManyTests < ActiveRecord::TestCase reset_callbacks(Interest, :initialize) do Interest.after_initialize { raise unless association(:man).loaded? && man.present? } - assert Man.first.interests.reload.any? - assert Man.includes(:interests).first.interests.any? - assert Man.joins(:interests).includes(:interests).first.interests.any? + assert_predicate Man.first.interests.reload, :any? + assert_predicate Man.includes(:interests).first.interests, :any? + assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any? end end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 87694b0788..f19a9f5f7a 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -44,11 +44,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_distinct_through_count author = authors(:mary) - assert !authors(:mary).unique_categorized_posts.loaded? + assert_not_predicate authors(:mary).unique_categorized_posts, :loaded? assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count } assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) } assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) } - assert !authors(:mary).unique_categorized_posts.loaded? + assert_not_predicate authors(:mary).unique_categorized_posts, :loaded? end def test_has_many_distinct_through_find @@ -454,8 +454,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_through_uses_conditions_specified_on_the_has_many_association author = Author.first - assert author.comments.present? - assert author.nonexistent_comments.blank? + assert_predicate author.comments, :present? + assert_predicate author.nonexistent_comments, :blank? end def test_has_many_through_uses_correct_attributes @@ -467,27 +467,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase new_tag = Tag.new(name: "new") saved_post.tags << new_tag - assert new_tag.persisted? #consistent with habtm! - assert saved_post.persisted? + assert new_tag.persisted? # consistent with habtm! + assert_predicate saved_post, :persisted? assert_includes saved_post.tags, new_tag - assert new_tag.persisted? + assert_predicate new_tag, :persisted? assert_includes saved_post.reload.tags.reload, new_tag new_post = Post.new(title: "Association replacement works!", body: "You best believe it.") saved_tag = tags(:general) new_post.tags << saved_tag - assert !new_post.persisted? - assert saved_tag.persisted? + assert_not_predicate new_post, :persisted? + assert_predicate saved_tag, :persisted? assert_includes new_post.tags, saved_tag new_post.save! - assert new_post.persisted? + assert_predicate new_post, :persisted? assert_includes new_post.reload.tags.reload, saved_tag - assert !posts(:thinking).tags.build.persisted? - assert !posts(:thinking).tags.new.persisted? + assert_not_predicate posts(:thinking).tags.build, :persisted? + assert_not_predicate posts(:thinking).tags.new, :persisted? end def test_create_associate_when_adding_to_has_many_through @@ -529,14 +529,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded author = authors(:david) assert_equal 10, author.comments.size - assert !author.comments.loaded? + assert_not_predicate author.comments, :loaded? end def test_has_many_through_collection_size_uses_counter_cache_if_it_exists c = categories(:general) c.categorizations_count = 100 assert_equal 100, c.categorizations.size - assert !c.categorizations.loaded? + assert_not_predicate c.categorizations, :loaded? end def test_adding_junk_to_has_many_through_should_raise_type_mismatch @@ -710,7 +710,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase category = david.categories.first assert_no_queries do - assert david.categories.loaded? + assert_predicate david.categories, :loaded? assert_includes david.categories, category end end @@ -720,18 +720,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase category = david.categories.first david.reload - assert ! david.categories.loaded? + assert_not_predicate david.categories, :loaded? assert_queries(1) do assert_includes david.categories, category end - assert ! david.categories.loaded? + assert_not_predicate david.categories, :loaded? end def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping david = authors(:david) category = Category.create!(name: "Not Associated") - assert ! david.categories.loaded? + assert_not_predicate david.categories, :loaded? assert ! david.categories.include?(category) end diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb index c95d0425cd..7b5c394177 100644 --- a/activerecord/test/cases/associations/left_outer_join_association_test.rb +++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb @@ -69,15 +69,15 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase scope = Post.left_outer_joins(:special_comments).where(id: posts(:sti_comments).id) # The join should match SpecialComment and its subclasses only - assert scope.where("comments.type" => "Comment").empty? - assert !scope.where("comments.type" => "SpecialComment").empty? - assert !scope.where("comments.type" => "SubSpecialComment").empty? + assert_empty scope.where("comments.type" => "Comment") + assert_not_empty scope.where("comments.type" => "SpecialComment") + assert_not_empty scope.where("comments.type" => "SubSpecialComment") end def test_does_not_override_select authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts) - assert authors.any? - assert authors.first.respond_to?(:addr_id) + assert_predicate authors, :any? + assert_respond_to authors.first, :addr_id end test "the default scope of the target is applied when joining associations" do diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 65d30d011b..03ed1c1d47 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -78,7 +78,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # This ensures that the polymorphism of taggings is being observed correctly authors = Author.joins(:tags).where("taggings.taggable_type" => "FakeModel") - assert authors.empty? + assert_empty authors end # has_many through @@ -177,7 +177,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase members = Member.joins(:organization_member_details). where("member_details.id" => 9) - assert members.empty? + assert_empty members end # has_many through @@ -209,7 +209,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase members = Member.joins(:organization_member_details_2). where("member_details.id" => 9) - assert members.empty? + assert_empty members end # has_many through @@ -425,9 +425,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # Check the polymorphism of taggings is being observed correctly (in both joins) authors = Author.joins(:similar_posts).where("taggings.taggable_type" => "FakeModel") - assert authors.empty? + assert_empty authors authors = Author.joins(:similar_posts).where("taggings_authors_join.taggable_type" => "FakeModel") - assert authors.empty? + assert_empty authors end def test_nested_has_many_through_with_scope_on_polymorphic_reflection @@ -456,9 +456,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase # Ensure STI is respected in the join scope = Post.joins(:special_comments_ratings).where(id: posts(:sti_comments).id) - assert scope.where("comments.type" => "Comment").empty? - assert !scope.where("comments.type" => "SpecialComment").empty? - assert !scope.where("comments.type" => "SubSpecialComment").empty? + assert_empty scope.where("comments.type" => "Comment") + assert_not_empty scope.where("comments.type" => "SpecialComment") + assert_not_empty scope.where("comments.type" => "SubSpecialComment") end def test_has_many_through_with_sti_on_nested_through_reflection @@ -466,8 +466,8 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal [taggings(:special_comment_rating)], taggings scope = Post.joins(:special_comments_ratings_taggings).where(id: posts(:sti_comments).id) - assert scope.where("comments.type" => "Comment").empty? - assert !scope.where("comments.type" => "SpecialComment").empty? + assert_empty scope.where("comments.type" => "Comment") + assert_not_empty scope.where("comments.type" => "SpecialComment") end def test_nested_has_many_through_writers_should_raise_error @@ -517,7 +517,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase end def test_nested_has_many_through_with_conditions_on_through_associations_preload - assert Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags).empty? + assert_empty Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags) authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) } blue = tags(:blue) @@ -574,9 +574,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase c = Categorization.new c.author = authors(:david) c.post_taggings.to_a - assert !c.post_taggings.empty? + assert_not_empty c.post_taggings c.save - assert !c.post_taggings.empty? + assert_not_empty c.post_taggings end def test_polymorphic_has_many_through_when_through_association_has_not_loaded diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index de04221ea6..fce2a7eed1 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -47,7 +47,7 @@ class AssociationsTest < ActiveRecord::TestCase ship = Ship.create!(name: "The good ship Dollypop") part = ship.parts.create!(name: "Mast") part.mark_for_destruction - assert ship.parts[0].marked_for_destruction? + assert_predicate ship.parts[0], :marked_for_destruction? end def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction @@ -119,7 +119,7 @@ class AssociationProxyTest < ActiveRecord::TestCase david = authors(:david) david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!")) - assert !david.posts.loaded? + assert_not_predicate david.posts, :loaded? assert_includes david.posts, post end @@ -127,7 +127,7 @@ class AssociationProxyTest < ActiveRecord::TestCase david = authors(:david) david.categories << categories(:technology) - assert !david.categories.loaded? + assert_not_predicate david.categories, :loaded? assert_includes david.categories, categories(:technology) end @@ -135,23 +135,23 @@ class AssociationProxyTest < ActiveRecord::TestCase david = authors(:david) david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!")) - assert !david.posts.loaded? + assert_not_predicate david.posts, :loaded? david.save - assert !david.posts.loaded? + assert_not_predicate david.posts, :loaded? assert_includes david.posts, post end def test_push_does_not_lose_additions_to_new_record josh = Author.new(name: "Josh") josh.posts << Post.new(title: "New on Edge", body: "More cool stuff!") - assert josh.posts.loaded? + assert_predicate josh.posts, :loaded? assert_equal 1, josh.posts.size end def test_append_behaves_like_push josh = Author.new(name: "Josh") josh.posts.append Post.new(title: "New on Edge", body: "More cool stuff!") - assert josh.posts.loaded? + assert_predicate josh.posts, :loaded? assert_equal 1, josh.posts.size end @@ -163,22 +163,22 @@ class AssociationProxyTest < ActiveRecord::TestCase def test_save_on_parent_does_not_load_target david = developers(:david) - assert !david.projects.loaded? + assert_not_predicate david.projects, :loaded? david.update_columns(created_at: Time.now) - assert !david.projects.loaded? + assert_not_predicate david.projects, :loaded? end def test_load_does_load_target david = developers(:david) - assert !david.projects.loaded? + assert_not_predicate david.projects, :loaded? david.projects.load - assert david.projects.loaded? + assert_predicate david.projects, :loaded? end def test_inspect_does_not_reload_a_not_yet_loaded_target andreas = Developer.new name: "Andreas", log: "new developer added" - assert !andreas.audit_logs.loaded? + assert_not_predicate andreas.audit_logs, :loaded? assert_match(/message: "new developer added"/, andreas.audit_logs.inspect) end @@ -248,14 +248,14 @@ class AssociationProxyTest < ActiveRecord::TestCase test "first! works on loaded associations" do david = authors(:david) assert_equal david.first_posts.first, david.first_posts.reload.first! - assert david.first_posts.loaded? + assert_predicate david.first_posts, :loaded? assert_no_queries { david.first_posts.first! } end def test_pluck_uses_loaded_target david = authors(:david) assert_equal david.first_posts.pluck(:title), david.first_posts.load.pluck(:title) - assert david.first_posts.loaded? + assert_predicate david.first_posts, :loaded? assert_no_queries { david.first_posts.pluck(:title) } end @@ -263,9 +263,9 @@ class AssociationProxyTest < ActiveRecord::TestCase david = authors(:david) david.posts.reload - assert david.posts.loaded? + assert_predicate david.posts, :loaded? david.posts.reset - assert !david.posts.loaded? + assert_not_predicate david.posts, :loaded? end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index c48f7d3518..dc6638d45d 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -99,8 +99,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase end test "boolean attributes" do - assert !Topic.find(1).approved? - assert Topic.find(2).approved? + assert_not_predicate Topic.find(1), :approved? + assert_predicate Topic.find(2), :approved? end test "set attributes" do @@ -142,16 +142,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_respond_to topic, :title= assert_respond_to topic, "author_name" assert_respond_to topic, "attribute_names" - assert !topic.respond_to?("nothingness") - assert !topic.respond_to?(:nothingness) + assert_not_respond_to topic, "nothingness" + assert_not_respond_to topic, :nothingness end test "respond_to? with a custom primary key" do keyboard = Keyboard.create assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id - assert keyboard.respond_to?("key_number") - assert keyboard.respond_to?("id") + assert_respond_to keyboard, "key_number" + assert_respond_to keyboard, "id" end test "id_before_type_cast with a custom primary key" do @@ -170,8 +170,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase end topic = klass.allocate - assert !topic.respond_to?("nothingness") - assert !topic.respond_to?(:nothingness) + assert_not_respond_to topic, "nothingness" + assert_not_respond_to topic, :nothingness assert_respond_to topic, "title" assert_respond_to topic, :title end @@ -457,30 +457,30 @@ class AttributeMethodsTest < ActiveRecord::TestCase SQL assert_equal "Firm", object.string_value - assert object.string_value? + assert_predicate object, :string_value? object.string_value = " " - assert !object.string_value? + assert_not_predicate object, :string_value? assert_equal 1, object.int_value.to_i - assert object.int_value? + assert_predicate object, :int_value? object.int_value = "0" - assert !object.int_value? + assert_not_predicate object, :int_value? end test "non-attribute read and write" do topic = Topic.new - assert !topic.respond_to?("mumbo") + assert_not_respond_to topic, "mumbo" assert_raise(NoMethodError) { topic.mumbo } assert_raise(NoMethodError) { topic.mumbo = 5 } end test "undeclared attribute method does not affect respond_to? and method_missing" do topic = @target.new(title: "Budget") - assert topic.respond_to?("title") + assert_respond_to topic, "title" assert_equal "Budget", topic.title - assert !topic.respond_to?("title_hello_world") + assert_not_respond_to topic, "title_hello_world" assert_raise(NoMethodError) { topic.title_hello_world } end @@ -491,7 +491,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase @target.attribute_method_prefix prefix meth = "#{prefix}title" - assert topic.respond_to?(meth) + assert_respond_to topic, meth assert_equal ["title"], topic.send(meth) assert_equal ["title", "a"], topic.send(meth, "a") assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) @@ -505,7 +505,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(title: "Budget") meth = "title#{suffix}" - assert topic.respond_to?(meth) + assert_respond_to topic, meth assert_equal ["title"], topic.send(meth) assert_equal ["title", "a"], topic.send(meth, "a") assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) @@ -519,7 +519,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(title: "Budget") meth = "#{prefix}title#{suffix}" - assert topic.respond_to?(meth) + assert_respond_to topic, meth assert_equal ["title"], topic.send(meth) assert_equal ["title", "a"], topic.send(meth, "a") assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3) @@ -541,7 +541,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase else topic = Topic.all.merge!(select: "topics.*, 1=2 as is_test").first end - assert !topic.is_test? + assert_not_predicate topic, :is_test? end test "typecast attribute from select to true" do @@ -552,7 +552,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase else topic = Topic.all.merge!(select: "topics.*, 2=2 as is_test").first end - assert topic.is_test? + assert_predicate topic, :is_test? end test "raises ActiveRecord::DangerousAttributeError when defining an AR method in a model" do @@ -743,7 +743,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase expected_time = Time.utc(2000, 01, 01, 10) assert_equal expected_time, record.bonus_time - assert record.bonus_time.utc? + assert_predicate record.bonus_time, :utc? end end end @@ -767,7 +767,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase privatize("title") topic = @target.new(title: "The pros and cons of programming naked.") - assert !topic.respond_to?(:title) + assert_not_respond_to topic, :title exception = assert_raise(NoMethodError) { topic.title } assert_includes exception.message, "private method" assert_equal "I'm private", topic.send(:title) @@ -777,7 +777,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase privatize("title=(value)") topic = @target.new - assert !topic.respond_to?(:title=) + assert_not_respond_to topic, :title= exception = assert_raise(NoMethodError) { topic.title = "Pants" } assert_includes exception.message, "private method" topic.send(:title=, "Very large pants") @@ -787,7 +787,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase privatize("title?") topic = @target.new(title: "Isaac Newton's pants") - assert !topic.respond_to?(:title?) + assert_not_respond_to topic, :title? exception = assert_raise(NoMethodError) { topic.title? } assert_includes exception.message, "private method" assert topic.send(:title?) @@ -979,9 +979,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase test "came_from_user?" do model = @target.first - assert_not model.id_came_from_user? + assert_not_predicate model, :id_came_from_user? model.id = "omg" - assert model.id_came_from_user? + assert_predicate model, :id_came_from_user? end test "accessed_fields" do diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb index 2caf2a63d4..3bc56694be 100644 --- a/activerecord/test/cases/attributes_test.rb +++ b/activerecord/test/cases/attributes_test.rb @@ -59,7 +59,7 @@ module ActiveRecord test "nonexistent attribute" do data = OverloadedType.new(non_existent_decimal: 1) - assert_equal BigDecimal.new(1), data.non_existent_decimal + assert_equal BigDecimal(1), data.non_existent_decimal assert_raise ActiveRecord::UnknownAttributeError do UnoverloadedType.new(non_existent_decimal: 1) end @@ -211,7 +211,7 @@ module ActiveRecord end test "attributes not backed by database columns are not dirty when unchanged" do - refute OverloadedType.new.non_existent_decimal_changed? + assert_not_predicate OverloadedType.new, :non_existent_decimal_changed? end test "attributes not backed by database columns are always initialized" do @@ -245,13 +245,13 @@ module ActiveRecord model.foo << "asdf" assert_equal "lolasdf", model.foo - assert model.foo_changed? + assert_predicate model, :foo_changed? model.reload assert_equal "lol", model.foo model.foo = "lol" - refute model.changed? + assert_not_predicate model, :changed? end test "attributes not backed by database columns appear in inspect" do diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 4d8368fd8a..09684520d1 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -51,7 +51,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase u = person.create!(first_name: "cool") u.update_attributes!(first_name: "nah") # still valid because validation only applies on 'create' - assert reference.create!(person: u).persisted? + assert_predicate reference.create!(person: u), :persisted? end def test_should_not_add_the_same_callbacks_multiple_times_for_has_one @@ -74,7 +74,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase ship = ShipWithoutNestedAttributes.new ship.prisoners.build - assert_not ship.valid? + assert_not_predicate ship, :valid? assert_equal 1, ship.errors[:name].length end @@ -99,35 +99,35 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas def test_should_save_parent_but_not_invalid_child firm = Firm.new(name: "GlobalMegaCorp") - assert firm.valid? + assert_predicate firm, :valid? firm.build_account_using_primary_key - assert !firm.build_account_using_primary_key.valid? + assert_not_predicate firm.build_account_using_primary_key, :valid? assert firm.save - assert !firm.account_using_primary_key.persisted? + assert_not_predicate firm.account_using_primary_key, :persisted? end def test_save_fails_for_invalid_has_one firm = Firm.first - assert firm.valid? + assert_predicate firm, :valid? firm.build_account - assert !firm.account.valid? - assert !firm.valid? + assert_not_predicate firm.account, :valid? + assert_not_predicate firm, :valid? assert !firm.save assert_equal ["is invalid"], firm.errors["account"] end def test_save_succeeds_for_invalid_has_one_with_validate_false firm = Firm.first - assert firm.valid? + assert_predicate firm, :valid? firm.build_unvalidated_account - assert !firm.unvalidated_account.valid? - assert firm.valid? + assert_not_predicate firm.unvalidated_account, :valid? + assert_predicate firm, :valid? assert firm.save end @@ -136,10 +136,10 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas account = firm.build_account("credit_limit" => 1000) assert_equal account, firm.account - assert !account.persisted? + assert_not_predicate account, :persisted? assert firm.save assert_equal account, firm.account - assert account.persisted? + assert_predicate account, :persisted? end def test_build_before_either_saved @@ -147,16 +147,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas firm.account = account = Account.new("credit_limit" => 1000) assert_equal account, firm.account - assert !account.persisted? + assert_not_predicate account, :persisted? assert firm.save assert_equal account, firm.account - assert account.persisted? + assert_predicate account, :persisted? end def test_assignment_before_parent_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.find(1) - assert !firm.persisted? + assert_not_predicate firm, :persisted? assert_equal a, firm.account assert firm.save assert_equal a, firm.account @@ -167,12 +167,12 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas def test_assignment_before_either_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.new("credit_limit" => 1000) - assert !firm.persisted? - assert !a.persisted? + assert_not_predicate firm, :persisted? + assert_not_predicate a, :persisted? assert_equal a, firm.account assert firm.save - assert firm.persisted? - assert a.persisted? + assert_predicate firm, :persisted? + assert_predicate a, :persisted? assert_equal a, firm.account firm.association(:account).reload assert_equal a, firm.account @@ -221,13 +221,13 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test def test_should_save_parent_but_not_invalid_child client = Client.new(name: "Joe (the Plumber)") - assert client.valid? + assert_predicate client, :valid? client.build_firm - assert !client.firm.valid? + assert_not_predicate client.firm, :valid? assert client.save - assert !client.firm.persisted? + assert_not_predicate client.firm, :persisted? end def test_save_fails_for_invalid_belongs_to @@ -235,8 +235,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test assert log = AuditLog.create(developer_id: 0, message: " ") log.developer = Developer.new - assert !log.developer.valid? - assert !log.valid? + assert_not_predicate log.developer, :valid? + assert_not_predicate log, :valid? assert !log.save assert_equal ["is invalid"], log.errors["developer"] end @@ -246,8 +246,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test assert log = AuditLog.create(developer_id: 0, message: " ") log.unvalidated_developer = Developer.new - assert !log.unvalidated_developer.valid? - assert log.valid? + assert_not_predicate log.unvalidated_developer, :valid? + assert_predicate log, :valid? assert log.save end @@ -256,10 +256,10 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test apple = Firm.new("name" => "Apple") client.firm = apple assert_equal apple, client.firm - assert !apple.persisted? + assert_not_predicate apple, :persisted? assert client.save assert apple.save - assert apple.persisted? + assert_predicate apple, :persisted? assert_equal apple, client.firm client.association(:firm).reload assert_equal apple, client.firm @@ -269,11 +269,11 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test final_cut = Client.new("name" => "Final Cut") apple = Firm.new("name" => "Apple") final_cut.firm = apple - assert !final_cut.persisted? - assert !apple.persisted? + assert_not_predicate final_cut, :persisted? + assert_not_predicate apple, :persisted? assert final_cut.save - assert final_cut.persisted? - assert apple.persisted? + assert_predicate final_cut, :persisted? + assert_predicate apple, :persisted? assert_equal apple, final_cut.firm final_cut.association(:firm).reload assert_equal apple, final_cut.firm @@ -382,7 +382,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test auditlog.developer = invalid_developer auditlog.developer_id = valid_developer.id - assert auditlog.valid? + assert_predicate auditlog, :valid? end end @@ -395,8 +395,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib molecule.electrons = [valid_electron, invalid_electron] molecule.save - assert_not invalid_electron.valid? - assert valid_electron.valid? + assert_not_predicate invalid_electron, :valid? + assert_predicate valid_electron, :valid? assert_not molecule.persisted?, "Molecule should not be persisted when its electrons are invalid" end @@ -408,9 +408,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid] - assert_not tuning_peg_invalid.valid? - assert tuning_peg_valid.valid? - assert_not guitar.valid? + assert_not_predicate tuning_peg_invalid, :valid? + assert_predicate tuning_peg_valid, :valid? + assert_not_predicate guitar, :valid? assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"] assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"] end @@ -425,9 +425,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib molecule.electrons = [valid_electron, invalid_electron] - assert_not invalid_electron.valid? - assert valid_electron.valid? - assert_not molecule.valid? + assert_not_predicate invalid_electron, :valid? + assert_predicate valid_electron, :valid? + assert_not_predicate molecule, :valid? assert_equal ["can't be blank"], molecule.errors["electrons[1].name"] assert_not_equal ["can't be blank"], molecule.errors["electrons.name"] ensure @@ -441,9 +441,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib molecule.electrons = [valid_electron, invalid_electron] - assert_not invalid_electron.valid? - assert valid_electron.valid? - assert_not molecule.valid? + assert_not_predicate invalid_electron, :valid? + assert_predicate valid_electron, :valid? + assert_not_predicate molecule, :valid? assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"] end @@ -455,9 +455,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid] - assert_not tuning_peg_invalid.valid? - assert tuning_peg_valid.valid? - assert_not guitar.valid? + assert_not_predicate tuning_peg_invalid, :valid? + assert_predicate tuning_peg_valid, :valid? + assert_not_predicate guitar, :valid? assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"] assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"] end @@ -472,9 +472,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib molecule.electrons = [valid_electron, invalid_electron] - assert_not invalid_electron.valid? - assert valid_electron.valid? - assert_not molecule.valid? + assert_not_predicate invalid_electron, :valid? + assert_predicate valid_electron, :valid? + assert_not_predicate molecule, :valid? assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"] assert_equal [], molecule.errors.details[:"electrons.name"] ensure @@ -488,8 +488,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib molecule.electrons = [valid_electron] molecule.save - assert valid_electron.valid? - assert molecule.persisted? + assert_predicate valid_electron, :valid? + assert_predicate molecule, :persisted? assert_equal 1, molecule.electrons.count end end @@ -500,21 +500,21 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_invalid_adding firm = Firm.find(1) assert !(firm.clients_of_firm << c = Client.new) - assert !c.persisted? - assert !firm.valid? + assert_not_predicate c, :persisted? + assert_not_predicate firm, :valid? assert !firm.save - assert !c.persisted? + assert_not_predicate c, :persisted? end def test_invalid_adding_before_save new_firm = Firm.new("name" => "A New Firm, Inc") new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")]) - assert !c.persisted? - assert !c.valid? - assert !new_firm.valid? + assert_not_predicate c, :persisted? + assert_not_predicate c, :valid? + assert_not_predicate new_firm, :valid? assert !new_firm.save - assert !c.persisted? - assert !new_firm.persisted? + assert_not_predicate c, :persisted? + assert_not_predicate new_firm, :persisted? end def test_invalid_adding_with_validate_false @@ -522,10 +522,10 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa client = Client.new firm.unvalidated_clients_of_firm << client - assert firm.valid? - assert !client.valid? + assert_predicate firm, :valid? + assert_not_predicate client, :valid? assert firm.save - assert !client.persisted? + assert_not_predicate client, :persisted? end def test_valid_adding_with_validate_false @@ -534,24 +534,24 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa firm = Firm.first client = Client.new("name" => "Apple") - assert firm.valid? - assert client.valid? - assert !client.persisted? + assert_predicate firm, :valid? + assert_predicate client, :valid? + assert_not_predicate client, :persisted? firm.unvalidated_clients_of_firm << client assert firm.save - assert client.persisted? + assert_predicate client, :persisted? assert_equal no_of_clients + 1, Client.count end def test_invalid_build new_client = companies(:first_firm).clients_of_firm.build - assert !new_client.persisted? - assert !new_client.valid? + assert_not_predicate new_client, :persisted? + assert_not_predicate new_client, :valid? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert !companies(:first_firm).save - assert !new_client.persisted? + assert_not_predicate new_client, :persisted? assert_equal 2, companies(:first_firm).clients_of_firm.reload.size end @@ -570,8 +570,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert_equal no_of_firms, Firm.count # Firm was not saved to database. assert_equal no_of_clients, Client.count # Clients were not saved to database. assert new_firm.save - assert new_firm.persisted? - assert c.persisted? + assert_predicate new_firm, :persisted? + assert_predicate c, :persisted? assert_equal new_firm, c.firm assert_equal no_of_firms + 1, Firm.count # Firm was saved to database. assert_equal no_of_clients + 2, Client.count # Clients were saved to database. @@ -601,11 +601,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_before_save company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") } - assert !company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? company.name += "-changed" assert_queries(2) { assert company.save } - assert new_client.persisted? + assert_predicate new_client, :persisted? assert_equal 3, company.clients_of_firm.reload.size end @@ -621,11 +621,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_build_via_block_before_save company = companies(:first_firm) new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } } - assert !company.clients_of_firm.loaded? + assert_not_predicate company.clients_of_firm, :loaded? company.name += "-changed" assert_queries(2) { assert company.save } - assert new_client.persisted? + assert_predicate new_client, :persisted? assert_equal 3, company.clients_of_firm.reload.size end @@ -657,62 +657,62 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase new_account = Account.new("credit_limit" => 1000) new_firm = Firm.new("name" => "some firm") - assert !new_firm.persisted? + assert_not_predicate new_firm, :persisted? new_account.firm = new_firm new_account.save! - assert new_firm.persisted? + assert_predicate new_firm, :persisted? new_account = Account.new("credit_limit" => 1000) new_autosaved_firm = Firm.new("name" => "some firm") - assert !new_autosaved_firm.persisted? + assert_not_predicate new_autosaved_firm, :persisted? new_account.unautosaved_firm = new_autosaved_firm new_account.save! - assert !new_autosaved_firm.persisted? + assert_not_predicate new_autosaved_firm, :persisted? end def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert !account.persisted? + assert_not_predicate account, :persisted? firm.account = account firm.save! - assert account.persisted? + assert_predicate account, :persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) firm.unautosaved_account = account - assert !account.persisted? + assert_not_predicate account, :persisted? firm.unautosaved_account = account firm.save! - assert !account.persisted? + assert_not_predicate account, :persisted? end def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert !account.persisted? + assert_not_predicate account, :persisted? firm.accounts << account firm.save! - assert account.persisted? + assert_predicate account, :persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert !account.persisted? + assert_not_predicate account, :persisted? firm.unautosaved_accounts << account firm.save! - assert !account.persisted? + assert_not_predicate account, :persisted? end def test_autosave_new_record_with_after_create_callback @@ -745,18 +745,18 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.mark_for_destruction @pirate.ship.mark_for_destruction - assert !@pirate.reload.marked_for_destruction? - assert !@pirate.ship.reload.marked_for_destruction? + assert_not_predicate @pirate.reload, :marked_for_destruction? + assert_not_predicate @pirate.ship.reload, :marked_for_destruction? end # has_one def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction - assert !@pirate.ship.marked_for_destruction? + assert_not_predicate @pirate.ship, :marked_for_destruction? @pirate.ship.mark_for_destruction id = @pirate.ship.id - assert @pirate.ship.marked_for_destruction? + assert_predicate @pirate.ship, :marked_for_destruction? assert Ship.find_by_id(id) @pirate.save @@ -766,7 +766,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_should_skip_validation_on_a_child_association_if_marked_for_destruction @pirate.ship.name = "" - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? @pirate.ship.mark_for_destruction @pirate.ship.expects(:valid?).never @@ -812,12 +812,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase # belongs_to def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction - assert !@ship.pirate.marked_for_destruction? + assert_not_predicate @ship.pirate, :marked_for_destruction? @ship.pirate.mark_for_destruction id = @ship.pirate.id - assert @ship.pirate.marked_for_destruction? + assert_predicate @ship.pirate, :marked_for_destruction? assert Pirate.find_by_id(id) @ship.save @@ -827,7 +827,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction @ship.pirate.catchphrase = "" - assert !@ship.valid? + assert_not_predicate @ship, :valid? @ship.pirate.mark_for_destruction @ship.pirate.expects(:valid?).never @@ -881,7 +881,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase ids.each { |id| assert klass.find_by_id(id) } @pirate.save - assert @pirate.reload.birds.empty? + assert_empty @pirate.reload.birds ids.each { |id| assert_nil klass.find_by_id(id) } end @@ -889,14 +889,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.birds.create!(name: :parrot) @pirate.birds.first.destroy @pirate.save! - assert @pirate.reload.birds.empty? + assert_empty @pirate.reload.birds end def test_should_skip_validation_on_has_many_if_marked_for_destruction 2.times { |i| @pirate.birds.create!(name: "birds_#{i}") } @pirate.birds.each { |bird| bird.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? @pirate.birds.each do |bird| bird.mark_for_destruction @@ -909,10 +909,10 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.birds.create!(name: "birds_1") @pirate.birds.each { |bird| bird.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? @pirate.birds.each(&:destroy) - assert @pirate.valid? + assert_predicate @pirate, :valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many @@ -1010,17 +1010,17 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.save end - assert @pirate.reload.parrots.empty? + assert_empty @pirate.reload.parrots join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}") - assert join_records.empty? + assert_empty join_records end def test_should_skip_validation_on_habtm_if_marked_for_destruction 2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") } @pirate.parrots.each { |parrot| parrot.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? @pirate.parrots.each do |parrot| parrot.mark_for_destruction @@ -1028,17 +1028,17 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end @pirate.save! - assert @pirate.reload.parrots.empty? + assert_empty @pirate.reload.parrots end def test_should_skip_validation_on_habtm_if_destroyed @pirate.parrots.create!(name: "parrots_1") @pirate.parrots.each { |parrot| parrot.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? @pirate.parrots.each(&:destroy) - assert @pirate.valid? + assert_predicate @pirate, :valid? end def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm @@ -1145,16 +1145,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def test_should_automatically_validate_the_associated_model @pirate.ship.name = "" - assert @pirate.invalid? - assert @pirate.errors[:"ship.name"].any? + assert_predicate @pirate, :invalid? + assert_predicate @pirate.errors[:"ship.name"], :any? end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid @pirate.ship.name = nil @pirate.catchphrase = nil - assert @pirate.invalid? - assert @pirate.errors[:"ship.name"].any? - assert @pirate.errors[:catchphrase].any? + assert_predicate @pirate, :invalid? + assert_predicate @pirate.errors[:"ship.name"], :any? + assert_predicate @pirate.errors[:catchphrase], :any? end def test_should_not_ignore_different_error_messages_on_the_same_attribute @@ -1163,7 +1163,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase Ship.validates_format_of :name, with: /\w/ @pirate.ship.name = "" @pirate.catchphrase = nil - assert @pirate.invalid? + assert_predicate @pirate, :invalid? assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"] ensure Ship._validators = old_validators if old_validators @@ -1244,7 +1244,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase ship = ShipWithoutNestedAttributes.new(name: "The Black Flag") ship.parts.build.mark_for_destruction - assert_not ship.valid? + assert_not_predicate ship, :valid? end end @@ -1299,16 +1299,16 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase def test_should_automatically_validate_the_associated_model @ship.pirate.catchphrase = "" - assert @ship.invalid? - assert @ship.errors[:"pirate.catchphrase"].any? + assert_predicate @ship, :invalid? + assert_predicate @ship.errors[:"pirate.catchphrase"], :any? end def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid @ship.name = nil @ship.pirate.catchphrase = nil - assert @ship.invalid? - assert @ship.errors[:name].any? - assert @ship.errors[:"pirate.catchphrase"].any? + assert_predicate @ship, :invalid? + assert_predicate @ship.errors[:name], :any? + assert_predicate @ship.errors[:"pirate.catchphrase"], :any? end def test_should_still_allow_to_bypass_validations_on_the_associated_model @@ -1403,17 +1403,17 @@ module AutosaveAssociationOnACollectionAssociationTests def test_should_automatically_validate_the_associated_models @pirate.send(@association_name).each { |child| child.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] - assert @pirate.errors[@association_name].empty? + assert_empty @pirate.errors[@association_name] end def test_should_not_use_default_invalid_error_on_associated_models @pirate.send(@association_name).build(name: "") - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] - assert @pirate.errors[@association_name].empty? + assert_empty @pirate.errors[@association_name] end def test_should_default_invalid_error_from_i18n @@ -1423,10 +1423,10 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).build(name: "") - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages - assert @pirate.errors[@association_name].empty? + assert_empty @pirate.errors[@association_name] ensure I18n.backend = I18n::Backend::Simple.new end @@ -1435,9 +1435,9 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).each { |child| child.name = "" } @pirate.catchphrase = nil - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] - assert @pirate.errors[:catchphrase].any? + assert_predicate @pirate.errors[:catchphrase], :any? end def test_should_allow_to_bypass_validations_on_the_associated_models_on_update @@ -1595,10 +1595,10 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te end test "should automatically validate associations" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.birds.each { |bird| bird.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? end end @@ -1613,15 +1613,15 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes end test "should automatically validate associations with :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.ship.name = "" - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? end test "should not automatically add validate associations without :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.non_validated_ship.name = "" - assert @pirate.valid? + assert_predicate @pirate, :valid? end end @@ -1634,15 +1634,15 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord:: end test "should automatically validate associations with :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.parrot = Parrot.new(name: "") - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? end test "should not automatically validate associations without :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.non_validated_parrot = Parrot.new(name: "") - assert @pirate.valid? + assert_predicate @pirate, :valid? end end @@ -1655,17 +1655,17 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test end test "should automatically validate associations with :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.parrots = [ Parrot.new(name: "popuga") ] @pirate.parrots.each { |parrot| parrot.name = "" } - assert !@pirate.valid? + assert_not_predicate @pirate, :valid? end test "should not automatically validate associations without :validate => true" do - assert @pirate.valid? + assert_predicate @pirate, :valid? @pirate.non_validated_parrots = [ Parrot.new(name: "popuga") ] @pirate.non_validated_parrots.each { |parrot| parrot.name = "" } - assert @pirate.valid? + assert_predicate @pirate, :valid? end end @@ -1686,7 +1686,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas end test "should not generate validation methods for has_one associations without :validate => true" do - assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship) + assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_ship end test "should generate validation methods for belongs_to associations with :validate => true" do @@ -1694,7 +1694,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas end test "should not generate validation methods for belongs_to associations without :validate => true" do - assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot) + assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_parrot end test "should generate validation methods for HABTM associations with :validate => true" do diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 3497f6aae4..983a3d366a 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -104,7 +104,7 @@ class BasicsTest < ActiveRecord::TestCase pk = Author.columns_hash["id"] ref = Post.columns_hash["author_id"] - assert_equal pk.bigint?, ref.bigint? + assert_equal pk.sql_type, ref.sql_type end def test_many_mutations @@ -147,8 +147,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_table_exists - assert !NonExistentTable.table_exists? - assert Topic.table_exists? + assert_not_predicate NonExistentTable, :table_exists? + assert_predicate Topic, :table_exists? end def test_preserving_date_objects @@ -450,7 +450,7 @@ class BasicsTest < ActiveRecord::TestCase def test_default_values topic = Topic.new - assert topic.approved? + assert_predicate topic, :approved? assert_nil topic.written_on assert_nil topic.bonus_time assert_nil topic.last_read @@ -458,7 +458,7 @@ class BasicsTest < ActiveRecord::TestCase topic.save topic = Topic.find(topic.id) - assert topic.approved? + assert_predicate topic, :approved? assert_nil topic.last_read # Oracle has some funky default handling, so it requires a bit of @@ -629,7 +629,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_readonly_attributes - assert_equal Set.new([ "title" , "comments_count" ]), ReadonlyTitlePost.readonly_attributes + assert_equal Set.new([ "title", "comments_count" ]), ReadonlyTitlePost.readonly_attributes post = ReadonlyTitlePost.create(title: "cannot change this", body: "changeable") post.reload @@ -727,9 +727,9 @@ class BasicsTest < ActiveRecord::TestCase b_nil = Boolean.find(nil_id) assert_nil b_nil.value b_false = Boolean.find(false_id) - assert !b_false.value? + assert_not_predicate b_false, :value? b_true = Boolean.find(true_id) - assert b_true.value? + assert_predicate b_true, :value? end def test_boolean_without_questionmark @@ -753,9 +753,9 @@ class BasicsTest < ActiveRecord::TestCase b_blank = Boolean.find(blank_id) assert_nil b_blank.value b_false = Boolean.find(false_id) - assert !b_false.value? + assert_not_predicate b_false, :value? b_true = Boolean.find(true_id) - assert b_true.value? + assert_predicate b_true, :value? end def test_new_record_returns_boolean @@ -768,7 +768,7 @@ class BasicsTest < ActiveRecord::TestCase duped_topic = nil assert_nothing_raised { duped_topic = topic.dup } assert_equal topic.title, duped_topic.title - assert !duped_topic.persisted? + assert_not_predicate duped_topic, :persisted? # test if the attributes have been duped topic.title = "a" @@ -786,7 +786,7 @@ class BasicsTest < ActiveRecord::TestCase # test if saved clone object differs from original duped_topic.save - assert duped_topic.persisted? + assert_predicate duped_topic, :persisted? assert_not_equal duped_topic.id, topic.id duped_topic.reload @@ -807,7 +807,7 @@ class BasicsTest < ActiveRecord::TestCase assert_nothing_raised { dup = dev.dup } assert_kind_of DeveloperSalary, dup.salary assert_equal dev.salary.amount, dup.salary.amount - assert !dup.persisted? + assert_not_predicate dup, :persisted? # test if the attributes have been duped original_amount = dup.salary.amount @@ -815,7 +815,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal original_amount, dup.salary.amount assert dup.save - assert dup.persisted? + assert_predicate dup, :persisted? assert_not_equal dup.id, dev.id end @@ -835,22 +835,22 @@ class BasicsTest < ActiveRecord::TestCase def test_clone_of_new_object_with_defaults developer = Developer.new - assert !developer.name_changed? - assert !developer.salary_changed? + assert_not_predicate developer, :name_changed? + assert_not_predicate developer, :salary_changed? cloned_developer = developer.clone - assert !cloned_developer.name_changed? - assert !cloned_developer.salary_changed? + assert_not_predicate cloned_developer, :name_changed? + assert_not_predicate cloned_developer, :salary_changed? end def test_clone_of_new_object_marks_attributes_as_dirty developer = Developer.new name: "Bjorn", salary: 100000 - assert developer.name_changed? - assert developer.salary_changed? + assert_predicate developer, :name_changed? + assert_predicate developer, :salary_changed? cloned_developer = developer.clone - assert cloned_developer.name_changed? - assert cloned_developer.salary_changed? + assert_predicate cloned_developer, :name_changed? + assert_predicate cloned_developer, :salary_changed? end def test_clone_of_new_object_marks_as_dirty_only_changed_attributes @@ -859,24 +859,24 @@ class BasicsTest < ActiveRecord::TestCase assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed cloned_developer = developer.clone - assert cloned_developer.name_changed? + assert_predicate cloned_developer, :name_changed? assert !cloned_developer.salary_changed? # ... and cloned instance should behave same end def test_dup_of_saved_object_marks_attributes_as_dirty developer = Developer.create! name: "Bjorn", salary: 100000 - assert !developer.name_changed? - assert !developer.salary_changed? + assert_not_predicate developer, :name_changed? + assert_not_predicate developer, :salary_changed? cloned_developer = developer.dup assert cloned_developer.name_changed? # both attributes differ from defaults - assert cloned_developer.salary_changed? + assert_predicate cloned_developer, :salary_changed? end def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes developer = Developer.create! name: "Bjorn" assert !developer.name_changed? # both attributes of saved object should be treated as not changed - assert !developer.salary_changed? + assert_not_predicate developer, :salary_changed? cloned_developer = developer.dup assert cloned_developer.name_changed? # ... but on cloned object should be @@ -951,14 +951,14 @@ class BasicsTest < ActiveRecord::TestCase end def test_toggle_attribute - assert !topics(:first).approved? + assert_not_predicate topics(:first), :approved? topics(:first).toggle!(:approved) - assert topics(:first).approved? + assert_predicate topics(:first), :approved? topic = topics(:first) topic.toggle(:approved) - assert !topic.approved? + assert_not_predicate topic, :approved? topic.reload - assert topic.approved? + assert_predicate topic, :approved? end def test_reload @@ -1431,11 +1431,11 @@ class BasicsTest < ActiveRecord::TestCase test "resetting column information doesn't remove attribute methods" do topic = topics(:first) - assert_not topic.id_changed? + assert_not_predicate topic, :id_changed? Topic.reset_column_information - assert_not topic.id_changed? + assert_not_predicate topic, :id_changed? end test "ignored columns are not present in columns_hash" do @@ -1447,27 +1447,27 @@ class BasicsTest < ActiveRecord::TestCase end test "ignored columns have no attribute methods" do - refute Developer.new.respond_to?(:first_name) - refute Developer.new.respond_to?(:first_name=) - refute Developer.new.respond_to?(:first_name?) - refute SubDeveloper.new.respond_to?(:first_name) - refute SubDeveloper.new.respond_to?(:first_name=) - refute SubDeveloper.new.respond_to?(:first_name?) - refute SymbolIgnoredDeveloper.new.respond_to?(:first_name) - refute SymbolIgnoredDeveloper.new.respond_to?(:first_name=) - refute SymbolIgnoredDeveloper.new.respond_to?(:first_name?) + assert_not_respond_to Developer.new, :first_name + assert_not_respond_to Developer.new, :first_name= + assert_not_respond_to Developer.new, :first_name? + assert_not_respond_to SubDeveloper.new, :first_name + assert_not_respond_to SubDeveloper.new, :first_name= + assert_not_respond_to SubDeveloper.new, :first_name? + assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name + assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name= + assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name? end test "ignored columns don't prevent explicit declaration of attribute methods" do - assert Developer.new.respond_to?(:last_name) - assert Developer.new.respond_to?(:last_name=) - assert Developer.new.respond_to?(:last_name?) - assert SubDeveloper.new.respond_to?(:last_name) - assert SubDeveloper.new.respond_to?(:last_name=) - assert SubDeveloper.new.respond_to?(:last_name?) - assert SymbolIgnoredDeveloper.new.respond_to?(:last_name) - assert SymbolIgnoredDeveloper.new.respond_to?(:last_name=) - assert SymbolIgnoredDeveloper.new.respond_to?(:last_name?) + assert_respond_to Developer.new, :last_name + assert_respond_to Developer.new, :last_name= + assert_respond_to Developer.new, :last_name? + assert_respond_to SubDeveloper.new, :last_name + assert_respond_to SubDeveloper.new, :last_name= + assert_respond_to SubDeveloper.new, :last_name? + assert_respond_to SymbolIgnoredDeveloper.new, :last_name + assert_respond_to SymbolIgnoredDeveloper.new, :last_name= + assert_respond_to SymbolIgnoredDeveloper.new, :last_name? end test "ignored columns are stored as an array of string" do @@ -1477,22 +1477,46 @@ class BasicsTest < ActiveRecord::TestCase test "when #reload called, ignored columns' attribute methods are not defined" do developer = Developer.create!(name: "Developer") - refute developer.respond_to?(:first_name) - refute developer.respond_to?(:first_name=) + assert_not_respond_to developer, :first_name + assert_not_respond_to developer, :first_name= developer.reload - refute developer.respond_to?(:first_name) - refute developer.respond_to?(:first_name=) + assert_not_respond_to developer, :first_name + assert_not_respond_to developer, :first_name= end test "ignored columns not included in SELECT" do query = Developer.all.to_sql.downcase # ignored column - refute query.include?("first_name") + assert_not query.include?("first_name") # regular column assert query.include?("name") end + + test "column names are quoted when using #from clause and model has ignored columns" do + refute_empty Developer.ignored_columns + query = Developer.from("developers").to_sql + quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}" + + assert_match(/SELECT #{quoted_id}.* FROM developers/, query) + end + + test "using table name qualified column names unless having SELECT list explicitly" do + assert_equal developers(:david), Developer.from("developers").joins(:shared_computers).take + end + + test "protected environments by default is an array with production" do + assert_equal ["production"], ActiveRecord::Base.protected_environments + end + + def test_protected_environments_are_stored_as_an_array_of_string + previous_protected_environments = ActiveRecord::Base.protected_environments + ActiveRecord::Base.protected_environments = [:staging, "production"] + assert_equal ["staging", "production"], ActiveRecord::Base.protected_environments + ensure + ActiveRecord::Base.protected_environments = previous_protected_environments + end end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index be8aeed5ac..ad701fb200 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -313,7 +313,7 @@ class EachTest < ActiveRecord::TestCase def test_in_batches_each_record_should_yield_record_if_block_is_given assert_queries(6) do Post.in_batches(of: 2).each_record do |post| - assert post.title.present? + assert_predicate post.title, :present? assert_kind_of Post, post end end @@ -322,7 +322,7 @@ class EachTest < ActiveRecord::TestCase def test_in_batches_each_record_should_return_enumerator_if_no_block_given assert_queries(6) do Post.in_batches(of: 2).each_record.with_index do |post, i| - assert post.title.present? + assert_predicate post.title, :present? assert_kind_of Post, post end end @@ -353,24 +353,24 @@ class EachTest < ActiveRecord::TestCase def test_in_batches_should_not_be_loaded Post.in_batches(of: 1) do |relation| - assert_not relation.loaded? + assert_not_predicate relation, :loaded? end Post.in_batches(of: 1, load: false) do |relation| - assert_not relation.loaded? + assert_not_predicate relation, :loaded? end end def test_in_batches_should_be_loaded Post.in_batches(of: 1, load: true) do |relation| - assert relation.loaded? + assert_predicate relation, :loaded? end end def test_in_batches_if_not_loaded_executes_more_queries assert_queries(@total + 1) do Post.in_batches(of: 1, load: false) do |relation| - assert_not relation.loaded? + assert_not_predicate relation, :loaded? end end end @@ -592,7 +592,11 @@ class EachTest < ActiveRecord::TestCase table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) - posts = ActiveRecord::Relation.create(Post, table_alias, predicate_builder) + posts = ActiveRecord::Relation.create( + Post, + table: table_alias, + predicate_builder: predicate_builder + ) posts.find_each {} end end diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb index 8f2f2c6186..3a569f226e 100644 --- a/activerecord/test/cases/cache_key_test.rb +++ b/activerecord/test/cases/cache_key_test.rb @@ -38,8 +38,8 @@ module ActiveRecord end test "cache_version is only there when versioning is on" do - assert CacheMeWithVersion.create.cache_version.present? - assert_not CacheMe.create.cache_version.present? + assert_predicate CacheMeWithVersion.create.cache_version, :present? + assert_not_predicate CacheMe.create.cache_version, :present? end test "cache_key_with_version always has both key and version" do diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 55b50e4f84..82b15e565b 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -21,7 +21,7 @@ require "models/comment" require "models/rating" class CalculationsTest < ActiveRecord::TestCase - fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books + fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments def test_should_sum_field assert_equal 318, Account.sum(:credit_limit) @@ -236,6 +236,18 @@ class CalculationsTest < ActiveRecord::TestCase end end + def test_count_with_eager_loading_and_custom_order + posts = Post.includes(:comments).order("comments.id") + assert_queries(1) { assert_equal 11, posts.count } + assert_queries(1) { assert_equal 11, posts.count(:all) } + end + + def test_distinct_count_all_with_custom_select_and_order + accounts = Account.distinct.select("credit_limit % 10").order(Arel.sql("credit_limit % 10")) + assert_queries(1) { assert_equal 3, accounts.count(:all) } + assert_queries(1) { assert_equal 3, accounts.load.size } + end + def test_distinct_count_with_order_and_limit assert_equal 4, Account.distinct.order(:firm_id).limit(4).count end @@ -682,6 +694,11 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id) end + def test_pluck_with_includes_offset + assert_equal [5], Topic.includes(:replies).order(:id).offset(4).pluck(:id) + assert_equal [], Topic.includes(:replies).order(:id).offset(5).pluck(:id) + end + def test_pluck_not_auto_table_name_prefix_if_column_included Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)]) ids = Company.includes(:contracts).pluck(:developer_id) diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 55c7475f46..3b283a3aa6 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -394,27 +394,27 @@ class CallbacksTest < ActiveRecord::TestCase def test_before_create_throwing_abort someone = CallbackHaltedDeveloper.new someone.cancel_before_create = true - assert someone.valid? + assert_predicate someone, :valid? assert !someone.save assert_save_callbacks_not_called(someone) end def test_before_save_throwing_abort david = DeveloperWithCanceledCallbacks.find(1) - assert david.valid? + assert_predicate david, :valid? assert !david.save exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } assert_equal david, exc.record david = DeveloperWithCanceledCallbacks.find(1) david.salary = 10_000_000 - assert !david.valid? + assert_not_predicate david, :valid? assert !david.save assert_raise(ActiveRecord::RecordInvalid) { david.save! } someone = CallbackHaltedDeveloper.find(1) someone.cancel_before_save = true - assert someone.valid? + assert_predicate someone, :valid? assert !someone.save assert_save_callbacks_not_called(someone) end @@ -422,7 +422,7 @@ class CallbacksTest < ActiveRecord::TestCase def test_before_update_throwing_abort someone = CallbackHaltedDeveloper.find(1) someone.cancel_before_update = true - assert someone.valid? + assert_predicate someone, :valid? assert !someone.save assert_save_callbacks_not_called(someone) end diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb index 3187e6aed5..65e5016040 100644 --- a/activerecord/test/cases/clone_test.rb +++ b/activerecord/test/cases/clone_test.rb @@ -36,7 +36,7 @@ module ActiveRecord cloned = Topic.new clone = cloned.clone cloned.freeze - assert_not clone.frozen? + assert_not_predicate clone, :frozen? end end end diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb index 19d6464a22..a5d908344a 100644 --- a/activerecord/test/cases/collection_cache_key_test.rb +++ b/activerecord/test/cases/collection_cache_key_test.rb @@ -24,7 +24,7 @@ module ActiveRecord /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key - assert_equal Digest::MD5.hexdigest(developers.to_sql), $1 + assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1 assert_equal developers.count.to_s, $2 assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 end @@ -37,7 +37,7 @@ module ActiveRecord /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key - assert_equal Digest::MD5.hexdigest(developers.to_sql), $1 + assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1 assert_equal developers.count.to_s, $2 assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 end @@ -50,7 +50,7 @@ module ActiveRecord /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key - assert_equal Digest::MD5.hexdigest(developers.to_sql), $1 + assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1 assert_equal developers.count.to_s, $2 assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 end @@ -60,7 +60,11 @@ module ActiveRecord table_metadata = ActiveRecord::TableMetadata.new(Developer, table_alias) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) - developers = ActiveRecord::Relation.create(Developer, table_alias, predicate_builder) + developers = ActiveRecord::Relation.create( + Developer, + table: table_alias, + predicate_builder: predicate_builder + ) developers = developers.where(salary: 100000).order(updated_at: :desc) last_developer_timestamp = developers.first.updated_at @@ -68,7 +72,7 @@ module ActiveRecord /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key - assert_equal Digest::MD5.hexdigest(developers.to_sql), $1 + assert_equal ActiveSupport::Digest.hexdigest(developers.to_sql), $1 assert_equal developers.count.to_s, $2 assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3 end @@ -141,5 +145,17 @@ module ActiveRecord assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key) end + + test "cache_key with a relation having distinct and order" do + developers = Developer.distinct.order(:salary).limit(5) + + assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key) + end + + test "cache_key with a relation having custom select and order" do + developers = Developer.select("name AS dev_name").order("dev_name DESC").limit(5) + + assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key) + end end end diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb index 82c6cf8dea..72838ff56b 100644 --- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb +++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb @@ -45,11 +45,11 @@ module ActiveRecord # Make sure the pool marks the connection in use assert_equal @adapter, pool.connection - assert @adapter.in_use? + assert_predicate @adapter, :in_use? # Close should put the adapter back in the pool @adapter.close - assert_not @adapter.in_use? + assert_not_predicate @adapter, :in_use? assert_equal @adapter, pool.connection end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index cae74a2b9b..f4cc251fb9 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -103,11 +103,11 @@ module ActiveRecord end def test_active_connections? - assert !@handler.active_connections? + assert_not_predicate @handler, :active_connections? assert @handler.retrieve_connection(@spec_name) - assert @handler.active_connections? + assert_predicate @handler, :active_connections? @handler.clear_active_connections! - assert !@handler.active_connections? + assert_not_predicate @handler, :active_connections? end def test_retrieve_connection_pool @@ -146,7 +146,7 @@ module ActiveRecord def test_forked_child_doesnt_mangle_parent_connection object_id = ActiveRecord::Base.connection.object_id - assert ActiveRecord::Base.connection.active? + assert_predicate ActiveRecord::Base.connection, :active? rd, wr = IO.pipe rd.binmode @@ -171,6 +171,48 @@ module ActiveRecord assert_equal 3, ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people") end + unless in_memory_db? + def test_forked_child_recovers_from_disconnected_parent + object_id = ActiveRecord::Base.connection.object_id + assert_predicate ActiveRecord::Base.connection, :active? + + rd, wr = IO.pipe + rd.binmode + wr.binmode + + outer_pid = fork { + ActiveRecord::Base.connection.disconnect! + + pid = fork { + rd.close + if ActiveRecord::Base.connection.active? + pair = [ActiveRecord::Base.connection.object_id, + ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")] + wr.write Marshal.dump pair + end + wr.close + + exit # allow finalizers to run + } + + Process.waitpid pid + } + + wr.close + + Process.waitpid outer_pid + child_id, child_count = Marshal.load(rd.read) + + assert_not_equal object_id, child_id + rd.close + + assert_equal 3, child_count + + # Outer connection is unaffected + assert_equal 6, ActiveRecord::Base.connection.select_value("SELECT 2 * COUNT(*) FROM people") + end + end + def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool @pool.schema_cache = @pool.connection.schema_cache @pool.schema_cache.add("posts") @@ -232,15 +274,15 @@ module ActiveRecord def test_a_class_using_custom_pool_and_switching_back_to_primary klass2 = Class.new(Base) { def self.name; "klass2"; end } - assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + assert_same klass2.connection, ActiveRecord::Base.connection pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config) - assert_equal klass2.connection.object_id, pool.connection.object_id - refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + assert_same klass2.connection, pool.connection + refute_same klass2.connection, ActiveRecord::Base.connection klass2.remove_connection - assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + assert_same klass2.connection, ActiveRecord::Base.connection end def test_connection_specification_name_should_fallback_to_parent @@ -255,8 +297,8 @@ module ActiveRecord def test_remove_connection_should_not_remove_parent klass2 = Class.new(Base) { def self.name; "klass2"; end } klass2.remove_connection - refute_nil ActiveRecord::Base.connection.object_id - assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id + refute_nil ActiveRecord::Base.connection + assert_same klass2.connection, ActiveRecord::Base.connection end end end diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 006be9e65d..67496381d1 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -22,8 +22,8 @@ module ActiveRecord new_cache = YAML.load(YAML.dump(@cache)) assert_no_queries do - assert_equal 11, new_cache.columns("posts").size - assert_equal 11, new_cache.columns_hash("posts").size + assert_equal 12, new_cache.columns("posts").size + assert_equal 12, new_cache.columns_hash("posts").size assert new_cache.data_sources("posts") assert_equal "id", new_cache.primary_keys("posts") end @@ -75,8 +75,8 @@ module ActiveRecord @cache = Marshal.load(Marshal.dump(@cache)) assert_no_queries do - assert_equal 11, @cache.columns("posts").size - assert_equal 11, @cache.columns_hash("posts").size + assert_equal 12, @cache.columns("posts").size + assert_equal 12, @cache.columns_hash("posts").size assert @cache.data_sources("posts") assert_equal "id", @cache.primary_keys("posts") end diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb index 917a04ebc3..1c79d776f0 100644 --- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -82,11 +82,11 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin end def test_bigint_limit - cast_type = @connection.send(:type_map).lookup("bigint") + limit = @connection.send(:type_map).lookup("bigint").send(:_limit) if current_adapter?(:OracleAdapter) - assert_equal 19, cast_type.limit + assert_equal 19, limit else - assert_equal 8, cast_type.limit + assert_equal 8, limit end end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 9d6ecbde78..0941ee3309 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -27,7 +27,7 @@ module ActiveRecord # make sure we have an active connection assert ActiveRecord::Base.connection - assert ActiveRecord::Base.connection_handler.active_connections? + assert_predicate ActiveRecord::Base.connection_handler, :active_connections? end def test_app_delegation @@ -47,14 +47,14 @@ module ActiveRecord def test_connections_are_cleared_after_body_close _, _, body = @management.call(@env) body.close - assert !ActiveRecord::Base.connection_handler.active_connections? + assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections? end def test_active_connections_are_not_cleared_on_body_close_during_transaction ActiveRecord::Base.transaction do _, _, body = @management.call(@env) body.close - assert ActiveRecord::Base.connection_handler.active_connections? + assert_predicate ActiveRecord::Base.connection_handler, :active_connections? end end @@ -62,7 +62,7 @@ module ActiveRecord app = Class.new(App) { def call(env); raise NotImplementedError; end }.new explosive = middleware(app) assert_raises(NotImplementedError) { explosive.call(@env) } - assert !ActiveRecord::Base.connection_handler.active_connections? + assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections? end def test_connections_not_closed_if_exception_inside_transaction @@ -70,14 +70,14 @@ module ActiveRecord app = Class.new(App) { def call(env); raise RuntimeError; end }.new explosive = middleware(app) assert_raises(RuntimeError) { explosive.call(@env) } - assert ActiveRecord::Base.connection_handler.active_connections? + assert_predicate ActiveRecord::Base.connection_handler, :active_connections? end end test "doesn't clear active connections when running in a test case" do executor.wrap do @management.call(@env) - assert ActiveRecord::Base.connection_handler.active_connections? + assert_predicate ActiveRecord::Base.connection_handler, :active_connections? end end @@ -85,7 +85,7 @@ module ActiveRecord body = Class.new(String) { def to_path; "/path"; end }.new app = lambda { |_| [200, {}, body] } response_body = middleware(app).call(@env)[2] - assert response_body.respond_to?(:to_path) + assert_respond_to response_body, :to_path assert_equal "/path", response_body.to_path end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 1e08cc74dc..bfaaa3c54e 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -35,12 +35,12 @@ module ActiveRecord def test_checkout_after_close connection = pool.connection - assert connection.in_use? + assert_predicate connection, :in_use? connection.close - assert !connection.in_use? + assert_not_predicate connection, :in_use? - assert pool.connection.in_use? + assert_predicate pool.connection, :in_use? end def test_released_connection_moves_between_threads @@ -80,18 +80,18 @@ module ActiveRecord end def test_active_connection_in_use - assert !pool.active_connection? + assert_not_predicate pool, :active_connection? main_thread = pool.connection - assert pool.active_connection? + assert_predicate pool, :active_connection? main_thread.close - assert !pool.active_connection? + assert_not_predicate pool, :active_connection? end def test_full_pool_exception - @pool.size.times { @pool.checkout } + @pool.size.times { assert @pool.checkout } assert_raises(ConnectionTimeoutError) do @pool.checkout end @@ -205,11 +205,11 @@ module ActiveRecord def test_remove_connection conn = @pool.checkout - assert conn.in_use? + assert_predicate conn, :in_use? length = @pool.connections.length @pool.remove conn - assert conn.in_use? + assert_predicate conn, :in_use? assert_equal(length - 1, @pool.connections.length) ensure conn.close @@ -224,11 +224,11 @@ module ActiveRecord end def test_active_connection? - assert !@pool.active_connection? + assert_not_predicate @pool, :active_connection? assert @pool.connection - assert @pool.active_connection? + assert_predicate @pool, :active_connection? @pool.release_connection - assert !@pool.active_connection? + assert_not_predicate @pool, :active_connection? end def test_checkout_behaviour @@ -469,6 +469,7 @@ module ActiveRecord end def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns + Thread.report_on_exception, original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception) @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout [:disconnect, :clear_reloadable_connections].each do |group_action_method| @pool.with_connection do |connection| @@ -477,6 +478,8 @@ module ActiveRecord end end end + ensure + Thread.report_on_exception = original_report_on_exception if Thread.respond_to?(:report_on_exception) end def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns @@ -493,7 +496,7 @@ module ActiveRecord assert_nil timed_join_result # assert that since this is within default timeout our connection hasn't been forcefully taken away from us - assert @pool.active_connection? + assert_predicate @pool, :active_connection? end ensure thread.join if thread && !timed_join_result # clean up the other thread @@ -507,7 +510,7 @@ module ActiveRecord @pool.with_connection do |connection| Thread.new { @pool.send(group_action_method) }.join # assert connection has been forcefully taken away from us - assert_not @pool.active_connection? + assert_not_predicate @pool, :active_connection? # make a new connection for with_connection to clean up @pool.connection diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 4690682cd8..3d11b573f1 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -53,7 +53,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase def test_default_decimal_number record = DefaultNumber.new - assert_equal BigDecimal.new("2.78"), record.decimal_number + assert_equal BigDecimal("2.78"), record.decimal_number assert_equal "2.78", record.decimal_number_before_type_cast end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index a602f83d8c..14c4e3cbd4 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -24,18 +24,18 @@ class DirtyTest < ActiveRecord::TestCase # Change catchphrase. pirate.catchphrase = "arrr" - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? assert_nil pirate.catchphrase_was assert_equal [nil, "arrr"], pirate.catchphrase_change # Saved - no changes. pirate.save! - assert !pirate.catchphrase_changed? + assert_not_predicate pirate, :catchphrase_changed? assert_nil pirate.catchphrase_change # Same value - no changes. pirate.catchphrase = "arrr" - assert !pirate.catchphrase_changed? + assert_not_predicate pirate, :catchphrase_changed? assert_nil pirate.catchphrase_change end @@ -46,23 +46,23 @@ class DirtyTest < ActiveRecord::TestCase # New record - no changes. pirate = target.new - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = "arrrr, time zone!!" pirate.save! - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now - 1.day - assert pirate.created_on_changed? + assert_predicate pirate, :created_on_changed? assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was pirate.created_on = old_created_on - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? end end @@ -73,7 +73,7 @@ class DirtyTest < ActiveRecord::TestCase pirate = target.create! pirate.created_on = pirate.created_on - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? end end @@ -86,19 +86,19 @@ class DirtyTest < ActiveRecord::TestCase # New record - no changes. pirate = target.new - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = "arrrr, time zone!!" pirate.save! - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now + 1.day - assert pirate.created_on_changed? + assert_predicate pirate, :created_on_changed? # kind_of does not work because # ActiveSupport::TimeWithZone.name == 'Time' assert_instance_of Time, pirate.created_on_was @@ -113,19 +113,19 @@ class DirtyTest < ActiveRecord::TestCase # New record - no changes. pirate = target.new - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Saved - no changes. pirate.catchphrase = "arrrr, time zone!!" pirate.save! - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? assert_nil pirate.created_on_change # Change created_on. old_created_on = pirate.created_on pirate.created_on = Time.now + 1.day - assert pirate.created_on_changed? + assert_predicate pirate, :created_on_changed? # kind_of does not work because # ActiveSupport::TimeWithZone.name == 'Time' assert_instance_of Time, pirate.created_on_was @@ -137,11 +137,11 @@ class DirtyTest < ActiveRecord::TestCase # the actual attribute here is name, title is an # alias setup via alias_attribute parrot = Parrot.new - assert !parrot.title_changed? + assert_not_predicate parrot, :title_changed? assert_nil parrot.title_change parrot.name = "Sam" - assert parrot.title_changed? + assert_predicate parrot, :title_changed? assert_nil parrot.title_was assert_equal parrot.name_change, parrot.title_change end @@ -153,7 +153,7 @@ class DirtyTest < ActiveRecord::TestCase pirate.restore_catchphrase! assert_equal "Yar!", pirate.catchphrase assert_equal Hash.new, pirate.changes - assert !pirate.catchphrase_changed? + assert_not_predicate pirate, :catchphrase_changed? end def test_nullable_number_not_marked_as_changed_if_new_value_is_blank @@ -161,7 +161,7 @@ class DirtyTest < ActiveRecord::TestCase ["", nil].each do |value| pirate.parrot_id = value - assert !pirate.parrot_id_changed? + assert_not_predicate pirate, :parrot_id_changed? assert_nil pirate.parrot_id_change end end @@ -171,7 +171,7 @@ class DirtyTest < ActiveRecord::TestCase ["", nil].each do |value| numeric_data.bank_balance = value - assert !numeric_data.bank_balance_changed? + assert_not_predicate numeric_data, :bank_balance_changed? assert_nil numeric_data.bank_balance_change end end @@ -181,7 +181,7 @@ class DirtyTest < ActiveRecord::TestCase ["", nil].each do |value| numeric_data.temperature = value - assert !numeric_data.temperature_changed? + assert_not_predicate numeric_data, :temperature_changed? assert_nil numeric_data.temperature_change end end @@ -197,7 +197,7 @@ class DirtyTest < ActiveRecord::TestCase ["", nil].each do |value| topic.written_on = value assert_nil topic.written_on - assert !topic.written_on_changed? + assert_not_predicate topic, :written_on_changed? end end end @@ -208,10 +208,10 @@ class DirtyTest < ActiveRecord::TestCase pirate.catchphrase = "arrr" assert pirate.save! - assert !pirate.changed? + assert_not_predicate pirate, :changed? pirate.parrot_id = "0" - assert !pirate.changed? + assert_not_predicate pirate, :changed? end def test_integer_zero_to_integer_zero_not_marked_as_changed @@ -220,17 +220,17 @@ class DirtyTest < ActiveRecord::TestCase pirate.catchphrase = "arrr" assert pirate.save! - assert !pirate.changed? + assert_not_predicate pirate, :changed? pirate.parrot_id = 0 - assert !pirate.changed? + assert_not_predicate pirate, :changed? end def test_float_zero_to_string_zero_not_marked_as_changed data = NumericData.new temperature: 0.0 data.save! - assert_not data.changed? + assert_not_predicate data, :changed? data.temperature = "0" assert_empty data.changes @@ -251,38 +251,38 @@ class DirtyTest < ActiveRecord::TestCase # check the change from 1 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = "" - assert pirate.parrot_id_changed? + assert_predicate pirate, :parrot_id_changed? assert_equal([1, nil], pirate.parrot_id_change) pirate.save # check the change from nil to 0 pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = 0 - assert pirate.parrot_id_changed? + assert_predicate pirate, :parrot_id_changed? assert_equal([nil, 0], pirate.parrot_id_change) pirate.save # check the change from 0 to '' pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties") pirate.parrot_id = "" - assert pirate.parrot_id_changed? + assert_predicate pirate, :parrot_id_changed? assert_equal([0, nil], pirate.parrot_id_change) end def test_object_should_be_changed_if_any_attribute_is_changed pirate = Pirate.new - assert !pirate.changed? + assert_not_predicate pirate, :changed? assert_equal [], pirate.changed assert_equal Hash.new, pirate.changes pirate.catchphrase = "arrr" - assert pirate.changed? + assert_predicate pirate, :changed? assert_nil pirate.catchphrase_was assert_equal %w(catchphrase), pirate.changed assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes) pirate.save - assert !pirate.changed? + assert_not_predicate pirate, :changed? assert_equal [], pirate.changed assert_equal Hash.new, pirate.changes end @@ -290,40 +290,40 @@ class DirtyTest < ActiveRecord::TestCase def test_attribute_will_change! pirate = Pirate.create!(catchphrase: "arr") - assert !pirate.catchphrase_changed? + assert_not_predicate pirate, :catchphrase_changed? assert pirate.catchphrase_will_change! - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? assert_equal ["arr", "arr"], pirate.catchphrase_change pirate.catchphrase << " matey!" - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? assert_equal ["arr", "arr matey!"], pirate.catchphrase_change end def test_virtual_attribute_will_change parrot = Parrot.create!(name: "Ruby") parrot.send(:attribute_will_change!, :cancel_save_from_callback) - assert parrot.has_changes_to_save? + assert_predicate parrot, :has_changes_to_save? end def test_association_assignment_changes_foreign_key pirate = Pirate.create!(catchphrase: "jarl") pirate.parrot = Parrot.create!(name: "Lorre") - assert pirate.changed? + assert_predicate pirate, :changed? assert_equal %w(parrot_id), pirate.changed end def test_attribute_should_be_compared_with_type_cast topic = Topic.new - assert topic.approved? - assert !topic.approved_changed? + assert_predicate topic, :approved? + assert_not_predicate topic, :approved_changed? # Coming from web form. params = { topic: { approved: 1 } } # In the controller. topic.attributes = params[:topic] - assert topic.approved? - assert !topic.approved_changed? + assert_predicate topic, :approved? + assert_not_predicate topic, :approved_changed? end def test_partial_update @@ -378,9 +378,9 @@ class DirtyTest < ActiveRecord::TestCase def test_reload_should_clear_changed_attributes pirate = Pirate.create!(catchphrase: "shiver me timbers") pirate.catchphrase = "*hic*" - assert pirate.changed? + assert_predicate pirate, :changed? pirate.reload - assert !pirate.changed? + assert_not_predicate pirate, :changed? end def test_dup_objects_should_not_copy_dirty_flag_from_creator @@ -388,17 +388,17 @@ class DirtyTest < ActiveRecord::TestCase pirate_dup = pirate.dup pirate_dup.restore_catchphrase! pirate.catchphrase = "I love Rum" - assert pirate.catchphrase_changed? - assert !pirate_dup.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? + assert_not_predicate pirate_dup, :catchphrase_changed? end def test_reverted_changes_are_not_dirty phrase = "shiver me timbers" pirate = Pirate.create!(catchphrase: phrase) pirate.catchphrase = "*hic*" - assert pirate.changed? + assert_predicate pirate, :changed? pirate.catchphrase = phrase - assert !pirate.changed? + assert_not_predicate pirate, :changed? end def test_reverted_changes_are_not_dirty_after_multiple_changes @@ -406,40 +406,40 @@ class DirtyTest < ActiveRecord::TestCase pirate = Pirate.create!(catchphrase: phrase) 10.times do |i| pirate.catchphrase = "*hic*" * i - assert pirate.changed? + assert_predicate pirate, :changed? end - assert pirate.changed? + assert_predicate pirate, :changed? pirate.catchphrase = phrase - assert !pirate.changed? + assert_not_predicate pirate, :changed? end def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back pirate = Pirate.create!(catchphrase: "Yar!") pirate.parrot_id = 1 - assert pirate.changed? - assert pirate.parrot_id_changed? - assert !pirate.catchphrase_changed? + assert_predicate pirate, :changed? + assert_predicate pirate, :parrot_id_changed? + assert_not_predicate pirate, :catchphrase_changed? pirate.parrot_id = nil - assert !pirate.changed? - assert !pirate.parrot_id_changed? - assert !pirate.catchphrase_changed? + assert_not_predicate pirate, :changed? + assert_not_predicate pirate, :parrot_id_changed? + assert_not_predicate pirate, :catchphrase_changed? end def test_save_should_store_serialized_attributes_even_with_partial_writes with_partial_writes(Topic) do topic = Topic.create!(content: { a: "a" }) - assert_not topic.changed? + assert_not_predicate topic, :changed? topic.content[:b] = "b" - assert topic.changed? + assert_predicate topic, :changed? topic.save! - assert_not topic.changed? + assert_not_predicate topic, :changed? assert_equal "b", topic.content[:b] topic.reload @@ -596,7 +596,7 @@ class DirtyTest < ActiveRecord::TestCase pirate = Pirate.create!(catchphrase: "rrrr", created_on: time_in_paris) pirate.created_on = pirate.created_on.in_time_zone("Tokyo").to_s - assert !pirate.created_on_changed? + assert_not_predicate pirate, :created_on_changed? end test "partial insert" do @@ -627,7 +627,7 @@ class DirtyTest < ActiveRecord::TestCase pirate = Pirate.create!(catchphrase: "arrrr") pirate.catchphrase << " matey!" - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? expected_changes = { "catchphrase" => ["arrrr", "arrrr matey!"] } @@ -641,7 +641,7 @@ class DirtyTest < ActiveRecord::TestCase pirate.reload assert_equal "arrrr matey!", pirate.catchphrase - assert_not pirate.changed? + assert_not_predicate pirate, :changed? end test "in place mutation for binary" do @@ -652,19 +652,19 @@ class DirtyTest < ActiveRecord::TestCase binary = klass.create!(data: "\\\\foo") - assert_not binary.changed? + assert_not_predicate binary, :changed? binary.data = binary.data.dup - assert_not binary.changed? + assert_not_predicate binary, :changed? binary = klass.last - assert_not binary.changed? + assert_not_predicate binary, :changed? binary.data << "bar" - assert binary.changed? + assert_predicate binary, :changed? end test "changes is correct for subclass" do @@ -679,7 +679,7 @@ class DirtyTest < ActiveRecord::TestCase new_catchphrase = "arrrr matey!" pirate.catchphrase = new_catchphrase - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? expected_changes = { "catchphrase" => ["arrrr", new_catchphrase] @@ -698,7 +698,7 @@ class DirtyTest < ActiveRecord::TestCase new_catchphrase = "arrrr matey!" pirate.catchphrase = new_catchphrase - assert pirate.catchphrase_changed? + assert_predicate pirate, :catchphrase_changed? expected_changes = { "catchphrase" => ["arrrr", new_catchphrase] @@ -720,7 +720,7 @@ class DirtyTest < ActiveRecord::TestCase end model = klass.new(first_name: "Jim") - assert model.first_name_changed? + assert_predicate model, :first_name_changed? end test "attribute_will_change! doesn't try to save non-persistable attributes" do @@ -732,7 +732,7 @@ class DirtyTest < ActiveRecord::TestCase record = klass.new(first_name: "Sean") record.non_persisted_attribute_will_change! - assert record.non_persisted_attribute_changed? + assert_predicate record, :non_persisted_attribute_changed? assert record.save end @@ -762,26 +762,33 @@ class DirtyTest < ActiveRecord::TestCase test "attributes assigned but not selected are dirty" do person = Person.select(:id).first - refute person.changed? + assert_not_predicate person, :changed? person.first_name = "Sean" - assert person.changed? + assert_predicate person, :changed? person.first_name = nil - assert person.changed? + assert_predicate person, :changed? + end + + test "attributes not selected are still missing after save" do + person = Person.select(:id).first + assert_raises(ActiveModel::MissingAttributeError) { person.first_name } + assert person.save # calls forget_attribute_assignments + assert_raises(ActiveModel::MissingAttributeError) { person.first_name } end test "saved_change_to_attribute? returns whether a change occurred in the last save" do person = Person.create!(first_name: "Sean") - assert person.saved_change_to_first_name? - refute person.saved_change_to_gender? + assert_predicate person, :saved_change_to_first_name? + assert_not_predicate person, :saved_change_to_gender? assert person.saved_change_to_first_name?(from: nil, to: "Sean") assert person.saved_change_to_first_name?(from: nil) assert person.saved_change_to_first_name?(to: "Sean") - refute person.saved_change_to_first_name?(from: "Jim", to: "Sean") - refute person.saved_change_to_first_name?(from: "Jim") - refute person.saved_change_to_first_name?(to: "Jim") + assert_not person.saved_change_to_first_name?(from: "Jim", to: "Sean") + assert_not person.saved_change_to_first_name?(from: "Jim") + assert_not person.saved_change_to_first_name?(to: "Jim") end test "saved_change_to_attribute returns the change that occurred in the last save" do @@ -816,11 +823,11 @@ class DirtyTest < ActiveRecord::TestCase test "saved_changes? returns whether the last call to save changed anything" do person = Person.create!(first_name: "Sean") - assert person.saved_changes? + assert_predicate person, :saved_changes? person.save - refute person.saved_changes? + assert_not_predicate person, :saved_changes? end test "saved_changes returns a hash of all the changes that occurred" do @@ -850,7 +857,7 @@ class DirtyTest < ActiveRecord::TestCase end person = klass.create!(first_name: "Sean") - refute person.changed? + assert_not_predicate person, :changed? end private @@ -863,8 +870,8 @@ class DirtyTest < ActiveRecord::TestCase end def check_pirate_after_save_failure(pirate) - assert pirate.changed? - assert pirate.parrot_id_changed? + assert_predicate pirate, :changed? + assert_predicate pirate, :parrot_id_changed? assert_equal %w(parrot_id), pirate.changed assert_nil pirate.parrot_id_was end diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 2fefdbf204..9e33c3110c 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -3,13 +3,14 @@ require "cases/helper" require "models/reply" require "models/topic" +require "models/movie" module ActiveRecord class DupTest < ActiveRecord::TestCase fixtures :topics def test_dup - assert !Topic.new.freeze.dup.frozen? + assert_not_predicate Topic.new.freeze.dup, :frozen? end def test_not_readonly @@ -40,7 +41,7 @@ module ActiveRecord topic.destroy duped = topic.dup - assert_not duped.destroyed? + assert_not_predicate duped, :destroyed? end def test_dup_has_no_id @@ -62,10 +63,10 @@ module ActiveRecord topic.attributes = dbtopic.attributes.except("id") - #duped has no timestamp values + # duped has no timestamp values duped = dbtopic.dup - #clear topic timestamp values + # clear topic timestamp values topic.send(:clear_timestamp_attributes) assert_equal topic.changes, duped.changes @@ -100,7 +101,7 @@ module ActiveRecord # temporary change to the topic object topic.updated_at -= 3.days - #dup should not preserve the timestamps if present + # dup should not preserve the timestamps if present new_topic = topic.dup assert_nil new_topic.updated_at assert_nil new_topic.created_at @@ -126,12 +127,12 @@ module ActiveRecord duped = topic.dup duped.title = nil - assert duped.invalid? + assert_predicate duped, :invalid? topic.title = nil duped.title = "Mathematics" - assert topic.invalid? - assert duped.valid? + assert_predicate topic, :invalid? + assert_predicate duped, :valid? end end @@ -157,5 +158,20 @@ module ActiveRecord record.dup end end + + def test_dup_record_not_persisted_after_rollback_transaction + movie = Movie.new(name: "test") + + assert_raises(ActiveRecord::RecordInvalid) do + Movie.transaction do + movie.save! + duped = movie.dup + duped.name = nil + duped.save! + end + end + + assert !movie.persisted? + end end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 7cda712112..d5a1d11e12 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -12,16 +12,16 @@ class EnumTest < ActiveRecord::TestCase end test "query state by predicate" do - assert @book.published? - assert_not @book.written? - assert_not @book.proposed? + assert_predicate @book, :published? + assert_not_predicate @book, :written? + assert_not_predicate @book, :proposed? - assert @book.read? - assert @book.in_english? - assert @book.author_visibility_visible? - assert @book.illustrator_visibility_visible? - assert @book.with_medium_font_size? - assert @book.medium_to_read? + assert_predicate @book, :read? + assert_predicate @book, :in_english? + assert_predicate @book, :author_visibility_visible? + assert_predicate @book, :illustrator_visibility_visible? + assert_predicate @book, :with_medium_font_size? + assert_predicate @book, :medium_to_read? end test "query state with strings" do @@ -76,46 +76,46 @@ class EnumTest < ActiveRecord::TestCase end test "build from scope" do - assert Book.written.build.written? - assert_not Book.written.build.proposed? + assert_predicate Book.written.build, :written? + assert_not_predicate Book.written.build, :proposed? end test "build from where" do - assert Book.where(status: Book.statuses[:written]).build.written? - assert_not Book.where(status: Book.statuses[:written]).build.proposed? - assert Book.where(status: :written).build.written? - assert_not Book.where(status: :written).build.proposed? - assert Book.where(status: "written").build.written? - assert_not Book.where(status: "written").build.proposed? + assert_predicate Book.where(status: Book.statuses[:written]).build, :written? + assert_not_predicate Book.where(status: Book.statuses[:written]).build, :proposed? + assert_predicate Book.where(status: :written).build, :written? + assert_not_predicate Book.where(status: :written).build, :proposed? + assert_predicate Book.where(status: "written").build, :written? + assert_not_predicate Book.where(status: "written").build, :proposed? end test "update by declaration" do @book.written! - assert @book.written? + assert_predicate @book, :written? @book.in_english! - assert @book.in_english? + assert_predicate @book, :in_english? @book.author_visibility_visible! - assert @book.author_visibility_visible? + assert_predicate @book, :author_visibility_visible? end test "update by setter" do @book.update! status: :written - assert @book.written? + assert_predicate @book, :written? end test "enum methods are overwritable" do assert_equal "do publish work...", @book.published! - assert @book.published? + assert_predicate @book, :published? end test "direct assignment" do @book.status = :written - assert @book.written? + assert_predicate @book, :written? end test "assign string value" do @book.status = "written" - assert @book.written? + assert_predicate @book, :written? end test "enum changed attributes" do @@ -242,17 +242,17 @@ class EnumTest < ActiveRecord::TestCase end test "building new objects with enum scopes" do - assert Book.written.build.written? - assert Book.read.build.read? - assert Book.in_spanish.build.in_spanish? - assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible? + assert_predicate Book.written.build, :written? + assert_predicate Book.read.build, :read? + assert_predicate Book.in_spanish.build, :in_spanish? + assert_predicate Book.illustrator_visibility_invisible.build, :illustrator_visibility_invisible? end test "creating new objects with enum scopes" do - assert Book.written.create.written? - assert Book.read.create.read? - assert Book.in_spanish.create.in_spanish? - assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible? + assert_predicate Book.written.create, :written? + assert_predicate Book.read.create, :read? + assert_predicate Book.in_spanish.create, :in_spanish? + assert_predicate Book.illustrator_visibility_invisible.create, :illustrator_visibility_invisible? end test "_before_type_cast" do @@ -355,9 +355,9 @@ class EnumTest < ActiveRecord::TestCase klass.delete_all klass.create!(status: "proposed") book = klass.new(status: "written") - assert book.valid? + assert_predicate book, :valid? book.status = "proposed" - assert_not book.valid? + assert_not_predicate book, :valid? end test "validate inclusion of value in array" do @@ -368,9 +368,9 @@ class EnumTest < ActiveRecord::TestCase end klass.delete_all invalid_book = klass.new(status: "proposed") - assert_not invalid_book.valid? + assert_not_predicate invalid_book, :valid? valid_book = klass.new(status: "written") - assert valid_book.valid? + assert_predicate valid_book, :valid? end test "enums are distinct per class" do @@ -417,10 +417,10 @@ class EnumTest < ActiveRecord::TestCase end book1 = klass.proposed.create! - assert book1.proposed? + assert_predicate book1, :proposed? book2 = klass.single.create! - assert book2.single? + assert_predicate book2, :single? end test "enum with alias_attribute" do @@ -431,62 +431,62 @@ class EnumTest < ActiveRecord::TestCase end book = klass.proposed.create! - assert book.proposed? + assert_predicate book, :proposed? assert_equal "proposed", book.aliased_status book = klass.find(book.id) - assert book.proposed? + assert_predicate book, :proposed? assert_equal "proposed", book.aliased_status end test "query state by predicate with prefix" do - assert @book.author_visibility_visible? - assert_not @book.author_visibility_invisible? - assert @book.illustrator_visibility_visible? - assert_not @book.illustrator_visibility_invisible? + assert_predicate @book, :author_visibility_visible? + assert_not_predicate @book, :author_visibility_invisible? + assert_predicate @book, :illustrator_visibility_visible? + assert_not_predicate @book, :illustrator_visibility_invisible? end test "query state by predicate with custom prefix" do - assert @book.in_english? - assert_not @book.in_spanish? - assert_not @book.in_french? + assert_predicate @book, :in_english? + assert_not_predicate @book, :in_spanish? + assert_not_predicate @book, :in_french? end test "query state by predicate with custom suffix" do - assert @book.medium_to_read? - assert_not @book.easy_to_read? - assert_not @book.hard_to_read? + assert_predicate @book, :medium_to_read? + assert_not_predicate @book, :easy_to_read? + assert_not_predicate @book, :hard_to_read? end test "enum methods with custom suffix defined" do - assert @book.class.respond_to?(:easy_to_read) - assert @book.class.respond_to?(:medium_to_read) - assert @book.class.respond_to?(:hard_to_read) + assert_respond_to @book.class, :easy_to_read + assert_respond_to @book.class, :medium_to_read + assert_respond_to @book.class, :hard_to_read - assert @book.respond_to?(:easy_to_read?) - assert @book.respond_to?(:medium_to_read?) - assert @book.respond_to?(:hard_to_read?) + assert_respond_to @book, :easy_to_read? + assert_respond_to @book, :medium_to_read? + assert_respond_to @book, :hard_to_read? - assert @book.respond_to?(:easy_to_read!) - assert @book.respond_to?(:medium_to_read!) - assert @book.respond_to?(:hard_to_read!) + assert_respond_to @book, :easy_to_read! + assert_respond_to @book, :medium_to_read! + assert_respond_to @book, :hard_to_read! end test "update enum attributes with custom suffix" do @book.medium_to_read! - assert_not @book.easy_to_read? - assert @book.medium_to_read? - assert_not @book.hard_to_read? + assert_not_predicate @book, :easy_to_read? + assert_predicate @book, :medium_to_read? + assert_not_predicate @book, :hard_to_read? @book.easy_to_read! - assert @book.easy_to_read? - assert_not @book.medium_to_read? - assert_not @book.hard_to_read? + assert_predicate @book, :easy_to_read? + assert_not_predicate @book, :medium_to_read? + assert_not_predicate @book, :hard_to_read? @book.hard_to_read! - assert_not @book.easy_to_read? - assert_not @book.medium_to_read? - assert @book.hard_to_read? + assert_not_predicate @book, :easy_to_read? + assert_not_predicate @book, :medium_to_read? + assert_predicate @book, :hard_to_read? end test "uses default status when no status is provided in fixtures" do @@ -497,12 +497,12 @@ class EnumTest < ActiveRecord::TestCase test "uses default value from database on initialization" do book = Book.new - assert book.proposed? + assert_predicate book, :proposed? end test "uses default value from database on initialization when using custom mapping" do book = Book.new - assert book.hard? + assert_predicate book, :hard? end test "data type of Enum type" do diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index fb698c47cd..82cc891970 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -15,20 +15,20 @@ if ActiveRecord::Base.connection.supports_explain? def test_collects_nothing_if_the_payload_has_an_exception SUBSCRIBER.finish(nil, nil, exception: Exception.new) - assert queries.empty? + assert_empty queries end def test_collects_nothing_for_ignored_payloads ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| SUBSCRIBER.finish(nil, nil, name: ip) end - assert queries.empty? + assert_empty queries end def test_collects_nothing_if_collect_is_false ActiveRecord::ExplainRegistry.collect = false SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select 1 from users", binds: [1, 2]) - assert queries.empty? + assert_empty queries end def test_collects_pairs_of_queries_and_binds @@ -42,12 +42,12 @@ if ActiveRecord::Base.connection.supports_explain? def test_collects_nothing_if_the_statement_is_not_whitelisted SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length") - assert queries.empty? + assert_empty queries end def test_collects_nothing_if_the_statement_is_only_partially_matched SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select_db yo_mama") - assert queries.empty? + assert_empty queries end def test_collects_cte_queries diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 4039af66d0..59af4e6961 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -8,7 +8,7 @@ class FinderRespondToTest < ActiveRecord::TestCase def test_should_preserve_normal_respond_to_behaviour_on_base assert_respond_to ActiveRecord::Base, :new - assert !ActiveRecord::Base.respond_to?(:find_by_something) + assert_not_respond_to ActiveRecord::Base, :find_by_something end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method @@ -43,14 +43,14 @@ class FinderRespondToTest < ActiveRecord::TestCase end def test_should_not_respond_to_find_by_one_missing_attribute - assert !Topic.respond_to?(:find_by_undertitle) + assert_not_respond_to Topic, :find_by_undertitle end def test_should_not_respond_to_find_by_invalid_method_syntax - assert !Topic.respond_to?(:fail_to_find_by_title) - assert !Topic.respond_to?(:find_by_title?) - assert !Topic.respond_to?(:fail_to_find_or_create_by_title) - assert !Topic.respond_to?(:find_or_create_by_title?) + assert_not_respond_to Topic, :fail_to_find_by_title + assert_not_respond_to Topic, :find_by_title? + assert_not_respond_to Topic, :fail_to_find_or_create_by_title + assert_not_respond_to Topic, :find_or_create_by_title? end private diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e936c56ab8..b413212e26 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -270,27 +270,27 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_with_includes_limit_and_empty_result - assert_equal false, Topic.includes(:replies).limit(0).exists? - assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? + assert_no_queries { assert_equal false, Topic.includes(:replies).limit(0).exists? } + assert_queries(1) { assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? } end def test_exists_with_distinct_association_includes_and_limit author = Author.first - assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists? - assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists? + unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments) + assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? } + assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? } end def test_exists_with_distinct_association_includes_limit_and_order author = Author.first - assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists? - assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists? + unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC") + assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? } + assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? } end def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association - assert_nothing_raised do - developer = developers(:david) - developer.ratings.includes(comment: :post).where(posts: { id: 1 }).exists? - end + ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 }) + assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? } end def test_exists_with_empty_table_and_no_args_given @@ -565,7 +565,7 @@ class FinderTest < ActiveRecord::TestCase assert_nil Topic.offset(4).second_to_last assert_nil Topic.offset(5).second_to_last - #test with limit + # test with limit assert_nil Topic.limit(1).second assert_nil Topic.limit(1).second_to_last end @@ -648,13 +648,13 @@ class FinderTest < ActiveRecord::TestCase def test_last_with_integer_and_order_should_use_sql_limit relation = Topic.order("title") assert_queries(1) { relation.last(5) } - assert !relation.loaded? + assert_not_predicate relation, :loaded? end def test_last_with_integer_and_reorder_should_use_sql_limit relation = Topic.reorder("title") assert_queries(1) { relation.last(5) } - assert !relation.loaded? + assert_not_predicate relation, :loaded? end def test_last_on_loaded_relation_should_not_use_sql @@ -679,12 +679,34 @@ class FinderTest < ActiveRecord::TestCase assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2) assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3) + assert_equal comments.offset(2).to_a.last, comments.offset(2).last + assert_equal comments.offset(2).to_a.last(2), comments.offset(2).last(2) + assert_equal comments.offset(2).to_a.last(3), comments.offset(2).last(3) + comments = comments.offset(1) assert_equal comments.limit(2).to_a.last, comments.limit(2).last assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2) assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3) end + def test_first_on_relation_with_limit_and_offset + post = posts("sti_comments") + + comments = post.comments.order(id: :asc) + assert_equal comments.limit(2).to_a.first, comments.limit(2).first + assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2) + assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3) + + assert_equal comments.offset(2).to_a.first, comments.offset(2).first + assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2) + assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3) + + comments = comments.offset(1) + assert_equal comments.limit(2).to_a.first, comments.limit(2).first + assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2) + assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3) + end + def test_take_and_first_and_last_with_integer_should_return_an_array assert_kind_of Array, Topic.take(5) assert_kind_of Array, Topic.first(5) @@ -846,6 +868,25 @@ class FinderTest < ActiveRecord::TestCase assert_equal customers(:david), found_customer end + def test_hash_condition_find_with_aggregate_having_three_mappings_array + david_address = customers(:david).address + zaphod_address = customers(:zaphod).address + barney_address = customers(:barney).address + assert_kind_of Address, david_address + assert_kind_of Address, zaphod_address + found_customers = Customer.where(address: [david_address, zaphod_address, barney_address]) + assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id) + end + + def test_hash_condition_find_with_aggregate_having_one_mapping_array + david_balance = customers(:david).balance + zaphod_balance = customers(:zaphod).balance + assert_kind_of Money, david_balance + assert_kind_of Money, zaphod_balance + found_customers = Customer.where(balance: [david_balance, zaphod_balance]) + assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id) + end + def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate gps_location = customers(:david).gps_location assert_kind_of GpsLocation, gps_location @@ -1051,14 +1092,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") } end - def test_find_last_with_offset - devs = Developer.order("id") - - assert_equal devs[2], Developer.offset(2).first - assert_equal devs[-3], Developer.offset(2).last - assert_equal devs[-3], Developer.offset(2).order("id DESC").first - end - def test_find_by_nil_attribute topic = Topic.find_by_last_read nil assert_not_nil topic @@ -1238,7 +1271,7 @@ class FinderTest < ActiveRecord::TestCase test "find_by with associations" do assert_equal authors(:david), Post.find_by(author: authors(:david)).author - assert_equal authors(:mary) , Post.find_by(author: authors(:mary)).author + assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author end test "find_by doesn't have implicit ordering" do @@ -1303,12 +1336,12 @@ class FinderTest < ActiveRecord::TestCase test "#skip_query_cache! for #exists? with a limited eager load" do Topic.cache do - assert_queries(2) do + assert_queries(1) do Topic.eager_load(:replies).limit(1).exists? Topic.eager_load(:replies).limit(1).exists? end - assert_queries(4) do + assert_queries(2) do Topic.eager_load(:replies).limit(1).skip_query_cache!.exists? Topic.eager_load(:replies).limit(1).skip_query_cache!.exists? end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index b0b63f5203..c88b90c8eb 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -79,6 +79,151 @@ class FixturesTest < ActiveRecord::TestCase ActiveSupport::Notifications.unsubscribe(subscription) end end + + def test_bulk_insert_multiple_table_with_a_multi_statement_query + subscriber = InsertQuerySubscriber.new + subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber) + + create_fixtures("bulbs", "authors", "computers") + + expected_sql = <<-EOS.strip_heredoc.chop + INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("bulbs")} .* + INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("authors")} .* + INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("computers")} .* + EOS + assert_equal 1, subscriber.events.size + assert_match(/#{expected_sql}/, subscriber.events.first) + ensure + ActiveSupport::Notifications.unsubscribe(subscription) + end + + def test_bulk_insert_with_a_multi_statement_query_raises_an_exception_when_any_insert_fails + require "models/aircraft" + + assert_equal false, Aircraft.columns_hash["wheels_count"].null + fixtures = { + "aircraft" => [ + { "name" => "working_aircrafts", "wheels_count" => 2 }, + { "name" => "broken_aircrafts", "wheels_count" => nil }, + ] + } + + assert_no_difference "Aircraft.count" do + assert_raises(ActiveRecord::NotNullViolation) do + ActiveRecord::Base.connection.insert_fixtures_set(fixtures) + end + end + end + end + + if current_adapter?(:Mysql2Adapter) + def test_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size + conn = ActiveRecord::Base.connection + mysql_margin = 2 + packet_size = 1024 + bytes_needed_to_have_a_1024_bytes_fixture = 858 + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a" * bytes_needed_to_have_a_1024_bytes_fixture] }, + ] + } + + conn.stubs(:max_allowed_packet).returns(packet_size - mysql_margin) + + error = assert_raises(ActiveRecord::ActiveRecordError) { conn.insert_fixtures_set(fixtures) } + assert_match(/Fixtures set is too large #{packet_size}\./, error.message) + end + + def test_insert_fixture_set_when_max_allowed_packet_is_bigger_than_fixtures_set_size + conn = ActiveRecord::Base.connection + packet_size = 1024 + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 51] }, + ] + } + + conn.stubs(:max_allowed_packet).returns(packet_size) + + assert_difference "TrafficLight.count" do + conn.insert_fixtures_set(fixtures) + end + end + + def test_insert_fixtures_set_split_the_total_sql_into_two_chunks_smaller_than_max_allowed_packet + subscriber = InsertQuerySubscriber.new + subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber) + conn = ActiveRecord::Base.connection + packet_size = 1024 + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 450] }, + ], + "comments" => [ + { "post_id" => 1, "body" => "a" * 450 }, + ] + } + + conn.stubs(:max_allowed_packet).returns(packet_size) + + conn.insert_fixtures_set(fixtures) + assert_equal 2, subscriber.events.size + assert_operator subscriber.events.first.bytesize, :<, packet_size + assert_operator subscriber.events.second.bytesize, :<, packet_size + ensure + ActiveSupport::Notifications.unsubscribe(subscription) + end + + def test_insert_fixtures_set_concat_total_sql_into_a_single_packet_smaller_than_max_allowed_packet + subscriber = InsertQuerySubscriber.new + subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber) + conn = ActiveRecord::Base.connection + packet_size = 1024 + fixtures = { + "traffic_lights" => [ + { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 200] }, + ], + "comments" => [ + { "post_id" => 1, "body" => "a" * 200 }, + ] + } + + conn.stubs(:max_allowed_packet).returns(packet_size) + + assert_difference ["TrafficLight.count", "Comment.count"], +1 do + conn.insert_fixtures_set(fixtures) + end + assert_equal 1, subscriber.events.size + ensure + ActiveSupport::Notifications.unsubscribe(subscription) + end + end + + def test_auto_value_on_primary_key + fixtures = [ + { "name" => "first", "wheels_count" => 2 }, + { "name" => "second", "wheels_count" => 3 } + ] + conn = ActiveRecord::Base.connection + assert_nothing_raised do + conn.insert_fixtures_set({ "aircraft" => fixtures }, ["aircraft"]) + end + result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id") + assert_equal fixtures, result.to_a + end + + def test_deprecated_insert_fixtures + fixtures = [ + { "name" => "first", "wheels_count" => 2 }, + { "name" => "second", "wheels_count" => 3 } + ] + conn = ActiveRecord::Base.connection + conn.delete("DELETE FROM aircraft") + assert_deprecated do + conn.insert_fixtures(fixtures, "aircraft") + end + result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id") + assert_equal fixtures, result.to_a end def test_broken_yaml_exception @@ -247,8 +392,8 @@ class FixturesTest < ActiveRecord::TestCase def test_nonexistent_fixture_file nonexistent_fixture_path = FIXTURES_ROOT + "/imnothere" - #sanity check to make sure that this file never exists - assert Dir[nonexistent_fixture_path + "*"].empty? + # sanity check to make sure that this file never exists + assert_empty Dir[nonexistent_fixture_path + "*"] assert_raise(Errno::ENOENT) do ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path) @@ -998,10 +1143,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase end def test_resolves_enums - assert books(:awdr).published? - assert books(:awdr).read? - assert books(:rfr).proposed? - assert books(:ddd).published? + assert_predicate books(:awdr), :published? + assert_predicate books(:awdr), :read? + assert_predicate books(:rfr), :proposed? + assert_predicate books(:ddd), :published? end end diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb index 5e503272e1..b15e1b48c4 100644 --- a/activerecord/test/cases/habtm_destroy_order_test.rb +++ b/activerecord/test/cases/habtm_destroy_order_test.rb @@ -15,7 +15,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase sicp.destroy end end - assert !sicp.destroyed? + assert_not_predicate sicp, :destroyed? end test "should not raise error if have foreign key in the join table" do @@ -42,7 +42,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase ben.lessons << sicp ben.save! ben.destroy - assert !ben.reload.lessons.empty? + assert_not_empty ben.reload.lessons ensure # get rid of it so Student is still like it was Student.reset_callbacks(:destroy) @@ -58,6 +58,6 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase assert_raises LessonError do sicp.destroy end - assert !sicp.reload.students.empty? + assert_not_empty sicp.reload.students end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index c931f7d21c..e3ca79af99 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -130,46 +130,46 @@ class InheritanceTest < ActiveRecord::TestCase end def test_descends_from_active_record - assert !ActiveRecord::Base.descends_from_active_record? + assert_not_predicate ActiveRecord::Base, :descends_from_active_record? # Abstract subclass of AR::Base. - assert LoosePerson.descends_from_active_record? + assert_predicate LoosePerson, :descends_from_active_record? # Concrete subclass of an abstract class. - assert LooseDescendant.descends_from_active_record? + assert_predicate LooseDescendant, :descends_from_active_record? # Concrete subclass of AR::Base. - assert TightPerson.descends_from_active_record? + assert_predicate TightPerson, :descends_from_active_record? # Concrete subclass of a concrete class but has no type column. - assert TightDescendant.descends_from_active_record? + assert_predicate TightDescendant, :descends_from_active_record? # Concrete subclass of AR::Base. - assert Post.descends_from_active_record? + assert_predicate Post, :descends_from_active_record? # Concrete subclasses of a concrete class which has a type column. - assert !StiPost.descends_from_active_record? - assert !SubStiPost.descends_from_active_record? + assert_not_predicate StiPost, :descends_from_active_record? + assert_not_predicate SubStiPost, :descends_from_active_record? # Abstract subclass of a concrete class which has a type column. # This is pathological, as you'll never have Sub < Abstract < Concrete. - assert !AbstractStiPost.descends_from_active_record? + assert_not_predicate AbstractStiPost, :descends_from_active_record? # Concrete subclass of an abstract class which has a type column. - assert !SubAbstractStiPost.descends_from_active_record? + assert_not_predicate SubAbstractStiPost, :descends_from_active_record? end def test_company_descends_from_active_record - assert !ActiveRecord::Base.descends_from_active_record? + assert_not_predicate ActiveRecord::Base, :descends_from_active_record? assert AbstractCompany.descends_from_active_record?, "AbstractCompany should descend from ActiveRecord::Base" assert Company.descends_from_active_record?, "Company should descend from ActiveRecord::Base" assert !Class.new(Company).descends_from_active_record?, "Company subclass should not descend from ActiveRecord::Base" end def test_abstract_class - assert !ActiveRecord::Base.abstract_class? - assert LoosePerson.abstract_class? - assert !LooseDescendant.abstract_class? + assert_not_predicate ActiveRecord::Base, :abstract_class? + assert_predicate LoosePerson, :abstract_class? + assert_not_predicate LooseDescendant, :abstract_class? end def test_inheritance_base_class @@ -280,6 +280,21 @@ class InheritanceTest < ActiveRecord::TestCase assert_equal Firm, firm.class end + def test_where_new_with_subclass + firm = Company.where(type: "Firm").new + assert_equal Firm, firm.class + end + + def test_where_create_with_subclass + firm = Company.where(type: "Firm").create(name: "Basecamp") + assert_equal Firm, firm.class + end + + def test_where_create_bang_with_subclass + firm = Company.where(type: "Firm").create!(name: "Basecamp") + assert_equal Firm, firm.class + end + def test_new_with_abstract_class e = assert_raises(NotImplementedError) do AbstractCompany.new @@ -302,6 +317,30 @@ class InheritanceTest < ActiveRecord::TestCase assert_raise(ActiveRecord::SubclassNotFound) { Company.new(type: "Account") } end + def test_where_new_with_invalid_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").new } + end + + def test_where_new_with_unrelated_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").new } + end + + def test_where_create_with_invalid_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create } + end + + def test_where_create_with_unrelated_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create } + end + + def test_where_create_bang_with_invalid_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create! } + end + + def test_where_create_bang_with_unrelated_type + assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create! } + end + def test_new_with_unrelated_namespaced_type without_store_full_sti_class do e = assert_raises ActiveRecord::SubclassNotFound do diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb index 63f3c77fc3..afc39d0420 100644 --- a/activerecord/test/cases/json_attribute_test.rb +++ b/activerecord/test/cases/json_attribute_test.rb @@ -19,14 +19,14 @@ class JsonAttributeTest < ActiveRecord::TestCase def setup super @connection.create_table("json_data_type") do |t| - t.text "payload" - t.text "settings" + t.string "payload" + t.string "settings" end end private def column_type - :text + :string end def klass diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 52fe488cd5..82cf281cff 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -252,7 +252,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase def @david.favorite_quote; "Constraints are liberating"; end json = @david.to_json(include: :posts, methods: :favorite_quote) - assert !@david.posts.first.respond_to?(:favorite_quote) + assert_not_respond_to @david.posts.first, :favorite_quote assert_match %r{"favorite_quote":"Constraints are liberating"}, json assert_equal 1, %r{"favorite_quote":}.match(json).size end diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb index 56ec8c8a82..c60a276850 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -23,10 +23,10 @@ module JSONSharedTestCases def test_column column = klass.columns_hash["payload"] assert_equal column_type, column.type - assert_equal column_type.to_s, column.sql_type + assert_type_match column_type, column.sql_type type = klass.type_for_attribute("payload") - assert_not type.binary? + assert_not_predicate type, :binary? end def test_change_table_supports_json @@ -36,7 +36,7 @@ module JSONSharedTestCases klass.reset_column_information column = klass.columns_hash["users"] assert_equal column_type, column.type - assert_equal column_type.to_s, column.sql_type + assert_type_match column_type, column.sql_type end def test_schema_dumping @@ -66,26 +66,26 @@ module JSONSharedTestCases end def test_rewrite - @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|) + @connection.execute(insert_statement_per_database('{"k":"v"}')) x = klass.first x.payload = { '"a\'' => "b" } assert x.save! end def test_select - @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|) + @connection.execute(insert_statement_per_database('{"k":"v"}')) x = klass.first assert_equal({ "k" => "v" }, x.payload) end def test_select_multikey - @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|) + @connection.execute(insert_statement_per_database('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')) x = klass.first assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload) end def test_null_json - @connection.execute("insert into json_data_type (payload) VALUES(null)") + @connection.execute(insert_statement_per_database("null")) x = klass.first assert_nil(x.payload) end @@ -107,13 +107,13 @@ module JSONSharedTestCases end def test_select_array_json_value - @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|) + @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]')) x = klass.first assert_equal(["v0", { "k1" => "v1" }], x.payload) end def test_rewrite_array_json_value - @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|) + @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]')) x = klass.first x.payload = ["v1", { "k2" => "v2" }, "v3"] assert x.save! @@ -152,42 +152,42 @@ module JSONSharedTestCases def test_changes_in_place json = klass.new - assert_not json.changed? + assert_not_predicate json, :changed? json.payload = { "one" => "two" } - assert json.changed? - assert json.payload_changed? + assert_predicate json, :changed? + assert_predicate json, :payload_changed? json.save! - assert_not json.changed? + assert_not_predicate json, :changed? json.payload["three"] = "four" - assert json.payload_changed? + assert_predicate json, :payload_changed? json.save! json.reload assert_equal({ "one" => "two", "three" => "four" }, json.payload) - assert_not json.changed? + assert_not_predicate json, :changed? end def test_changes_in_place_ignores_key_order json = klass.new - assert_not json.changed? + assert_not_predicate json, :changed? json.payload = { "three" => "four", "one" => "two" } json.save! json.reload json.payload = { "three" => "four", "one" => "two" } - assert_not json.changed? + assert_not_predicate json, :changed? json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }] json.save! json.reload json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }] - assert_not json.changed? + assert_not_predicate json, :changed? end def test_changes_in_place_with_ruby_object @@ -195,10 +195,10 @@ module JSONSharedTestCases json = klass.create!(payload: time) json.reload - assert_not json.changed? + assert_not_predicate json, :changed? json.payload = time - assert_not json.changed? + assert_not_predicate json, :changed? end def test_assigning_string_literal @@ -253,4 +253,17 @@ module JSONSharedTestCases def klass JsonDataType end + + def assert_type_match(type, sql_type) + native_type = ActiveRecord::Base.connection.native_database_types[type][:name] + assert_match %r(\A#{native_type}\b), sql_type + end + + def insert_statement_per_database(values) + if current_adapter?(:OracleAdapter) + "insert into json_data_type (id, payload) VALUES (json_data_type_seq.nextval, '#{values}')" + else + "insert into json_data_type (payload) VALUES ('#{values}')" + end + end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e857180bd1..bb76137ef5 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -69,8 +69,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { s2.destroy } assert s1.destroy - assert s1.frozen? - assert s1.destroyed? + assert_predicate s1, :frozen? + assert_predicate s1, :destroyed? assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") } end @@ -104,8 +104,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } assert p1.destroy - assert p1.frozen? - assert p1.destroyed? + assert_predicate p1, :frozen? + assert_predicate p1, :destroyed? assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } end @@ -201,7 +201,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase person.first_name = "Douglas Adams" person.lock_version = 42 - assert person.lock_version_changed? + assert_predicate person, :lock_version_changed? person.save end @@ -399,25 +399,57 @@ class OptimisticLockingTest < ActiveRecord::TestCase end end + def test_counter_cache_with_touch_and_lock_version + car = Car.create! + + assert_equal 0, car.wheels_count + assert_equal 0, car.lock_version + + previously_car_updated_at = car.updated_at + travel(2.second) do + Wheel.create!(wheelable: car) + end + + assert_equal 1, car.reload.wheels_count + assert_not_equal previously_car_updated_at, car.updated_at + assert_equal 1, car.lock_version + + previously_car_updated_at = car.updated_at + car.wheels.first.update(size: 42) + + assert_equal 1, car.reload.wheels_count + assert_not_equal previously_car_updated_at, car.updated_at + assert_equal 2, car.lock_version + + previously_car_updated_at = car.updated_at + travel(2.second) do + car.wheels.first.destroy! + end + + assert_equal 0, car.reload.wheels_count + assert_not_equal previously_car_updated_at, car.updated_at + assert_equal 3, car.lock_version + end + def test_polymorphic_destroy_with_dependencies_and_lock_version car = Car.create! assert_difference "car.wheels.count" do - car.wheels << Wheel.create! + car.wheels.create end assert_difference "car.wheels.count", -1 do car.reload.destroy end - assert car.destroyed? + assert_predicate car, :destroyed? end def test_removing_has_and_belongs_to_many_associations_upon_destroy p = RichPerson.create! first_name: "Jon" p.treasures.create! - assert !p.treasures.empty? + assert_not_empty p.treasures p.destroy - assert p.treasures.empty? - assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty? + assert_empty p.treasures + assert_empty RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1") end def test_yaml_dumping_with_lock_column @@ -487,7 +519,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase t1.destroy - assert t1.destroyed? + assert_predicate t1, :destroyed? end def test_destroy_stale_object @@ -500,7 +532,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase stale_object.destroy! end - refute stale_object.destroyed? + assert_not_predicate stale_object, :destroyed? end private diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 208e54ed0b..e2742ed33e 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -33,8 +33,9 @@ class LogSubscriberTest < ActiveRecord::TestCase super end - def debug(message) - @debugs << message + def debug(progname = nil, &block) + @debugs << progname + super end end @@ -171,6 +172,22 @@ class LogSubscriberTest < ActiveRecord::TestCase assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last) end + def test_vebose_query_logs + ActiveRecord::Base.verbose_query_logs = true + + logger = TestDebugLogSubscriber.new + logger.sql(Event.new(0, sql: "hi mom!")) + assert_match(/↳/, @logger.logged(:debug).last) + ensure + ActiveRecord::Base.verbose_query_logs = false + end + + def test_verbose_query_logs_disabled_by_default + logger = TestDebugLogSubscriber.new + logger.sql(Event.new(0, sql: "hi mom!")) + assert_no_match(/↳/, @logger.logged(:debug).last) + end + def test_cached_queries ActiveRecord::Base.cache do Developer.all.load diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 38a906c8f5..1494027182 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -84,7 +84,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array? + assert_predicate array_column, :array? end def test_create_table_with_array_column @@ -95,7 +95,7 @@ module ActiveRecord columns = connection.columns(:testings) array_column = columns.detect { |c| c.name == "foo" } - assert array_column.array? + assert_predicate array_column, :array? end end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index be6dc2acb1..3022121f4c 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -80,7 +80,7 @@ module ActiveRecord TestModel.delete_all # Now use the Rails insertion - TestModel.create wealth: BigDecimal.new("12345678901234567890.0123456789") + TestModel.create wealth: BigDecimal("12345678901234567890.0123456789") # SELECT row = TestModel.first @@ -146,7 +146,7 @@ module ActiveRecord TestModel.create first_name: "bob", last_name: "bobsen", bio: "I was born ....", age: 18, height: 1.78, - wealth: BigDecimal.new("12345678901234567890.0123456789"), + wealth: BigDecimal("12345678901234567890.0123456789"), birthday: 18.years.ago, favorite_day: 10.days.ago, moment_of_truth: "1782-10-10 21:40:18", male: true @@ -159,7 +159,7 @@ module ActiveRecord # Test for 30 significant digits (beyond the 16 of float), 10 of them # after the decimal place. - assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth + assert_equal BigDecimal("0012345678901234567890.0123456789"), bob.wealth assert_equal true, bob.male? diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 8ca20b6172..cedd9c44e3 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -67,7 +67,7 @@ module ActiveRecord if current_adapter?(:Mysql2Adapter) def test_mysql_rename_column_preserves_auto_increment rename_column "test_models", "id", "id_test" - assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment? + assert_predicate connection.columns("test_models").find { |c| c.name == "id_test" }, :auto_increment? TestModel.reset_column_information ensure rename_column "test_models", "id_test", "id" @@ -223,31 +223,31 @@ module ActiveRecord def test_change_column_with_nil_default add_column "test_models", "contributor", :boolean, default: true - assert TestModel.new.contributor? + assert_predicate TestModel.new, :contributor? change_column "test_models", "contributor", :boolean, default: nil TestModel.reset_column_information - assert_not TestModel.new.contributor? + assert_not_predicate TestModel.new, :contributor? assert_nil TestModel.new.contributor end def test_change_column_to_drop_default_with_null_false add_column "test_models", "contributor", :boolean, default: true, null: false - assert TestModel.new.contributor? + assert_predicate TestModel.new, :contributor? change_column "test_models", "contributor", :boolean, default: nil, null: false TestModel.reset_column_information - assert_not TestModel.new.contributor? + assert_not_predicate TestModel.new, :contributor? assert_nil TestModel.new.contributor end def test_change_column_with_new_default add_column "test_models", "administrator", :boolean, default: true - assert TestModel.new.administrator? + assert_predicate TestModel.new, :administrator? change_column "test_models", "administrator", :boolean, default: false TestModel.reset_column_information - assert_not TestModel.new.administrator? + assert_not_predicate TestModel.new, :administrator? end def test_change_column_with_custom_index_name diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 58bc558619..3a11bb081b 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -14,7 +14,7 @@ module ActiveRecord recorder = CommandRecorder.new(Class.new { def america; end }.new) - assert recorder.respond_to?(:america) + assert_respond_to recorder, :america end def test_send_calls_super @@ -27,7 +27,7 @@ module ActiveRecord recorder = CommandRecorder.new(Class.new { def create_table(name); end }.new) - assert recorder.respond_to?(:create_table), "respond_to? create_table" + assert_respond_to recorder, :create_table recorder.send(:create_table, :horses) assert_equal [[:create_table, [:horses], nil]], recorder.commands end diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index cc2391f349..69a50674af 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -179,12 +179,10 @@ module LegacyPrimaryKeyTestCases @migration.migrate(:up) - legacy_pk = LegacyPrimaryKey.columns_hash["id"] - assert_not legacy_pk.bigint? - assert_not legacy_pk.null + assert_legacy_primary_key legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"] - assert_not legacy_ref.bigint? + assert_not_predicate legacy_ref, :bigint? record1 = LegacyPrimaryKey.create! assert_not_nil record1.id @@ -216,55 +214,50 @@ module LegacyPrimaryKeyTestCases assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema end - if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - def test_legacy_primary_key_in_create_table_should_be_integer - @migration = Class.new(migration_class) { - def change - create_table :legacy_primary_keys, id: false do |t| - t.primary_key :id - end + def test_legacy_primary_key_in_create_table_should_be_integer + @migration = Class.new(migration_class) { + def change + create_table :legacy_primary_keys, id: false do |t| + t.primary_key :id end - }.new + end + }.new - @migration.migrate(:up) + @migration.migrate(:up) - schema = dump_table_schema "legacy_primary_keys" - assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema - end + assert_legacy_primary_key + end - def test_legacy_primary_key_in_change_table_should_be_integer - @migration = Class.new(migration_class) { - def change - create_table :legacy_primary_keys, id: false do |t| - t.integer :dummy - end - change_table :legacy_primary_keys do |t| - t.primary_key :id - end + def test_legacy_primary_key_in_change_table_should_be_integer + @migration = Class.new(migration_class) { + def change + create_table :legacy_primary_keys, id: false do |t| + t.integer :dummy end - }.new + change_table :legacy_primary_keys do |t| + t.primary_key :id + end + end + }.new - @migration.migrate(:up) + @migration.migrate(:up) - schema = dump_table_schema "legacy_primary_keys" - assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema - end + assert_legacy_primary_key + end - def test_add_column_with_legacy_primary_key_should_be_integer - @migration = Class.new(migration_class) { - def change - create_table :legacy_primary_keys, id: false do |t| - t.integer :dummy - end - add_column :legacy_primary_keys, :id, :primary_key + def test_add_column_with_legacy_primary_key_should_be_integer + @migration = Class.new(migration_class) { + def change + create_table :legacy_primary_keys, id: false do |t| + t.integer :dummy end - }.new + add_column :legacy_primary_keys, :id, :primary_key + end + }.new - @migration.migrate(:up) + @migration.migrate(:up) - schema = dump_table_schema "legacy_primary_keys" - assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema - end + assert_legacy_primary_key end def test_legacy_join_table_foreign_keys_should_be_integer @@ -308,8 +301,8 @@ module LegacyPrimaryKeyTestCases @migration.migrate(:up) legacy_pk = LegacyPrimaryKey.columns_hash["id"] - assert legacy_pk.bigint? - assert legacy_pk.auto_increment? + assert_predicate legacy_pk, :bigint? + assert_predicate legacy_pk, :auto_increment? schema = dump_table_schema "legacy_primary_keys" assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema @@ -333,6 +326,22 @@ module LegacyPrimaryKeyTestCases assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema end end + + private + def assert_legacy_primary_key + assert_equal "id", LegacyPrimaryKey.primary_key + + legacy_pk = LegacyPrimaryKey.columns_hash["id"] + + assert_equal :integer, legacy_pk.type + assert_not_predicate legacy_pk, :bigint? + assert_not legacy_pk.null + + if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) + schema = dump_table_schema "legacy_primary_keys" + assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema + end + end end module LegacyPrimaryKeyTest diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index 77d32a24a5..83fb4f9385 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -69,7 +69,7 @@ module ActiveRecord def test_create_join_table_without_indexes connection.create_join_table :artists, :musics - assert connection.indexes(:artists_musics).blank? + assert_predicate connection.indexes(:artists_musics), :blank? end def test_create_join_table_with_index diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 079be04946..de37215e80 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -235,39 +235,39 @@ if ActiveRecord::Base.connection.supports_foreign_keys? assert_equal 1, foreign_keys.size fk = foreign_keys.first - refute fk.validated? + assert_not_predicate fk, :validated? end def test_validate_foreign_key_infers_column @connection.add_foreign_key :astronauts, :rockets, validate: false - refute @connection.foreign_keys("astronauts").first.validated? + assert_not_predicate @connection.foreign_keys("astronauts").first, :validated? @connection.validate_foreign_key :astronauts, :rockets - assert @connection.foreign_keys("astronauts").first.validated? + assert_predicate @connection.foreign_keys("astronauts").first, :validated? end def test_validate_foreign_key_by_column @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false - refute @connection.foreign_keys("astronauts").first.validated? + assert_not_predicate @connection.foreign_keys("astronauts").first, :validated? @connection.validate_foreign_key :astronauts, column: "rocket_id" - assert @connection.foreign_keys("astronauts").first.validated? + assert_predicate @connection.foreign_keys("astronauts").first, :validated? end def test_validate_foreign_key_by_symbol_column @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id, validate: false - refute @connection.foreign_keys("astronauts").first.validated? + assert_not_predicate @connection.foreign_keys("astronauts").first, :validated? @connection.validate_foreign_key :astronauts, column: :rocket_id - assert @connection.foreign_keys("astronauts").first.validated? + assert_predicate @connection.foreign_keys("astronauts").first, :validated? end def test_validate_foreign_key_by_name @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false - refute @connection.foreign_keys("astronauts").first.validated? + assert_not_predicate @connection.foreign_keys("astronauts").first, :validated? @connection.validate_foreign_key :astronauts, name: "fancy_named_fk" - assert @connection.foreign_keys("astronauts").first.validated? + assert_predicate @connection.foreign_keys("astronauts").first, :validated? end def test_validate_foreign_non_existing_foreign_key_raises @@ -280,7 +280,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false @connection.validate_constraint :astronauts, "fancy_named_fk" - assert @connection.foreign_keys("astronauts").first.validated? + assert_predicate @connection.foreign_keys("astronauts").first, :validated? end else # Foreign key should still be created, but should not be invalid @@ -291,7 +291,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? assert_equal 1, foreign_keys.size fk = foreign_keys.first - assert fk.validated? + assert_predicate fk, :validated? end end diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb index d0066f68be..dedb5ea502 100644 --- a/activerecord/test/cases/migration/pending_migrations_test.rb +++ b/activerecord/test/cases/migration/pending_migrations_test.rb @@ -4,37 +4,37 @@ require "cases/helper" module ActiveRecord class Migration - class PendingMigrationsTest < ActiveRecord::TestCase - def setup - super - @connection = Minitest::Mock.new - @app = Minitest::Mock.new - conn = @connection - @pending = Class.new(CheckPending) { - define_method(:connection) { conn } - }.new(@app) - @pending.instance_variable_set :@last_check, -1 # Force checking - end + if current_adapter?(:SQLite3Adapter) && !in_memory_db? + class PendingMigrationsTest < ActiveRecord::TestCase + setup do + file = ActiveRecord::Base.connection.raw_connection.filename + @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:", migrations_paths: MIGRATIONS_ROOT + "/valid" + source_db = SQLite3::Database.new file + dest_db = ActiveRecord::Base.connection.raw_connection + backup = SQLite3::Backup.new(dest_db, "main", source_db, "main") + backup.step(-1) + backup.finish + end - def teardown - assert @connection.verify - assert @app.verify - super - end + teardown do + @conn.release_connection if @conn + ActiveRecord::Base.establish_connection :arunit + end + + def test_errors_if_pending + ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true - def test_errors_if_pending - ActiveRecord::Migrator.stub :needs_migration?, true do - assert_raise ActiveRecord::PendingMigrationError do - @pending.call(nil) + assert_raises ActiveRecord::PendingMigrationError do + CheckPending.new(Proc.new {}).call({}) end end - end - def test_checks_if_supported - @app.expect :call, nil, [:foo] + def test_checks_if_supported + ActiveRecord::SchemaMigration.create_table + migrator = Base.connection.migration_context + capture(:stdout) { migrator.migrate } - ActiveRecord::Migrator.stub :needs_migration?, false do - @pending.call(:foo) + assert_nil CheckPending.new(Proc.new {}).call({}) end end end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index dfce266253..8514ccd55b 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -86,7 +86,7 @@ module ActiveRecord rename_table :cats, :felines assert connection.table_exists? :felines - refute connection.table_exists? :cats + assert_not connection.table_exists? :cats primary_key_name = connection.select_values(<<-SQL.strip_heredoc, "SCHEMA")[0] SELECT c.relname @@ -107,7 +107,7 @@ module ActiveRecord connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()" assert_nothing_raised { rename_table :cats, :felines } assert connection.table_exists? :felines - refute connection.table_exists? :cats + assert_not connection.table_exists? :cats ensure connection.drop_table :cats, if_exists: true connection.drop_table :felines, if_exists: true diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b18af2ab55..a4b8664a85 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -71,6 +71,16 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Migration.verbose = @verbose_was end + def test_migrator_migrations_path_is_deprecated + assert_deprecated do + ActiveRecord::Migrator.migrations_path = "/whatever" + end + ensure + assert_deprecated do + ActiveRecord::Migrator.migrations_path = "db/migrate" + end + end + def test_migration_version_matches_component_version assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version end @@ -78,20 +88,20 @@ class MigrationTest < ActiveRecord::TestCase def test_migrator_versions migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = migrations_path + migrator = ActiveRecord::MigrationContext.new(migrations_path) - ActiveRecord::Migrator.up(migrations_path) - assert_equal 3, ActiveRecord::Migrator.current_version - assert_equal false, ActiveRecord::Migrator.needs_migration? + migrator.up + assert_equal 3, migrator.current_version + assert_equal false, migrator.needs_migration? - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid") - assert_equal 0, ActiveRecord::Migrator.current_version - assert_equal true, ActiveRecord::Migrator.needs_migration? + migrator.down + assert_equal 0, migrator.current_version + assert_equal true, migrator.needs_migration? ActiveRecord::SchemaMigration.create!(version: 3) - assert_equal true, ActiveRecord::Migrator.needs_migration? + assert_equal true, migrator.needs_migration? ensure - ActiveRecord::Migrator.migrations_paths = old_path + ActiveRecord::MigrationContext.new(old_path) end def test_migration_detection_without_schema_migration_table @@ -99,28 +109,31 @@ class MigrationTest < ActiveRecord::TestCase migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = migrations_path + migrator = ActiveRecord::MigrationContext.new(migrations_path) - assert_equal true, ActiveRecord::Migrator.needs_migration? + assert_equal true, migrator.needs_migration? ensure - ActiveRecord::Migrator.migrations_paths = old_path + ActiveRecord::MigrationContext.new(old_path) end def test_any_migrations old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/valid" + migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid") - assert ActiveRecord::Migrator.any_migrations? + assert_predicate migrator, :any_migrations? - ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/empty" + migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty") - assert_not ActiveRecord::Migrator.any_migrations? + assert_not_predicate migrator_empty, :any_migrations? ensure - ActiveRecord::Migrator.migrations_paths = old_path + ActiveRecord::MigrationContext.new(old_path) end def test_migration_version - assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) } + migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check") + assert_equal 0, migrator.current_version + migrator.up(20131219224947) + assert_equal 20131219224947, migrator.current_version end def test_create_table_with_force_true_does_not_drop_nonexisting_table @@ -157,7 +170,7 @@ class MigrationTest < ActiveRecord::TestCase def test_add_table_with_decimals Person.connection.drop_table :big_numbers rescue nil - assert !BigNumber.table_exists? + assert_not_predicate BigNumber, :table_exists? GiveMeBigNumbers.up BigNumber.reset_column_information @@ -216,15 +229,16 @@ class MigrationTest < ActiveRecord::TestCase def test_filtering_migrations assert_no_column Person, :last_name - assert !Reminder.table_exists? + assert_not_predicate Reminder, :table_exists? name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) + migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid") + migrator.up(&name_filter) assert_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) + migrator.down(&name_filter) assert_no_column Person, :last_name assert_raise(ActiveRecord::StatementInvalid) { Reminder.first } @@ -382,9 +396,9 @@ class MigrationTest < ActiveRecord::TestCase current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = migrations_path + migrator = ActiveRecord::MigrationContext.new(migrations_path) - ActiveRecord::Migrator.up(migrations_path) + migrator.up assert_equal current_env, ActiveRecord::InternalMetadata[:environment] original_rails_env = ENV["RAILS_ENV"] @@ -395,13 +409,13 @@ class MigrationTest < ActiveRecord::TestCase refute_equal current_env, new_env sleep 1 # mysql by default does not store fractional seconds in the database - ActiveRecord::Migrator.up(migrations_path) + migrator.up assert_equal new_env, ActiveRecord::InternalMetadata[:environment] ensure - ActiveRecord::Migrator.migrations_paths = old_path + migrator = ActiveRecord::MigrationContext.new(old_path) ENV["RAILS_ENV"] = original_rails_env ENV["RACK_ENV"] = original_rack_env - ActiveRecord::Migrator.up(migrations_path) + migrator.up end def test_internal_metadata_stores_environment_when_other_data_exists @@ -411,14 +425,15 @@ class MigrationTest < ActiveRecord::TestCase current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call migrations_path = MIGRATIONS_ROOT + "/valid" old_path = ActiveRecord::Migrator.migrations_paths - ActiveRecord::Migrator.migrations_paths = migrations_path current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call - ActiveRecord::Migrator.up(migrations_path) + migrator = ActiveRecord::MigrationContext.new(migrations_path) + migrator.up assert_equal current_env, ActiveRecord::InternalMetadata[:environment] assert_equal "bar", ActiveRecord::InternalMetadata[:foo] ensure - ActiveRecord::Migrator.migrations_paths = old_path + migrator = ActiveRecord::MigrationContext.new(old_path) + migrator.up end def test_proper_table_name_on_migration @@ -450,7 +465,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_rename_table_with_prefix_and_suffix - assert !Thing.table_exists? + assert_not_predicate Thing, :table_exists? ActiveRecord::Base.table_name_prefix = "p_" ActiveRecord::Base.table_name_suffix = "_s" Thing.reset_table_name @@ -471,7 +486,7 @@ class MigrationTest < ActiveRecord::TestCase end def test_add_drop_table_with_prefix_and_suffix - assert !Reminder.table_exists? + assert_not_predicate Reminder, :table_exists? ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" Reminder.reset_table_name @@ -801,8 +816,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter? t.integer :age end - # Adding an index fires a query every time to check if an index already exists or not - assert_queries(3) do + classname = ActiveRecord::Base.connection.class.name[/[^:]*$/] + expected_query_count = { + "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not + "PostgreSQLAdapter" => 2, + }.fetch(classname) { + raise "need an expected query count for #{classname}" + } + + assert_queries(expected_query_count) do with_bulk_change_table do |t| t.index :username, unique: true, name: :awesome_username_index t.index [:name, :age] @@ -826,7 +848,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter? assert index(:index_delete_me_on_name) - assert_queries(3) do + classname = ActiveRecord::Base.connection.class.name[/[^:]*$/] + expected_query_count = { + "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not + "PostgreSQLAdapter" => 2, + }.fetch(classname) { + raise "need an expected query count for #{classname}" + } + + assert_queries(expected_query_count) do with_bulk_change_table do |t| t.remove_index :name t.index :name, name: :new_name_index, unique: true @@ -848,18 +878,24 @@ if ActiveRecord::Base.connection.supports_bulk_alter? assert ! column(:name).default assert_equal :date, column(:birthdate).type - # One query for columns (delete_me table) - # One query for primary key (delete_me table) - # One query to do the bulk change - assert_queries(3, ignore_none: true) do + classname = ActiveRecord::Base.connection.class.name[/[^:]*$/] + expected_query_count = { + "Mysql2Adapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change + "PostgreSQLAdapter" => 3, # one query for columns, one for bulk change, one for comment + }.fetch(classname) { + raise "need an expected query count for #{classname}" + } + + assert_queries(expected_query_count, ignore_none: true) do with_bulk_change_table do |t| t.change :name, :string, default: "NONAME" - t.change :birthdate, :datetime + t.change :birthdate, :datetime, comment: "This is a comment" end end assert_equal "NONAME", column(:name).default assert_equal :datetime, column(:birthdate).type + assert_equal "This is a comment", column(:birthdate).comment end private @@ -919,7 +955,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? + assert_empty copied ensure clear end @@ -960,7 +996,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? + assert_empty copied end ensure clear @@ -1002,7 +1038,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? + assert_empty copied end ensure clear @@ -1023,7 +1059,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase files_count = Dir[@migrations_path + "/*.rb"].length copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic") assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - assert copied.empty? + assert_empty copied ensure clear end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 1047ba1367..873455cf67 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -89,7 +89,7 @@ class MigratorTest < ActiveRecord::TestCase end def test_finds_migrations - migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid") + migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first @@ -98,7 +98,8 @@ class MigratorTest < ActiveRecord::TestCase end def test_finds_migrations_in_subdirectories - migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories") + migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations + [[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i| assert_equal migrations[i].version, pair.first @@ -108,7 +109,7 @@ class MigratorTest < ActiveRecord::TestCase def test_finds_migrations_from_two_directories directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"] - migrations = ActiveRecord::Migrator.migrations directories + migrations = ActiveRecord::MigrationContext.new(directories).migrations [[20090101010101, "PeopleHaveHobbies"], [20090101010202, "PeopleHaveDescriptions"], @@ -121,14 +122,14 @@ class MigratorTest < ActiveRecord::TestCase end def test_finds_migrations_in_numbered_directory - migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + "/10_urban"] + migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations assert_equal 9, migrations[0].version assert_equal "AddExpressions", migrations[0].name end def test_relative_migrations list = Dir.chdir(MIGRATIONS_ROOT) do - ActiveRecord::Migrator.migrations("valid") + ActiveRecord::MigrationContext.new("valid").migrations end migration_proxy = list.find { |item| @@ -157,7 +158,7 @@ class MigratorTest < ActiveRecord::TestCase ["up", "002", "We need reminders"], ["down", "003", "Innocent jointable"], ["up", "010", "********** NO FILE **********"], - ], ActiveRecord::Migrator.migrations_status(path) + ], ActiveRecord::MigrationContext.new(path).migrations_status end def test_migrations_status_in_subdirectories @@ -171,7 +172,7 @@ class MigratorTest < ActiveRecord::TestCase ["up", "002", "We need reminders"], ["down", "003", "Innocent jointable"], ["up", "010", "********** NO FILE **********"], - ], ActiveRecord::Migrator.migrations_status(path) + ], ActiveRecord::MigrationContext.new(path).migrations_status end def test_migrations_status_with_schema_define_in_subdirectories @@ -186,7 +187,7 @@ class MigratorTest < ActiveRecord::TestCase ["up", "001", "Valid people have last names"], ["up", "002", "We need reminders"], ["up", "003", "Innocent jointable"], - ], ActiveRecord::Migrator.migrations_status(path) + ], ActiveRecord::MigrationContext.new(path).migrations_status ensure ActiveRecord::Migrator.migrations_paths = prev_paths end @@ -204,7 +205,7 @@ class MigratorTest < ActiveRecord::TestCase ["down", "20100201010101", "Valid with timestamps we need reminders"], ["down", "20100301010101", "Valid with timestamps innocent jointable"], ["up", "20160528010101", "********** NO FILE **********"], - ], ActiveRecord::Migrator.migrations_status(paths) + ], ActiveRecord::MigrationContext.new(paths).migrations_status end def test_migrator_interleaved_migrations @@ -232,25 +233,28 @@ 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 + migrator = ActiveRecord::Migrator.new(:up, migrations) + migrator.migrate assert migrations.all?(&:went_up) assert migrations.all? { |m| !m.went_down } - assert_equal 2, ActiveRecord::Migrator.current_version + assert_equal 2, migrator.current_version end def test_down_calls_down test_up_calls_up migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] - ActiveRecord::Migrator.new(:down, migrations).migrate + migrator = ActiveRecord::Migrator.new(:down, migrations) + migrator.migrate assert migrations.all? { |m| !m.went_up } assert migrations.all?(&:went_down) - assert_equal 0, ActiveRecord::Migrator.current_version + assert_equal 0, migrator.current_version end def test_current_version ActiveRecord::SchemaMigration.create!(version: "1000") - assert_equal 1000, ActiveRecord::Migrator.current_version + migrator = ActiveRecord::MigrationContext.new("db/migrate") + assert_equal 1000, migrator.current_version end def test_migrator_one_up @@ -289,33 +293,36 @@ class MigratorTest < ActiveRecord::TestCase def test_migrator_double_up calls, migrations = sensors(3) - assert_equal(0, ActiveRecord::Migrator.current_version) + migrator = ActiveRecord::Migrator.new(:up, migrations, 1) + assert_equal(0, migrator.current_version) - ActiveRecord::Migrator.new(:up, migrations, 1).migrate + migrator.migrate assert_equal [[:up, 1]], calls calls.clear - ActiveRecord::Migrator.new(:up, migrations, 1).migrate + migrator.migrate assert_equal [], calls end def test_migrator_double_down calls, migrations = sensors(3) + migrator = ActiveRecord::Migrator.new(:up, migrations, 1) - assert_equal(0, ActiveRecord::Migrator.current_version) + assert_equal 0, migrator.current_version - ActiveRecord::Migrator.new(:up, migrations, 1).run + migrator.run assert_equal [[:up, 1]], calls calls.clear - ActiveRecord::Migrator.new(:down, migrations, 1).run + migrator = ActiveRecord::Migrator.new(:down, migrations, 1) + migrator.run assert_equal [[:down, 1]], calls calls.clear - ActiveRecord::Migrator.new(:down, migrations, 1).run + migrator.run assert_equal [], calls - assert_equal(0, ActiveRecord::Migrator.current_version) + assert_equal 0, migrator.current_version end def test_migrator_verbosity @@ -361,78 +368,85 @@ class MigratorTest < ActiveRecord::TestCase def test_migrator_going_down_due_to_version_target calls, migrator = migrator_class(3) + migrator = migrator.new("valid") - migrator.up("valid", 1) + migrator.up(1) assert_equal [[:up, 1]], calls calls.clear - migrator.migrate("valid", 0) + migrator.migrate(0) assert_equal [[:down, 1]], calls calls.clear - migrator.migrate("valid") + migrator.migrate assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls end def test_migrator_output_when_running_multiple_migrations _, migrator = migrator_class(3) + migrator = migrator.new("valid") - result = migrator.migrate("valid") + result = migrator.migrate assert_equal(3, result.count) # Nothing migrated from duplicate run - result = migrator.migrate("valid") + result = migrator.migrate assert_equal(0, result.count) - result = migrator.rollback("valid") + result = migrator.rollback assert_equal(1, result.count) end def test_migrator_output_when_running_single_migration _, migrator = migrator_class(1) - result = migrator.run(:up, "valid", 1) + migrator = migrator.new("valid") + + result = migrator.run(:up, 1) assert_equal(1, result.version) end def test_migrator_rollback _, migrator = migrator_class(3) + migrator = migrator.new("valid") - migrator.migrate("valid") - assert_equal(3, ActiveRecord::Migrator.current_version) + migrator.migrate + assert_equal(3, migrator.current_version) - migrator.rollback("valid") - assert_equal(2, ActiveRecord::Migrator.current_version) + migrator.rollback + assert_equal(2, migrator.current_version) - migrator.rollback("valid") - assert_equal(1, ActiveRecord::Migrator.current_version) + migrator.rollback + assert_equal(1, migrator.current_version) - migrator.rollback("valid") - assert_equal(0, ActiveRecord::Migrator.current_version) + migrator.rollback + assert_equal(0, migrator.current_version) - migrator.rollback("valid") - assert_equal(0, ActiveRecord::Migrator.current_version) + migrator.rollback + assert_equal(0, migrator.current_version) end def test_migrator_db_has_no_schema_migrations_table _, migrator = migrator_class(3) + migrator = migrator.new("valid") ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations") - migrator.migrate("valid", 1) + migrator.migrate(1) assert ActiveRecord::Base.connection.table_exists?("schema_migrations") end def test_migrator_forward _, migrator = migrator_class(3) - migrator.migrate("/valid", 1) - assert_equal(1, ActiveRecord::Migrator.current_version) + migrator = migrator.new("/valid") + migrator.migrate(1) + assert_equal(1, migrator.current_version) - migrator.forward("/valid", 2) - assert_equal(3, ActiveRecord::Migrator.current_version) + migrator.forward(2) + assert_equal(3, migrator.current_version) - migrator.forward("/valid") - assert_equal(3, ActiveRecord::Migrator.current_version) + migrator.forward + assert_equal(3, migrator.current_version) end def test_only_loads_pending_migrations @@ -440,25 +454,27 @@ class MigratorTest < ActiveRecord::TestCase ActiveRecord::SchemaMigration.create!(version: "1") calls, migrator = migrator_class(3) - migrator.migrate("valid", nil) + migrator = migrator.new("valid") + migrator.migrate assert_equal [[:up, 2], [:up, 3]], calls end def test_get_all_versions _, migrator = migrator_class(3) + migrator = migrator.new("valid") - migrator.migrate("valid") - assert_equal([1, 2, 3], ActiveRecord::Migrator.get_all_versions) + migrator.migrate + assert_equal([1, 2, 3], migrator.get_all_versions) - migrator.rollback("valid") - assert_equal([1, 2], ActiveRecord::Migrator.get_all_versions) + migrator.rollback + assert_equal([1, 2], migrator.get_all_versions) - migrator.rollback("valid") - assert_equal([1], ActiveRecord::Migrator.get_all_versions) + migrator.rollback + assert_equal([1], migrator.get_all_versions) - migrator.rollback("valid") - assert_equal([], ActiveRecord::Migrator.get_all_versions) + migrator.rollback + assert_equal([], migrator.get_all_versions) end private @@ -483,11 +499,11 @@ class MigratorTest < ActiveRecord::TestCase def migrator_class(count) calls, migrations = sensors(count) - migrator = Class.new(ActiveRecord::Migrator).extend(Module.new { - define_method(:migrations) { |paths| + migrator = Class.new(ActiveRecord::MigrationContext) { + define_method(:migrations) { |*| migrations } - }) + } [calls, migrator] end end diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb index 59be4dc5a8..a24b173cf5 100644 --- a/activerecord/test/cases/multiparameter_attributes_test.rb +++ b/activerecord/test/cases/multiparameter_attributes_test.rb @@ -227,7 +227,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase topic = Topic.find(1) topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on - assert_equal false, topic.written_on.respond_to?(:time_zone) + assert_not_respond_to topic.written_on, :time_zone end end @@ -242,7 +242,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on - assert_equal false, topic.written_on.respond_to?(:time_zone) + assert_not_respond_to topic.written_on, :time_zone end ensure Topic.skip_time_zone_conversion_for_attributes = [] @@ -261,7 +261,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase topic = Topic.find(1) topic.attributes = attributes assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time - assert_not topic.bonus_time.utc? + assert_not_predicate topic.bonus_time, :utc? attributes = { "written_on(1i)" => "2000", "written_on(2i)" => "", "written_on(3i)" => "", diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index a2ccb603a9..1ed3a61bbb 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -36,7 +36,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "", _destroy: "0" }] pirate.save! - assert pirate.birds_with_reject_all_blank.empty? + assert_empty pirate.birds_with_reject_all_blank end def test_should_not_build_a_new_record_if_reject_all_blank_returns_false @@ -44,7 +44,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "" }] pirate.save! - assert pirate.birds_with_reject_all_blank.empty? + assert_empty pirate.birds_with_reject_all_blank end def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false @@ -152,7 +152,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase man = Man.create(name: "Jon") interest = man.interests.create(topic: "the ladies") man.update(interests_attributes: { _destroy: "1", id: interest.id }) - assert man.reload.interests.empty? + assert_empty man.reload.interests end def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false @@ -240,7 +240,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @ship.destroy @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" } - assert !@pirate.ship.persisted? + assert_not_predicate @pirate.ship, :persisted? assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end @@ -261,7 +261,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_replace_an_existing_record_if_there_is_no_id @pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" } - assert !@pirate.ship.persisted? + assert_not_predicate @pirate.ship, :persisted? assert_equal "Davy Jones Gold Dagger", @pirate.ship.name assert_equal "Nights Dirty Lightning", @ship.name end @@ -335,7 +335,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_also_work_with_a_HashWithIndifferentAccess @pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(id: @ship.id, name: "Davy Jones Gold Dagger") - assert @pirate.ship.persisted? + assert_predicate @pirate.ship, :persisted? assert_equal "Davy Jones Gold Dagger", @pirate.ship.name end @@ -350,12 +350,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_not_destroy_the_associated_model_until_the_parent_is_saved @pirate.attributes = { ship_attributes: { id: @ship.id, _destroy: "1" } } - assert !@pirate.ship.destroyed? - assert @pirate.ship.marked_for_destruction? + assert_not_predicate @pirate.ship, :destroyed? + assert_predicate @pirate.ship, :marked_for_destruction? @pirate.save - assert @pirate.ship.destroyed? + assert_predicate @pirate.ship, :destroyed? assert_nil @pirate.reload.ship end @@ -424,7 +424,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @pirate.destroy @ship.reload.pirate_attributes = { catchphrase: "Arr" } - assert !@ship.pirate.persisted? + assert_not_predicate @ship.pirate, :persisted? assert_equal "Arr", @ship.pirate.catchphrase end @@ -445,7 +445,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_replace_an_existing_record_if_there_is_no_id @ship.reload.pirate_attributes = { catchphrase: "Arr" } - assert !@ship.pirate.persisted? + assert_not_predicate @ship.pirate, :persisted? assert_equal "Arr", @ship.pirate.catchphrase assert_equal "Aye", @pirate.catchphrase end @@ -550,7 +550,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @pirate.delete @ship.reload.attributes = { update_only_pirate_attributes: { catchphrase: "Arr" } } - assert !@ship.update_only_pirate.persisted? + assert_not_predicate @ship.update_only_pirate, :persisted? end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @@ -632,10 +632,10 @@ module NestedAttributesOnACollectionAssociationTests def test_should_not_load_association_when_updating_existing_records @pirate.reload @pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }]) - assert ! @pirate.send(@association_name).loaded? + assert_not_predicate @pirate.send(@association_name), :loaded? @pirate.save - assert ! @pirate.send(@association_name).loaded? + assert_not_predicate @pirate.send(@association_name), :loaded? assert_equal "Grace OMalley", @child_1.reload.name end @@ -663,7 +663,7 @@ module NestedAttributesOnACollectionAssociationTests def test_should_not_remove_scheduled_destroys_when_loading_association @pirate.reload @pirate.send(association_setter, [{ id: @child_1.id, _destroy: "1" }]) - assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction? + assert_predicate @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }, :marked_for_destruction? end def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models @@ -705,10 +705,10 @@ module NestedAttributesOnACollectionAssociationTests association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" } } } - assert !@pirate.send(@association_name).first.persisted? + assert_not_predicate @pirate.send(@association_name).first, :persisted? assert_equal "Grace OMalley", @pirate.send(@association_name).first.name - assert !@pirate.send(@association_name).last.persisted? + assert_not_predicate @pirate.send(@association_name).last, :persisted? assert_equal "Privateers Greed", @pirate.send(@association_name).last.name end @@ -1091,7 +1091,7 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR test "nested singular associations are validated" do part = ShipPart.new(name: "Stern", ship_attributes: { name: nil }) - assert_not part.valid? + assert_not_predicate part, :valid? assert_equal ["Ship name can't be blank"], part.errors.full_messages end end diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb index f04c68b08f..1d26057fdc 100644 --- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -63,7 +63,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase # Characterizing when :before_add callback is called test ":before_add called for new bird when not loaded" do - assert_not @pirate.birds_with_add.loaded? + assert_not_predicate @pirate.birds_with_add, :loaded? @pirate.birds_with_add_attributes = new_bird_attributes assert_new_bird_with_callback_called end @@ -80,7 +80,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase end test ":before_add not called for identical assignment when not loaded" do - assert_not @pirate.birds_with_add.loaded? + assert_not_predicate @pirate.birds_with_add, :loaded? @pirate.birds_with_add_attributes = existing_birds_attributes assert_callbacks_not_called end @@ -92,7 +92,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase end test ":before_add not called for destroy assignment when not loaded" do - assert_not @pirate.birds_with_add.loaded? + assert_not_predicate @pirate.birds_with_add, :loaded? @pirate.birds_with_add_attributes = destroy_bird_attributes assert_callbacks_not_called end @@ -111,7 +111,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase # Ensuring that the records in the association target are updated, # whether the association is loaded before or not test "Assignment updates records in target when not loaded" do - assert_not @pirate.birds_with_add.loaded? + assert_not_predicate @pirate.birds_with_add, :loaded? @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add) end @@ -124,7 +124,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase test("Assignment updates records in target when not loaded" \ " and callback loads target") do - assert_not @pirate.birds_with_add_load.loaded? + assert_not_predicate @pirate.birds_with_add_load, :loaded? @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes assert_assignment_affects_records_in_target(:birds_with_add_load) end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 4cc66a2e49..d4a4cd564a 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -59,13 +59,42 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_all_with_order_and_limit_updates_subset_only author = authors(:david) - assert_nothing_raised do - assert_equal 1, author.posts_sorted_by_id_limited.size - assert_equal 2, author.posts_sorted_by_id_limited.limit(2).to_a.size - assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ]) - assert_equal "bulk update!", posts(:welcome).body - assert_not_equal "bulk update!", posts(:thinking).body - end + limited_posts = author.posts_sorted_by_id_limited + assert_equal 1, limited_posts.size + assert_equal 2, limited_posts.limit(2).size + assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ]) + assert_equal "bulk update!", posts(:welcome).body + assert_not_equal "bulk update!", posts(:thinking).body + end + + def test_update_all_with_order_and_limit_and_offset_updates_subset_only + author = authors(:david) + limited_posts = author.posts_sorted_by_id_limited.offset(1) + assert_equal 1, limited_posts.size + assert_equal 2, limited_posts.limit(2).size + assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ]) + assert_equal "bulk update!", posts(:thinking).body + assert_not_equal "bulk update!", posts(:welcome).body + end + + def test_delete_all_with_order_and_limit_deletes_subset_only + author = authors(:david) + limited_posts = Post.where(author: author).order(:id).limit(1) + assert_equal 1, limited_posts.size + assert_equal 2, limited_posts.limit(2).size + assert_equal 1, limited_posts.delete_all + assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) } + assert posts(:thinking) + end + + def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only + author = authors(:david) + limited_posts = Post.where(author: author).order(:id).limit(1).offset(1) + assert_equal 1, limited_posts.size + assert_equal 2, limited_posts.limit(2).size + assert_equal 1, limited_posts.delete_all + assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) } + assert posts(:welcome) end end @@ -188,7 +217,7 @@ class PersistenceTest < ActiveRecord::TestCase def test_destroy_all conditions = "author_name = 'Mary'" topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a - assert ! topics_by_mary.empty? + assert_not_empty topics_by_mary assert_difference("Topic.count", -topics_by_mary.size) do destroyed = Topic.where(conditions).destroy_all.sort_by(&:id) @@ -222,9 +251,16 @@ class PersistenceTest < ActiveRecord::TestCase assert_equal "The First Topic", topics(:first).becomes(Reply).title end + def test_becomes_after_reload_schema_from_cache + Reply.define_attribute_methods + Reply.serialize(:content) # invoke reload_schema_from_cache + assert_kind_of Reply, topics(:first).becomes(Reply) + assert_equal "The First Topic", topics(:first).becomes(Reply).title + end + def test_becomes_includes_errors company = Company.new(name: nil) - assert !company.valid? + assert_not_predicate company, :valid? original_errors = company.errors client = company.becomes(Client) assert_equal original_errors.keys, client.errors.keys @@ -341,8 +377,8 @@ class PersistenceTest < ActiveRecord::TestCase developer.destroy new_developer = developer.dup new_developer.save - assert new_developer.persisted? - assert_not new_developer.destroyed? + assert_predicate new_developer, :persisted? + assert_not_predicate new_developer, :destroyed? end def test_create_many @@ -353,12 +389,13 @@ class PersistenceTest < ActiveRecord::TestCase def test_create_columns_not_equal_attributes topic = Topic.instantiate( - "attributes" => { - "title" => "Another New Topic", - "does_not_exist" => "test" - } + "title" => "Another New Topic", + "does_not_exist" => "test" ) + topic = topic.dup # reset @new_record assert_nothing_raised { topic.save } + assert_predicate topic, :persisted? + assert_equal "Another New Topic", topic.reload.title end def test_create_through_factory_with_block @@ -405,6 +442,8 @@ class PersistenceTest < ActiveRecord::TestCase topic_reloaded = Topic.instantiate(topic.attributes.merge("does_not_exist" => "test")) topic_reloaded.title = "A New Topic" assert_nothing_raised { topic_reloaded.save } + assert_predicate topic_reloaded, :persisted? + assert_equal "A New Topic", topic_reloaded.reload.title end def test_update_for_record_with_only_primary_key @@ -441,6 +480,22 @@ class PersistenceTest < ActiveRecord::TestCase assert_instance_of Reply, Reply.find(reply.id) end + def test_becomes_default_sti_subclass + original_type = Topic.columns_hash["type"].default + ActiveRecord::Base.connection.change_column_default :topics, :type, "Reply" + Topic.reset_column_information + + reply = topics(:second) + assert_instance_of Reply, reply + + topic = reply.becomes(Topic) + assert_instance_of Topic, topic + + ensure + ActiveRecord::Base.connection.change_column_default :topics, :type, original_type + Topic.reset_column_information + end + def test_update_after_create klass = Class.new(Topic) do def self.name; "Topic"; end @@ -567,40 +622,65 @@ class PersistenceTest < ActiveRecord::TestCase end def test_delete_new_record - client = Client.new + client = Client.new(name: "37signals") client.delete - assert client.frozen? + assert_predicate client, :frozen? + + assert_not client.save + assert_raise(ActiveRecord::RecordNotSaved) { client.save! } + + assert_predicate client, :frozen? + assert_raise(RuntimeError) { client.name = "something else" } end def test_delete_record_with_associations client = Client.find(3) client.delete - assert client.frozen? + assert_predicate client, :frozen? assert_kind_of Firm, client.firm + + assert_not client.save + assert_raise(ActiveRecord::RecordNotSaved) { client.save! } + + assert_predicate client, :frozen? assert_raise(RuntimeError) { client.name = "something else" } end def test_destroy_new_record - client = Client.new + client = Client.new(name: "37signals") client.destroy - assert client.frozen? + assert_predicate client, :frozen? + + assert_not client.save + assert_raise(ActiveRecord::RecordNotSaved) { client.save! } + + assert_predicate client, :frozen? + assert_raise(RuntimeError) { client.name = "something else" } end def test_destroy_record_with_associations client = Client.find(3) client.destroy - assert client.frozen? + assert_predicate client, :frozen? assert_kind_of Firm, client.firm + + assert_not client.save + assert_raise(ActiveRecord::RecordNotSaved) { client.save! } + + assert_predicate client, :frozen? assert_raise(RuntimeError) { client.name = "something else" } end def test_update_attribute - assert !Topic.find(1).approved? + assert_not_predicate Topic.find(1), :approved? Topic.find(1).update_attribute("approved", true) - assert Topic.find(1).approved? + assert_predicate Topic.find(1), :approved? Topic.find(1).update_attribute(:approved, false) - assert !Topic.find(1).approved? + assert_not_predicate Topic.find(1), :approved? + + Topic.find(1).update_attribute(:change_approved_before_save, true) + assert_predicate Topic.find(1), :approved? end def test_update_attribute_for_readonly_attribute @@ -637,14 +717,14 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_column topic = Topic.find(1) topic.update_column("approved", true) - assert topic.approved? + assert_predicate topic, :approved? topic.reload - assert topic.approved? + assert_predicate topic, :approved? topic.update_column(:approved, false) - assert !topic.approved? + assert_not_predicate topic, :approved? topic.reload - assert !topic.approved? + assert_not_predicate topic, :approved? end def test_update_column_should_not_use_setter_method @@ -731,10 +811,10 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_columns topic = Topic.find(1) topic.update_columns("approved" => true, title: "Sebastian Topic") - assert topic.approved? + assert_predicate topic, :approved? assert_equal "Sebastian Topic", topic.title topic.reload - assert topic.approved? + assert_predicate topic, :approved? assert_equal "Sebastian Topic", topic.title end @@ -842,33 +922,33 @@ class PersistenceTest < ActiveRecord::TestCase def test_update topic = Topic.find(1) - assert !topic.approved? + assert_not_predicate topic, :approved? assert_equal "The First Topic", topic.title topic.update("approved" => true, "title" => "The First Topic Updated") topic.reload - assert topic.approved? + assert_predicate topic, :approved? assert_equal "The First Topic Updated", topic.title topic.update(approved: false, title: "The First Topic") topic.reload - assert !topic.approved? + assert_not_predicate topic, :approved? assert_equal "The First Topic", topic.title end def test_update_attributes topic = Topic.find(1) - assert !topic.approved? + assert_not_predicate topic, :approved? assert_equal "The First Topic", topic.title topic.update_attributes("approved" => true, "title" => "The First Topic Updated") topic.reload - assert topic.approved? + assert_predicate topic, :approved? assert_equal "The First Topic Updated", topic.title topic.update_attributes(approved: false, title: "The First Topic") topic.reload - assert !topic.approved? + assert_not_predicate topic, :approved? assert_equal "The First Topic", topic.title error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do @@ -969,7 +1049,7 @@ class PersistenceTest < ActiveRecord::TestCase Topic.find(1).replies << should_be_destroyed_reply topic = Topic.destroy(1) - assert topic.destroyed? + assert_predicate topic, :destroyed? assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) } assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) } @@ -1049,13 +1129,13 @@ class PersistenceTest < ActiveRecord::TestCase def test_find_via_reload post = Post.new - assert post.new_record? + assert_predicate post, :new_record? post.id = 1 post.reload assert_equal "Welcome to the weblog", post.title - assert_not post.new_record? + assert_not_predicate post, :new_record? end def test_reload_via_querycache @@ -1103,13 +1183,18 @@ class PersistenceTest < ActiveRecord::TestCase end def test_reset_column_information_resets_children - child = Class.new(Topic) - child.new # force schema to load + child_class = Class.new(Topic) + child_class.new # force schema to load ActiveRecord::Base.connection.add_column(:topics, :foo, :string) Topic.reset_column_information - assert_equal "bar", child.new(foo: :bar).foo + # this should redefine attribute methods + child_class.new + + assert child_class.instance_methods.include?(:foo) + assert child_class.instance_methods.include?(:foo_changed?) + assert_equal "bar", child_class.new(foo: :bar).foo ensure ActiveRecord::Base.connection.remove_column(:topics, :foo) Topic.reset_column_information diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 7f6c2382ca..60dac91ec9 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -207,13 +207,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_serial_with_quoted_sequence_name column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key] assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function - assert column.serial? + assert_predicate column, :serial? end def test_serial_with_unquoted_sequence_name column = Topic.columns_hash[Topic.primary_key] assert_equal "nextval('topics_id_seq'::regclass)", column.default_function - assert column.serial? + assert_predicate column, :serial? end end end @@ -298,6 +298,8 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase assert_not column.null assert_equal :string, column.type assert_equal 42, column.limit + ensure + Barcode.reset_column_information end test "schema dump primary key includes type and options" do @@ -428,7 +430,7 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) @connection.create_table(:widgets, id: @pk_type, force: true) column = @connection.columns(:widgets).find { |c| c.name == "id" } assert_equal :integer, column.type - assert_not column.bigint? + assert_not_predicate column, :bigint? end test "primary key with serial/integer are automatically numbered" do @@ -447,10 +449,10 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) test "primary key column type with options" do @connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true) column = @connection.columns(:widgets).find { |c| c.name == "id" } - assert column.auto_increment? + assert_predicate column, :auto_increment? assert_equal :integer, column.type - assert_not column.bigint? - assert column.unsigned? + assert_not_predicate column, :bigint? + assert_predicate column, :unsigned? schema = dump_table_schema "widgets" assert_match %r{create_table "widgets", id: :integer, unsigned: true, }, schema @@ -459,10 +461,10 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) test "bigint primary key with unsigned" do @connection.create_table(:widgets, id: :bigint, unsigned: true, force: true) column = @connection.columns(:widgets).find { |c| c.name == "id" } - assert column.auto_increment? + assert_predicate column, :auto_increment? assert_equal :integer, column.type - assert column.bigint? - assert column.unsigned? + assert_predicate column, :bigint? + assert_predicate column, :unsigned? schema = dump_table_schema "widgets" assert_match %r{create_table "widgets", id: :bigint, unsigned: true, }, schema diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 46f90b0bca..8d37f5ea95 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -82,7 +82,7 @@ class QueryCacheTest < ActiveRecord::TestCase assert_cache :off, conn end - assert !ActiveRecord::Base.connection.nil? + assert_not_predicate ActiveRecord::Base.connection, :nil? assert_cache :off middleware { @@ -283,7 +283,7 @@ class QueryCacheTest < ActiveRecord::TestCase payload[:sql].downcase! end - assert_raises RuntimeError do + assert_raises frozen_error_class do ActiveRecord::Base.cache do assert_queries(1) { Task.find(1); Task.find(1) } end @@ -327,7 +327,7 @@ class QueryCacheTest < ActiveRecord::TestCase conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2") ActiveRecord::Base.connection_handler.establish_connection(conf) Task.connection_specification_name = "test2" - refute Task.connected? + assert_not_predicate Task, :connected? Task.cache do begin @@ -414,20 +414,20 @@ class QueryCacheTest < ActiveRecord::TestCase def test_query_cache_does_not_establish_connection_if_unconnected with_temporary_connection_pool do ActiveRecord::Base.clear_active_connections! - refute ActiveRecord::Base.connection_handler.active_connections? # sanity check + assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check middleware { - refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup" + assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup" }.call({}) - refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup" + assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup" end end def test_query_cache_is_enabled_on_connections_established_after_middleware_runs with_temporary_connection_pool do ActiveRecord::Base.clear_active_connections! - refute ActiveRecord::Base.connection_handler.active_connections? # sanity check + assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check middleware { assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled" @@ -444,8 +444,8 @@ class QueryCacheTest < ActiveRecord::TestCase assert ActiveRecord::Base.connection.query_cache_enabled Thread.new { - refute ActiveRecord::Base.connection_pool.query_cache_enabled - refute ActiveRecord::Base.connection.query_cache_enabled + assert_not ActiveRecord::Base.connection_pool.query_cache_enabled + assert_not ActiveRecord::Base.connection.query_cache_enabled }.join }.call({}) @@ -562,7 +562,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do ActiveRecord::Base.cache do p = Post.find(1) - assert p.categories.any? + assert_predicate p.categories, :any? p.categories.delete_all end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 897d252cf8..6534770c57 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -111,7 +111,7 @@ module ActiveRecord end def test_quote_bigdecimal - bigdec = BigDecimal.new((1 << 100).to_s) + bigdec = BigDecimal((1 << 100).to_s) assert_equal bigdec.to_s("F"), @quoter.quote(bigdec) end diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb index d1b85cb4ef..383e43ed55 100644 --- a/activerecord/test/cases/readonly_test.rb +++ b/activerecord/test/cases/readonly_test.rb @@ -16,10 +16,10 @@ class ReadOnlyTest < ActiveRecord::TestCase def test_cant_save_readonly_record dev = Developer.find(1) - assert !dev.readonly? + assert_not_predicate dev, :readonly? dev.readonly! - assert dev.readonly? + assert_predicate dev, :readonly? assert_nothing_raised do dev.name = "Luscious forbidden fruit." @@ -54,7 +54,7 @@ class ReadOnlyTest < ActiveRecord::TestCase def test_has_many_find_readonly post = Post.find(1) - assert !post.comments.empty? + assert_not_empty post.comments assert !post.comments.any?(&:readonly?) assert !post.comments.to_a.any?(&:readonly?) assert post.comments.readonly(true).all?(&:readonly?) @@ -66,44 +66,44 @@ class ReadOnlyTest < ActiveRecord::TestCase end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_by_id - assert !posts(:welcome).people.find(1).readonly? + assert_not_predicate posts(:welcome).people.find(1), :readonly? end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_first - assert !posts(:welcome).people.first.readonly? + assert_not_predicate posts(:welcome).people.first, :readonly? end def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_last - assert !posts(:welcome).people.last.readonly? + assert_not_predicate posts(:welcome).people.last, :readonly? end def test_readonly_scoping Post.where("1=1").scoping do - assert !Post.find(1).readonly? - assert Post.readonly(true).find(1).readonly? - assert !Post.readonly(false).find(1).readonly? + assert_not_predicate Post.find(1), :readonly? + assert_predicate Post.readonly(true).find(1), :readonly? + assert_not_predicate Post.readonly(false).find(1), :readonly? end Post.joins(" ").scoping do - assert !Post.find(1).readonly? - assert Post.readonly.find(1).readonly? - assert !Post.readonly(false).find(1).readonly? + assert_not_predicate Post.find(1), :readonly? + assert_predicate Post.readonly.find(1), :readonly? + assert_not_predicate Post.readonly(false).find(1), :readonly? end # Oracle barfs on this because the join includes unqualified and # conflicting column names unless current_adapter?(:OracleAdapter) Post.joins(", developers").scoping do - assert_not Post.find(1).readonly? - assert Post.readonly.find(1).readonly? - assert !Post.readonly(false).find(1).readonly? + assert_not_predicate Post.find(1), :readonly? + assert_predicate Post.readonly.find(1), :readonly? + assert_not_predicate Post.readonly(false).find(1), :readonly? end end Post.readonly(true).scoping do - assert Post.find(1).readonly? - assert Post.readonly.find(1).readonly? - assert !Post.readonly(false).find(1).readonly? + assert_predicate Post.find(1), :readonly? + assert_predicate Post.readonly.find(1), :readonly? + assert_not_predicate Post.readonly(false).find(1), :readonly? end end @@ -111,10 +111,10 @@ class ReadOnlyTest < ActiveRecord::TestCase developer = Developer.find(1) project = Post.find(1) - assert !developer.projects.all_as_method.first.readonly? - assert !developer.projects.all_as_scope.first.readonly? + assert_not_predicate developer.projects.all_as_method.first, :readonly? + assert_not_predicate developer.projects.all_as_scope.first, :readonly? - assert !project.comments.all_as_method.first.readonly? - assert !project.comments.all_as_scope.first.readonly? + assert_not_predicate project.comments.all_as_method.first, :readonly? + assert_not_predicate project.comments.all_as_scope.first, :readonly? end end diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index 6c7727ab1b..61cb0f130d 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -79,14 +79,14 @@ module ActiveRecord end Thread.pass while conn.nil? - assert conn.in_use? + assert_predicate conn, :in_use? child.terminate while conn.in_use? Thread.pass end - assert !conn.in_use? + assert_not_predicate conn, :in_use? end end end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 37c2235f1a..ed19192ad9 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -66,6 +66,9 @@ class ReflectionTest < ActiveRecord::TestCase def test_column_string_type_and_limit assert_equal :string, @first.column_for_attribute("title").type + assert_equal :string, @first.column_for_attribute(:title).type + assert_equal :string, @first.type_for_attribute("title").type + assert_equal :string, @first.type_for_attribute(:title).type assert_equal 250, @first.column_for_attribute("title").limit end @@ -81,6 +84,9 @@ class ReflectionTest < ActiveRecord::TestCase def test_integer_columns assert_equal :integer, @first.column_for_attribute("id").type + assert_equal :integer, @first.column_for_attribute(:id).type + assert_equal :integer, @first.type_for_attribute("id").type + assert_equal :integer, @first.type_for_attribute(:id).type end def test_non_existent_columns_return_null_object @@ -89,6 +95,9 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal "attribute_that_doesnt_exist", column.name assert_nil column.sql_type assert_nil column.type + + column = @first.column_for_attribute(:attribute_that_doesnt_exist) + assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column end def test_non_existent_types_are_identity_types @@ -98,6 +107,11 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal object, type.deserialize(object) assert_equal object, type.cast(object) assert_equal object, type.serialize(object) + + type = @first.type_for_attribute(:attribute_that_doesnt_exist) + assert_equal object, type.deserialize(object) + assert_equal object, type.cast(object) + assert_equal object, type.serialize(object) end def test_reflection_klass_for_nested_class_name @@ -149,7 +163,7 @@ class ReflectionTest < ActiveRecord::TestCase expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] } received = Pirate.reflect_on_all_autosave_associations - assert !received.empty? + assert_not_empty received assert_not_equal Pirate.reflect_on_all_associations.length, received.length assert_equal expected, received end @@ -254,23 +268,34 @@ class ReflectionTest < ActiveRecord::TestCase end def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case - @hotel = Hotel.create! - @department = @hotel.departments.create! - @department.chefs.create!(employable: CakeDesigner.create!) - @department.chefs.create!(employable: DrinkDesigner.create!) + hotel = Hotel.create! + department = hotel.departments.create! + department.chefs.create!(employable: CakeDesigner.create!) + department.chefs.create!(employable: DrinkDesigner.create!) - assert_equal 1, @hotel.cake_designers.size - assert_equal 1, @hotel.drink_designers.size - assert_equal 2, @hotel.chefs.size + assert_equal 1, hotel.cake_designers.size + assert_equal 1, hotel.cake_designers.count + assert_equal 1, hotel.drink_designers.size + assert_equal 1, hotel.drink_designers.count + assert_equal 2, hotel.chefs.size + assert_equal 2, hotel.chefs.count end def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti - @hotel = Hotel.create! - @hotel.mocktail_designers << MocktailDesigner.create! + hotel = Hotel.create! + hotel.mocktail_designers << MocktailDesigner.create! + + assert_equal 1, hotel.mocktail_designers.size + assert_equal 1, hotel.mocktail_designers.count + assert_equal 1, hotel.chef_lists.size + assert_equal 1, hotel.chef_lists.count + + hotel.mocktail_designers = [] - assert_equal 1, @hotel.mocktail_designers.size - assert_equal 1, @hotel.mocktail_designers.count - assert_equal 1, @hotel.chef_lists.size + assert_equal 0, hotel.mocktail_designers.size + assert_equal 0, hotel.mocktail_designers.count + assert_equal 0, hotel.chef_lists.size + assert_equal 0, hotel.chef_lists.count end def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations @@ -290,12 +315,12 @@ class ReflectionTest < ActiveRecord::TestCase end def test_nested? - assert !Author.reflect_on_association(:comments).nested? - assert Author.reflect_on_association(:tags).nested? + assert_not_predicate Author.reflect_on_association(:comments), :nested? + assert_predicate Author.reflect_on_association(:tags), :nested? # Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is # a nested through association - assert Category.reflect_on_association(:post_comments).nested? + assert_predicate Category.reflect_on_association(:post_comments), :nested? end def test_association_primary_key @@ -310,15 +335,6 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested end - def test_association_primary_key_type - # Normal Association - assert_equal :integer, Author.reflect_on_association(:posts).association_primary_key_type.type - assert_equal :string, Author.reflect_on_association(:essay).association_primary_key_type.type - - # Through Association - assert_equal :string, Author.reflect_on_association(:essay_category).association_primary_key_type.type - end - def test_association_primary_key_raises_when_missing_primary_key reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } @@ -352,36 +368,36 @@ class ReflectionTest < ActiveRecord::TestCase end def test_collection_association - assert Pirate.reflect_on_association(:birds).collection? - assert Pirate.reflect_on_association(:parrots).collection? + assert_predicate Pirate.reflect_on_association(:birds), :collection? + assert_predicate Pirate.reflect_on_association(:parrots), :collection? - assert !Pirate.reflect_on_association(:ship).collection? - assert !Ship.reflect_on_association(:pirate).collection? + assert_not_predicate Pirate.reflect_on_association(:ship), :collection? + assert_not_predicate Ship.reflect_on_association(:pirate), :collection? end def test_default_association_validation - assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate? + assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm), :validate? - assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate? - assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate? + assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm), :validate? + assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm), :validate? end def test_always_validate_association_if_explicit - assert ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm).validate? - assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm).validate? - assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm).validate? + assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm), :validate? + assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm), :validate? + assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm), :validate? end def test_validate_association_if_autosave - assert ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm).validate? - assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm).validate? - assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm).validate? + assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm), :validate? + assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm), :validate? + assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm), :validate? end def test_never_validate_association_if_explicit - assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm).validate? - assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm).validate? - assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm).validate? + assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm), :validate? + assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm), :validate? + assert_not_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm), :validate? end def test_foreign_key diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb index b68b3723f6..074ce9454f 100644 --- a/activerecord/test/cases/relation/merging_test.rb +++ b/activerecord/test/cases/relation/merging_test.rb @@ -58,7 +58,7 @@ class RelationMergingTest < ActiveRecord::TestCase def test_relation_merging_with_locks devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2)) - assert devs.locked? + assert_predicate devs, :locked? end def test_relation_merging_with_preload @@ -72,6 +72,12 @@ class RelationMergingTest < ActiveRecord::TestCase assert_equal 1, comments.count end + def test_relation_merging_with_left_outer_joins + comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.left_outer_joins(:author).where(body: "Such a lovely day")) + + assert_equal 1, comments.count + end + def test_relation_merging_with_association assert_queries(2) do # one for loading post, and another one merged query post = Post.where(body: "Such a lovely day").first @@ -107,9 +113,9 @@ class RelationMergingTest < ActiveRecord::TestCase def test_merging_with_from_clause relation = Post.all - assert relation.from_clause.empty? + assert_empty relation.from_clause relation = relation.merge(Post.from("posts")) - refute relation.from_clause.empty? + assert_not_empty relation.from_clause end end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index ad3700b73a..1428b3e132 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -25,7 +25,7 @@ module ActiveRecord test "#order! with symbol prepends the table name" do assert relation.order!(:name).equal?(relation) node = relation.order_values.first - assert node.ascending? + assert_predicate node, :ascending? assert_equal :name, node.expr.name assert_equal "posts", node.expr.relation.name end @@ -88,7 +88,7 @@ module ActiveRecord assert relation.reorder!(:name).equal?(relation) node = relation.order_values.first - assert node.ascending? + assert_predicate node, :ascending? assert_equal :name, node.expr.name assert_equal "posts", node.expr.relation.name end @@ -139,7 +139,7 @@ module ActiveRecord private def relation - @relation ||= Relation.new(FakeKlass, Post.arel_table, Post.predicate_builder) + @relation ||= Relation.new(FakeKlass) end end end diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb index e5eb159d36..8703d238a0 100644 --- a/activerecord/test/cases/relation/where_clause_test.rb +++ b/activerecord/test/cases/relation/where_clause_test.rb @@ -75,8 +75,8 @@ class ActiveRecord::Relation end test "a clause knows if it is empty" do - assert WhereClause.empty.empty? - assert_not WhereClause.new(["anything"]).empty? + assert_empty WhereClause.empty + assert_not_empty WhereClause.new(["anything"]) end test "invert cannot handle nil" do diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d95a54a2fe..99797528b2 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -265,7 +265,7 @@ module ActiveRecord end def test_where_with_decimal_for_string_column - count = Post.where(title: BigDecimal.new(0)).count + count = Post.where(title: BigDecimal(0)).count assert_equal 0, count end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index a71d8de521..4e75371147 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -11,30 +11,29 @@ module ActiveRecord fixtures :posts, :comments, :authors, :author_addresses, :ratings def test_construction - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass, table: :b) assert_equal FakeKlass, relation.klass assert_equal :b, relation.table assert !relation.loaded, "relation is not loaded" end def test_responds_to_model_and_returns_klass - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) assert_equal FakeKlass, relation.model end def test_initialize_single_values - relation = Relation.new(FakeKlass, :b, nil) - (Relation::SINGLE_VALUE_METHODS - [:create_with, :readonly]).each do |method| + relation = Relation.new(FakeKlass) + (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method| assert_nil relation.send("#{method}_value"), method.to_s end - assert_equal false, relation.readonly_value value = relation.create_with_value assert_equal({}, value) assert_predicate value, :frozen? end def test_multi_value_initialize - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) Relation::MULTI_VALUE_METHODS.each do |method| values = relation.send("#{method}_values") assert_equal [], values, method.to_s @@ -43,49 +42,49 @@ module ActiveRecord end def test_extensions - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) assert_equal [], relation.extensions end def test_empty_where_values_hash - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) assert_equal({}, relation.where_values_hash) end def test_has_values - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) relation.where!(id: 10) assert_equal({ "id" => 10 }, relation.where_values_hash) end def test_values_wrong_table - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) relation.where! Comment.arel_table[:id].eq(10) assert_equal({}, relation.where_values_hash) end def test_tree_is_not_traversed - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) left = relation.table[:id].eq(10) right = relation.table[:id].eq(10) - combine = left.and right + combine = left.or(right) relation.where! combine assert_equal({}, relation.where_values_hash) end def test_scope_for_create - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) assert_equal({}, relation.scope_for_create) end def test_create_with_value - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) relation.create_with_value = { hello: "world" } assert_equal({ "hello" => "world" }, relation.scope_for_create) end def test_create_with_value_with_wheres - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) assert_equal({}, relation.scope_for_create) relation.where!(id: 10) @@ -96,11 +95,11 @@ module ActiveRecord end def test_empty_scope - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) - assert relation.empty_scope? + relation = Relation.new(Post) + assert_predicate relation, :empty_scope? relation.merge!(relation) - assert relation.empty_scope? + assert_predicate relation, :empty_scope? end def test_bad_constants_raise_errors @@ -110,31 +109,31 @@ module ActiveRecord end def test_empty_eager_loading? - relation = Relation.new(FakeKlass, :b, nil) - assert !relation.eager_loading? + relation = Relation.new(FakeKlass) + assert_not_predicate relation, :eager_loading? end def test_eager_load_values - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) relation.eager_load! :b - assert relation.eager_loading? + assert_predicate relation, :eager_loading? end def test_references_values - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) assert_equal [], relation.references_values relation = relation.references(:foo).references(:omg, :lol) assert_equal ["foo", "omg", "lol"], relation.references_values end def test_references_values_dont_duplicate - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) relation = relation.references(:foo).references(:foo) assert_equal ["foo"], relation.references_values end test "merging a hash into a relation" do - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder) + relation = Relation.new(Post) relation = relation.merge where: { name: :lol }, readonly: true assert_equal({ "name" => :lol }, relation.where_clause.to_h) @@ -142,7 +141,7 @@ module ActiveRecord end test "merging an empty hash into a relation" do - assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause + assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass).merge({}).where_clause end test "merging a hash with unknown keys raises" do @@ -150,7 +149,7 @@ module ActiveRecord end test "merging nil or false raises" do - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) e = assert_raises(ArgumentError) do relation = relation.merge nil @@ -166,7 +165,7 @@ module ActiveRecord end test "#values returns a dup of the values" do - relation = Relation.new(Post, Post.arel_table, Post.predicate_builder).where!(name: :foo) + relation = Relation.new(Post).where!(name: :foo) values = relation.values values[:where] = nil @@ -174,7 +173,7 @@ module ActiveRecord end test "relations can be created with a values hash" do - relation = Relation.new(FakeKlass, :b, nil, select: [:foo]) + relation = Relation.new(FakeKlass, values: { select: [:foo] }) assert_equal [:foo], relation.select_values end @@ -186,13 +185,13 @@ module ActiveRecord end end - relation = Relation.new(klass, :b, nil) + relation = Relation.new(klass) relation.merge!(where: ["foo = ?", "bar"]) assert_equal Relation::WhereClause.new(["foo = bar"]), relation.where_clause end def test_merging_readonly_false - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) readonly_false_relation = relation.readonly(false) # test merging in both directions assert_equal false, relation.merge(readonly_false_relation).readonly_value @@ -236,17 +235,17 @@ module ActiveRecord def test_merge_raises_with_invalid_argument assert_raises ArgumentError do - relation = Relation.new(FakeKlass, :b, nil) + relation = Relation.new(FakeKlass) relation.merge(true) end end def test_respond_to_for_non_selected_element post = Post.select(:title).first - assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception" + assert_not_respond_to post, :body, "post should not respond_to?(:body) since invoking it raises exception" silence_warnings { post = Post.select("'title' as post_title").first } - assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception" + assert_not_respond_to post, :title, "post should not respond_to?(:body) since invoking it raises exception" end def test_select_quotes_when_using_from_clause diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 50ad1d5b26..bc7c54dbe0 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -56,7 +56,7 @@ class RelationTest < ActiveRecord::TestCase def test_dynamic_finder x = Post.where("author_id = ?", 1) - assert x.klass.respond_to?(:find_by_id), "@klass should handle dynamic finders" + assert_respond_to x.klass, :find_by_id end def test_multivalue_where @@ -98,7 +98,7 @@ class RelationTest < ActiveRecord::TestCase 2.times { assert_equal 5, topics.to_a.size } end - assert topics.loaded? + assert_predicate topics, :loaded? end def test_scoped_first @@ -108,7 +108,7 @@ class RelationTest < ActiveRecord::TestCase 2.times { assert_equal "The First Topic", topics.first.title } end - assert ! topics.loaded? + assert_not_predicate topics, :loaded? end def test_loaded_first @@ -119,7 +119,7 @@ class RelationTest < ActiveRecord::TestCase assert_equal "The First Topic", topics.first.title end - assert topics.loaded? + assert_predicate topics, :loaded? end def test_loaded_first_with_limit @@ -131,7 +131,7 @@ class RelationTest < ActiveRecord::TestCase "The Second Topic of the day"], topics.first(2).map(&:title) end - assert topics.loaded? + assert_predicate topics, :loaded? end def test_first_get_more_than_available @@ -152,14 +152,14 @@ class RelationTest < ActiveRecord::TestCase 2.times { topics.to_a } end - assert topics.loaded? + assert_predicate topics, :loaded? original_size = topics.to_a.size Topic.create! title: "fake" assert_queries(1) { topics.reload } assert_equal original_size + 1, topics.size - assert topics.loaded? + assert_predicate topics, :loaded? end def test_finding_with_subquery @@ -501,12 +501,12 @@ class RelationTest < ActiveRecord::TestCase relation = Topic.all ["find_by_title", "find_by_title_and_author_name"].each do |method| - assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}" + assert_respond_to relation, method end end def test_respond_to_class_methods_and_scopes - assert Topic.all.respond_to?(:by_lifo) + assert_respond_to Topic.all, :by_lifo end def test_find_with_readonly_option @@ -601,7 +601,7 @@ class RelationTest < ActiveRecord::TestCase reader = Reader.create! post_id: post.id, person_id: 1 comment = Comment.create! post_id: post.id, body: "body" - assert !comment.respond_to?(:readers) + assert_not_respond_to comment, :readers post_rel = Post.preload(:readers).joins(:readers).where(title: "Uhuu") result_comment = Comment.joins(:post).merge(post_rel).to_a.first @@ -722,7 +722,7 @@ class RelationTest < ActiveRecord::TestCase def test_find_in_empty_array authors = Author.all.where(id: []) - assert authors.to_a.blank? + assert_predicate authors.to_a, :blank? end def test_where_with_ar_object @@ -780,8 +780,6 @@ class RelationTest < ActiveRecord::TestCase def test_find_all_using_where_with_relation david = authors(:david) - # switching the lines below would succeed in current rails - # assert_queries(2) { assert_queries(1) { relation = Author.where(id: Author.where(id: david.id)) assert_equal [david], relation.to_a @@ -820,8 +818,6 @@ class RelationTest < ActiveRecord::TestCase def test_find_all_using_where_with_relation_and_alternate_primary_key cool_first = minivans(:cool_first) - # switching the lines below would succeed in current rails - # assert_queries(2) { assert_queries(1) { relation = Minivan.where(minivan_id: Minivan.where(name: cool_first.name)) assert_equal [cool_first], relation.to_a @@ -867,19 +863,19 @@ class RelationTest < ActiveRecord::TestCase # Force load assert_equal [authors(:david)], davids.to_a - assert davids.loaded? + assert_predicate davids, :loaded? assert_difference("Author.count", -1) { davids.destroy_all } assert_equal [], davids.to_a - assert davids.loaded? + assert_predicate davids, :loaded? end def test_delete_all davids = Author.where(name: "David") assert_difference("Author.count", -1) { davids.delete_all } - assert ! davids.loaded? + assert_not_predicate davids, :loaded? end def test_delete_all_loaded @@ -887,20 +883,18 @@ class RelationTest < ActiveRecord::TestCase # Force load assert_equal [authors(:david)], davids.to_a - assert davids.loaded? + assert_predicate davids, :loaded? assert_difference("Author.count", -1) { davids.delete_all } assert_equal [], davids.to_a - assert davids.loaded? + assert_predicate davids, :loaded? end def test_delete_all_with_unpermitted_relation_raises_error - assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all } assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all } - assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all } end def test_select_with_aggregates @@ -908,9 +902,9 @@ class RelationTest < ActiveRecord::TestCase assert_equal 11, posts.count(:all) assert_equal 11, posts.size - assert posts.any? - assert posts.many? - assert_not posts.empty? + assert_predicate posts, :any? + assert_predicate posts, :many? + assert_not_empty posts end def test_select_takes_a_variable_list_of_args @@ -956,7 +950,7 @@ class RelationTest < ActiveRecord::TestCase assert_equal author.posts.where(author_id: author.id).size, posts.count assert_equal 0, author.posts.where(author_id: another_author.id).size - assert author.posts.where(author_id: another_author.id).empty? + assert_empty author.posts.where(author_id: another_author.id) end def test_count_with_distinct @@ -969,6 +963,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal 11, posts.distinct(false).select(:comments_count).count end + def test_size_with_distinct + posts = Post.distinct.select(:author_id, :comments_count) + assert_queries(1) { assert_equal 8, posts.size } + assert_queries(1) { assert_equal 8, posts.load.size } + end + + def test_size_with_eager_loading_and_custom_order + posts = Post.includes(:comments).order("comments.id") + assert_queries(1) { assert_equal 11, posts.size } + assert_queries(1) { assert_equal 11, posts.load.size } + end + def test_update_all_with_scope tag = Tag.first Post.tagged_with(tag.id).update_all title: "rofl" @@ -1000,7 +1006,7 @@ class RelationTest < ActiveRecord::TestCase posts = Post.all assert_queries(1) { assert_equal 11, posts.size } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? best_posts = posts.where(comments_count: 0) best_posts.load # force load @@ -1011,7 +1017,7 @@ class RelationTest < ActiveRecord::TestCase posts = Post.limit(10) assert_queries(1) { assert_equal 10, posts.size } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? best_posts = posts.where(comments_count: 0) best_posts.load # force load @@ -1022,7 +1028,7 @@ class RelationTest < ActiveRecord::TestCase posts = Post.limit(0) assert_no_queries { assert_equal 0, posts.size } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? posts.load # force load assert_no_queries { assert_equal 0, posts.size } @@ -1032,7 +1038,7 @@ class RelationTest < ActiveRecord::TestCase posts = Post.limit(0) assert_no_queries { assert_equal true, posts.empty? } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? end def test_count_complex_chained_relations @@ -1046,11 +1052,11 @@ class RelationTest < ActiveRecord::TestCase posts = Post.all assert_queries(1) { assert_equal false, posts.empty? } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? no_posts = posts.where(title: "") assert_queries(1) { assert_equal true, no_posts.empty? } - assert ! no_posts.loaded? + assert_not_predicate no_posts, :loaded? best_posts = posts.where(comments_count: 0) best_posts.load # force load @@ -1061,11 +1067,11 @@ class RelationTest < ActiveRecord::TestCase posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0") assert_queries(1) { assert_equal false, posts.empty? } - assert ! posts.loaded? + assert_not_predicate posts, :loaded? no_posts = posts.where(title: "") assert_queries(1) { assert_equal true, no_posts.empty? } - assert ! no_posts.loaded? + assert_not_predicate no_posts, :loaded? end def test_any @@ -1081,13 +1087,13 @@ class RelationTest < ActiveRecord::TestCase assert_queries(3) do assert posts.any? # Uses COUNT() - assert ! posts.where(id: nil).any? + assert_not_predicate posts.where(id: nil), :any? assert posts.any? { |p| p.id > 0 } assert ! posts.any? { |p| p.id <= 0 } end - assert posts.loaded? + assert_predicate posts, :loaded? end def test_many @@ -1099,14 +1105,14 @@ class RelationTest < ActiveRecord::TestCase assert ! posts.many? { |p| p.id < 2 } end - assert posts.loaded? + assert_predicate posts, :loaded? end def test_many_with_limits posts = Post.all - assert posts.many? - assert ! posts.limit(1).many? + assert_predicate posts, :many? + assert_not_predicate posts.limit(1), :many? end def test_none? @@ -1115,14 +1121,14 @@ class RelationTest < ActiveRecord::TestCase assert ! posts.none? # Uses COUNT() end - assert ! posts.loaded? + assert_not_predicate posts, :loaded? assert_queries(1) do assert posts.none? { |p| p.id < 0 } assert ! posts.none? { |p| p.id == 1 } end - assert posts.loaded? + assert_predicate posts, :loaded? end def test_one @@ -1131,14 +1137,14 @@ class RelationTest < ActiveRecord::TestCase assert ! posts.one? # Uses COUNT() end - assert ! posts.loaded? + assert_not_predicate posts, :loaded? assert_queries(1) do assert ! posts.one? { |p| p.id < 3 } assert posts.one? { |p| p.id == 1 } end - assert posts.loaded? + assert_predicate posts, :loaded? end def test_to_a_should_dup_target @@ -1171,10 +1177,10 @@ class RelationTest < ActiveRecord::TestCase sparrow = birds.create assert_kind_of Bird, sparrow - assert !sparrow.persisted? + assert_not_predicate sparrow, :persisted? hen = birds.where(name: "hen").create - assert hen.persisted? + assert_predicate hen, :persisted? assert_equal "hen", hen.name end @@ -1185,34 +1191,43 @@ class RelationTest < ActiveRecord::TestCase hen = birds.where(name: "hen").create! assert_kind_of Bird, hen - assert hen.persisted? + assert_predicate hen, :persisted? assert_equal "hen", hen.name end + def test_create_with_polymorphic_association + author = authors(:david) + post = posts(:welcome) + comment = Comment.where(post: post, author: author).create!(body: "hello") + + assert_equal author, comment.author + assert_equal post, comment.post + end + def test_first_or_create parrot = Bird.where(color: "green").first_or_create(name: "parrot") assert_kind_of Bird, parrot - assert parrot.persisted? + assert_predicate parrot, :persisted? assert_equal "parrot", parrot.name assert_equal "green", parrot.color same_parrot = Bird.where(color: "green").first_or_create(name: "parakeet") assert_kind_of Bird, same_parrot - assert same_parrot.persisted? + assert_predicate same_parrot, :persisted? assert_equal parrot, same_parrot end def test_first_or_create_with_no_parameters parrot = Bird.where(color: "green").first_or_create assert_kind_of Bird, parrot - assert !parrot.persisted? + assert_not_predicate parrot, :persisted? assert_equal "green", parrot.color end def test_first_or_create_with_block parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot - assert parrot.persisted? + assert_predicate parrot, :persisted? assert_equal "green", parrot.color assert_equal "parrot", parrot.name @@ -1233,13 +1248,13 @@ class RelationTest < ActiveRecord::TestCase def test_first_or_create_bang_with_valid_options parrot = Bird.where(color: "green").first_or_create!(name: "parrot") assert_kind_of Bird, parrot - assert parrot.persisted? + assert_predicate parrot, :persisted? assert_equal "parrot", parrot.name assert_equal "green", parrot.color same_parrot = Bird.where(color: "green").first_or_create!(name: "parakeet") assert_kind_of Bird, same_parrot - assert same_parrot.persisted? + assert_predicate same_parrot, :persisted? assert_equal parrot, same_parrot end @@ -1254,7 +1269,7 @@ class RelationTest < ActiveRecord::TestCase def test_first_or_create_bang_with_valid_block parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot - assert parrot.persisted? + assert_predicate parrot, :persisted? assert_equal "green", parrot.color assert_equal "parrot", parrot.name @@ -1285,9 +1300,9 @@ class RelationTest < ActiveRecord::TestCase def test_first_or_initialize parrot = Bird.where(color: "green").first_or_initialize(name: "parrot") assert_kind_of Bird, parrot - assert !parrot.persisted? - assert parrot.valid? - assert parrot.new_record? + assert_not_predicate parrot, :persisted? + assert_predicate parrot, :valid? + assert_predicate parrot, :new_record? assert_equal "parrot", parrot.name assert_equal "green", parrot.color end @@ -1295,18 +1310,18 @@ class RelationTest < ActiveRecord::TestCase def test_first_or_initialize_with_no_parameters parrot = Bird.where(color: "green").first_or_initialize assert_kind_of Bird, parrot - assert !parrot.persisted? - assert !parrot.valid? - assert parrot.new_record? + assert_not_predicate parrot, :persisted? + assert_not_predicate parrot, :valid? + assert_predicate parrot, :new_record? assert_equal "green", parrot.color end def test_first_or_initialize_with_block parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" } assert_kind_of Bird, parrot - assert !parrot.persisted? - assert parrot.valid? - assert parrot.new_record? + assert_not_predicate parrot, :persisted? + assert_predicate parrot, :valid? + assert_predicate parrot, :new_record? assert_equal "green", parrot.color assert_equal "parrot", parrot.name end @@ -1315,7 +1330,7 @@ class RelationTest < ActiveRecord::TestCase assert_nil Bird.find_by(name: "bob") bird = Bird.find_or_create_by(name: "bob") - assert bird.persisted? + assert_predicate bird, :persisted? assert_equal bird, Bird.find_or_create_by(name: "bob") end @@ -1324,7 +1339,7 @@ class RelationTest < ActiveRecord::TestCase assert_nil Bird.find_by(name: "bob") bird = Bird.create_with(color: "green").find_or_create_by(name: "bob") - assert bird.persisted? + assert_predicate bird, :persisted? assert_equal "green", bird.color assert_equal bird, Bird.create_with(color: "blue").find_or_create_by(name: "bob") @@ -1338,7 +1353,7 @@ class RelationTest < ActiveRecord::TestCase assert_nil Bird.find_by(name: "bob") bird = Bird.find_or_initialize_by(name: "bob") - assert bird.new_record? + assert_predicate bird, :new_record? bird.save! assert_equal bird, Bird.find_or_initialize_by(name: "bob") @@ -1513,10 +1528,10 @@ class RelationTest < ActiveRecord::TestCase def test_doesnt_add_having_values_if_options_are_blank scope = Post.having("") - assert scope.having_clause.empty? + assert_empty scope.having_clause scope = Post.having([]) - assert scope.having_clause.empty? + assert_empty scope.having_clause end def test_having_with_binds_for_both_where_and_having @@ -1542,13 +1557,13 @@ class RelationTest < ActiveRecord::TestCase def test_references_triggers_eager_loading scope = Post.includes(:comments) - assert !scope.eager_loading? - assert scope.references(:comments).eager_loading? + assert_not_predicate scope, :eager_loading? + assert_predicate scope.references(:comments), :eager_loading? end def test_references_doesnt_trigger_eager_loading_if_reference_not_included scope = Post.references(:comments) - assert !scope.eager_loading? + assert_not_predicate scope, :eager_loading? end def test_automatically_added_where_references @@ -1656,7 +1671,7 @@ class RelationTest < ActiveRecord::TestCase # count always trigger the COUNT query. assert_queries(1) { topics.count } - assert topics.loaded? + assert_predicate topics, :loaded? end test "find_by with hash conditions returns the first matching record" do @@ -1831,7 +1846,7 @@ class RelationTest < ActiveRecord::TestCase test "delegations do not leak to other classes" do Topic.all.by_lifo assert Topic.all.class.method_defined?(:by_lifo) - assert !Post.all.respond_to?(:by_lifo) + assert_not_respond_to Post.all, :by_lifo end def test_unscope_with_subquery @@ -1856,7 +1871,7 @@ class RelationTest < ActiveRecord::TestCase def test_locked_should_not_build_arel posts = Post.locked - assert posts.locked? + assert_predicate posts, :locked? assert_nothing_raised { posts.lock!(false) } end @@ -1925,6 +1940,10 @@ class RelationTest < ActiveRecord::TestCase table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias) predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata) - ActiveRecord::Relation.create(Post, table_alias, predicate_builder) + ActiveRecord::Relation.create( + Post, + table: table_alias, + predicate_builder: predicate_builder + ) end end diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb index 0214dbec17..e32605fd11 100644 --- a/activerecord/test/cases/reserved_word_test.rb +++ b/activerecord/test/cases/reserved_word_test.rb @@ -39,7 +39,7 @@ class ReservedWordTest < ActiveRecord::TestCase t.string :order t.belongs_to :select end - @connection.create_table :values, force: true do |t| + @connection.create_table :values, primary_key: :as, force: true do |t| t.belongs_to :group end end @@ -88,6 +88,13 @@ class ReservedWordTest < ActiveRecord::TestCase assert_equal x, Group.find(x.id) end + def test_delete_all_with_subselect + create_test_fixtures :values + assert_equal 1, Values.order(:as).limit(1).offset(1).delete_all + assert_raise(ActiveRecord::RecordNotFound) { Values.find(2) } + assert Values.find(1) + end + def test_has_one_associations create_test_fixtures :group, :values v = Group.find(1).values @@ -109,7 +116,7 @@ class ReservedWordTest < ActiveRecord::TestCase end def test_activerecord_introspection - assert Group.table_exists? + assert_predicate Group, :table_exists? assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort end diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 85a555fe35..778cf86ac3 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -4,6 +4,7 @@ require "cases/helper" require "models/binary" require "models/author" require "models/post" +require "models/customer" class SanitizeTest < ActiveRecord::TestCase def setup @@ -11,30 +12,30 @@ class SanitizeTest < ActiveRecord::TestCase def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") - assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"]) - assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars]) + assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi"]) + assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper") - assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"]) - assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars]) + assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper"]) + assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper".mb_chars]) end def test_sanitize_sql_array_handles_bind_variables quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") - assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"]) - assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".mb_chars]) + assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi"]) + assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"]) - assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars]) + assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper"]) + assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper".mb_chars]) end def test_sanitize_sql_array_handles_named_bind_variables quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") - assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi"]) - assert_equal "name=#{quoted_bambi} AND id=1", Binary.send(:sanitize_sql_array, ["name=:name AND id=:id", name: "Bambi", id: 1]) + assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=:name", name: "Bambi"]) + assert_equal "name=#{quoted_bambi} AND id=1", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi\nand\nThumper"]) - assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name AND name2=:name", name: "Bambi\nand\nThumper"]) + assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name", name: "Bambi\nand\nThumper"]) + assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name AND name2=:name", name: "Bambi\nand\nThumper"]) end def test_sanitize_sql_array_handles_relations @@ -43,42 +44,50 @@ class SanitizeTest < ActiveRecord::TestCase sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i - select_author_sql = Post.send(:sanitize_sql_array, ["id in (?)", david_posts]) + select_author_sql = Post.sanitize_sql_array(["id in (?)", david_posts]) assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for bind variables") - select_author_sql = Post.send(:sanitize_sql_array, ["id in (:post_ids)", post_ids: david_posts]) + select_author_sql = Post.sanitize_sql_array(["id in (:post_ids)", post_ids: david_posts]) assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for named bind variables") end def test_sanitize_sql_array_handles_empty_statement - select_author_sql = Post.send(:sanitize_sql_array, [""]) + select_author_sql = Post.sanitize_sql_array([""]) assert_equal("", select_author_sql) end def test_sanitize_sql_like - assert_equal '100\%', Binary.send(:sanitize_sql_like, "100%") - assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, "snake_cased_string") - assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint') - assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42") + assert_equal '100\%', Binary.sanitize_sql_like("100%") + assert_equal 'snake\_cased\_string', Binary.sanitize_sql_like("snake_cased_string") + assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint') + assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42") end def test_sanitize_sql_like_with_custom_escape_character - assert_equal "100!%", Binary.send(:sanitize_sql_like, "100%", "!") - assert_equal "snake!_cased!_string", Binary.send(:sanitize_sql_like, "snake_cased_string", "!") - assert_equal "great!!", Binary.send(:sanitize_sql_like, "great!", "!") - assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', "!") - assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42", "!") + assert_equal "100!%", Binary.sanitize_sql_like("100%", "!") + assert_equal "snake!_cased!_string", Binary.sanitize_sql_like("snake_cased_string", "!") + assert_equal "great!!", Binary.sanitize_sql_like("great!", "!") + assert_equal 'C:\\Programs\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint', "!") + assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42", "!") end def test_sanitize_sql_like_example_use_case searchable_post = Class.new(Post) do - def self.search(term) + def self.search_as_method(term) where("title LIKE ?", sanitize_sql_like(term, "!")) end + + scope :search_as_scope, -> (term) { + where("title LIKE ?", sanitize_sql_like(term, "!")) + } + end + + assert_sql(/LIKE '20!% !_reduction!_!!'/) do + searchable_post.search_as_method("20% _reduction_!").to_a end assert_sql(/LIKE '20!% !_reduction!_!!'/) do - searchable_post.search("20% _reduction_!").to_a + searchable_post.search_as_scope("20% _reduction_!").to_a end end @@ -159,6 +168,12 @@ class SanitizeTest < ActiveRecord::TestCase assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call end + def test_deprecated_expand_hash_conditions_for_aggregates + assert_deprecated do + assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50))) + end + end + private def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index fdfeabaa3b..0804de1fb3 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -224,6 +224,18 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_unscope_left_outer_joins + expected = Developer.all.collect(&:name) + received = Developer.left_outer_joins(:projects).select(:id).unscope(:left_outer_joins, :select).collect(&:name) + assert_equal expected, received + end + + def test_unscope_left_joins + expected = Developer.all.collect(&:name) + received = Developer.left_joins(:projects).select(:id).unscope(:left_joins, :select).collect(&:name) + assert_equal expected, received + end + def test_unscope_includes expected = Developer.all.collect(&:name) received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name) @@ -290,8 +302,8 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_unscope_merging merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where)) - assert merged.where_clause.empty? - assert !merged.where(name: "Jon").where_clause.empty? + assert_empty merged.where_clause + assert_not_empty merged.where(name: "Jon").where_clause end def test_order_in_default_scope_should_not_prevail diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 17d3f27bb1..ea71a5ce28 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -13,7 +13,7 @@ class NamedScopingTest < ActiveRecord::TestCase fixtures :posts, :authors, :topics, :comments, :author_addresses def test_implements_enumerable - assert !Topic.all.empty? + assert_not_empty Topic.all assert_equal Topic.all.to_a, Topic.base assert_equal Topic.all.to_a, Topic.base.to_a @@ -40,7 +40,7 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_delegates_finds_and_calculations_to_the_base_class - assert !Topic.all.empty? + assert_not_empty Topic.all assert_equal Topic.all.to_a, Topic.base.to_a assert_equal Topic.first, Topic.base.first @@ -65,13 +65,13 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy - assert Topic.approved.respond_to?(:limit) - assert Topic.approved.respond_to?(:count) - assert Topic.approved.respond_to?(:length) + assert_respond_to Topic.approved, :limit + assert_respond_to Topic.approved, :count + assert_respond_to Topic.approved, :length end def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified - assert !Topic.all.merge!(where: { approved: true }).to_a.empty? + assert_not_empty Topic.all.merge!(where: { approved: true }).to_a assert_equal Topic.all.merge!(where: { approved: true }).to_a, Topic.approved assert_equal Topic.where(approved: true).count, Topic.approved.count @@ -87,7 +87,7 @@ class NamedScopingTest < ActiveRecord::TestCase assert_equal((approved = Topic.all.merge!(where: { approved: true }).to_a), Topic.approved) assert_equal((replied = Topic.all.merge!(where: "replies_count > 0").to_a), Topic.replied) assert !(approved == replied) - assert !(approved & replied).empty? + assert_not_empty (approved & replied) assert_equal approved & replied, Topic.approved.replied end @@ -115,7 +115,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_has_many_associations_have_access_to_scopes assert_not_equal Post.containing_the_letter_a, authors(:david).posts - assert !Post.containing_the_letter_a.empty? + assert_not_empty Post.containing_the_letter_a expected = authors(:david).posts & Post.containing_the_letter_a assert_equal expected.sort_by(&:id), authors(:david).posts.containing_the_letter_a.sort_by(&:id) @@ -128,15 +128,15 @@ class NamedScopingTest < ActiveRecord::TestCase def test_has_many_through_associations_have_access_to_scopes assert_not_equal Comment.containing_the_letter_e, authors(:david).comments - assert !Comment.containing_the_letter_e.empty? + assert_not_empty Comment.containing_the_letter_e expected = authors(:david).comments & Comment.containing_the_letter_e assert_equal expected.sort_by(&:id), authors(:david).comments.containing_the_letter_e.sort_by(&:id) end def test_scopes_honor_current_scopes_from_when_defined - assert !Post.ranked_by_comments.limit_by(5).empty? - assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty? + assert_not_empty Post.ranked_by_comments.limit_by(5) + assert_not_empty authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed @@ -168,14 +168,14 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_active_records_have_scope_named__all__ - assert !Topic.all.empty? + assert_not_empty Topic.all assert_equal Topic.all.to_a, Topic.base end def test_active_records_have_scope_named__scoped__ scope = Topic.where("content LIKE '%Have%'") - assert !scope.empty? + assert_not_empty scope assert_equal scope, Topic.all.merge!(where: "content LIKE '%Have%'") end @@ -228,9 +228,9 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_model_class_should_respond_to_any - assert Topic.any? + assert_predicate Topic, :any? Topic.delete_all - assert !Topic.any? + assert_not_predicate Topic, :any? end def test_many_should_not_load_results @@ -259,22 +259,22 @@ class NamedScopingTest < ActiveRecord::TestCase def test_many_should_return_false_if_none_or_one topics = Topic.base.where(id: 0) - assert !topics.many? + assert_not_predicate topics, :many? topics = Topic.base.where(id: 1) - assert !topics.many? + assert_not_predicate topics, :many? end def test_many_should_return_true_if_more_than_one - assert Topic.base.many? + assert_predicate Topic.base, :many? end def test_model_class_should_respond_to_many Topic.delete_all - assert !Topic.many? + assert_not_predicate Topic, :many? Topic.create! - assert !Topic.many? + assert_not_predicate Topic, :many? Topic.create! - assert Topic.many? + assert_predicate Topic, :many? end def test_should_build_on_top_of_scope @@ -423,16 +423,16 @@ class NamedScopingTest < ActiveRecord::TestCase def test_chaining_applies_last_conditions_when_creating post = Topic.rejected.new - assert !post.approved? + assert_not_predicate post, :approved? post = Topic.rejected.approved.new - assert post.approved? + assert_predicate post, :approved? post = Topic.approved.rejected.new - assert !post.approved? + assert_not_predicate post, :approved? post = Topic.approved.rejected.approved.new - assert post.approved? + assert_predicate post, :approved? end def test_chaining_combines_conditions_when_searching @@ -498,7 +498,7 @@ class NamedScopingTest < ActiveRecord::TestCase def test_index_on_scope approved = Topic.approved.order("id ASC") assert_equal topics(:second), approved[0] - assert approved.loaded? + assert_predicate approved, :loaded? end def test_nested_scopes_queries_size @@ -578,16 +578,16 @@ class NamedScopingTest < ActiveRecord::TestCase end def test_model_class_should_respond_to_none - assert !Topic.none? + assert_not_predicate Topic, :none? Topic.delete_all - assert Topic.none? + assert_predicate Topic, :none? end def test_model_class_should_respond_to_one - assert !Topic.one? + assert_not_predicate Topic, :one? Topic.delete_all - assert !Topic.one? + assert_not_predicate Topic, :one? Topic.create! - assert Topic.one? + assert_predicate Topic, :one? end end diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 116f8e83aa..5c86bc892d 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -213,21 +213,21 @@ class RelationScopingTest < ActiveRecord::TestCase def test_current_scope_does_not_pollute_sibling_subclasses Comment.none.scoping do - assert_not SpecialComment.all.any? - assert_not VerySpecialComment.all.any? - assert_not SubSpecialComment.all.any? + assert_not_predicate SpecialComment.all, :any? + assert_not_predicate VerySpecialComment.all, :any? + assert_not_predicate SubSpecialComment.all, :any? end SpecialComment.none.scoping do - assert Comment.all.any? - assert VerySpecialComment.all.any? - assert_not SubSpecialComment.all.any? + assert_predicate Comment.all, :any? + assert_predicate VerySpecialComment.all, :any? + assert_not_predicate SubSpecialComment.all, :any? end SubSpecialComment.none.scoping do - assert Comment.all.any? - assert VerySpecialComment.all.any? - assert SpecialComment.all.any? + assert_predicate Comment.all, :any? + assert_predicate VerySpecialComment.all, :any? + assert_predicate SpecialComment.all, :any? end end @@ -334,7 +334,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase def test_nested_exclusive_scope_for_create comment = Comment.create_with(body: "Hey guys, nested scopes are broken. Please fix!").scoping do Comment.unscoped.create_with(post_id: 1).scoping do - assert Comment.new.body.blank? + assert_predicate Comment.new.body, :blank? Comment.create body: "Hey guys" end end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 32dafbd458..7de5429cbb 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -279,7 +279,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase topic = Topic.new(content: nil) - assert_not topic.content_changed? + assert_not_predicate topic, :content_changed? end def test_classes_without_no_arg_constructors_are_not_supported @@ -349,7 +349,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase topic = model.create!(foo: "bar") topic.foo - refute topic.changed? + assert_not_predicate topic, :changed? end def test_serialized_attribute_works_under_concurrent_initial_access diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb index 1f715e41a6..ad6cd198e2 100644 --- a/activerecord/test/cases/statement_cache_test.rb +++ b/activerecord/test/cases/statement_cache_test.rb @@ -12,7 +12,6 @@ module ActiveRecord @connection = ActiveRecord::Base.connection end - #Cache v 1.1 tests def test_statement_cache Book.create(name: "my book") Book.create(name: "my other book") @@ -51,8 +50,6 @@ module ActiveRecord assert_equal("my other book", b.name) end - #End - def test_statement_cache_with_simple_statement cache = ActiveRecord::StatementCache.create(Book.connection) do |params| Book.where(name: "my book").where("author_id > 3") diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index ebf4016960..a30d13632a 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -45,7 +45,7 @@ class StoreTest < ActiveRecord::TestCase test "updating the store will mark it as changed" do @john.color = "red" - assert @john.settings_changed? + assert_predicate @john, :settings_changed? end test "updating the store populates the changed array correctly" do @@ -56,7 +56,7 @@ class StoreTest < ActiveRecord::TestCase test "updating the store won't mark it as changed if an attribute isn't changed" do @john.color = @john.color - assert !@john.settings_changed? + assert_not_predicate @john, :settings_changed? end test "object initialization with not nullable column" do @@ -137,7 +137,7 @@ class StoreTest < ActiveRecord::TestCase test "updating the store will mark it as changed encoded with JSON" do @john.height = "short" - assert @john.json_data_changed? + assert_predicate @john, :json_data_changed? end test "object initialization with not nullable column encoded with JSON" do diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 5a094ead42..21226352ff 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -28,15 +28,32 @@ module ActiveRecord class DatabaseTasksUtilsTask < ActiveRecord::TestCase def test_raises_an_error_when_called_with_protected_environment - ActiveRecord::Migrator.stubs(:current_version).returns(1) + ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1) - protected_environments = ActiveRecord::Base.protected_environments.dup - current_env = ActiveRecord::Migrator.current_environment + protected_environments = ActiveRecord::Base.protected_environments + current_env = ActiveRecord::Base.connection.migration_context.current_environment assert_not_includes protected_environments, current_env # Assert no error ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! - ActiveRecord::Base.protected_environments << current_env + ActiveRecord::Base.protected_environments = [current_env] + assert_raise(ActiveRecord::ProtectedEnvironmentError) do + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + end + ensure + ActiveRecord::Base.protected_environments = protected_environments + end + + def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol + ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1) + + protected_environments = ActiveRecord::Base.protected_environments + current_env = ActiveRecord::Base.connection.migration_context.current_environment + assert_not_includes protected_environments, current_env + # Assert no error + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + + ActiveRecord::Base.protected_environments = [current_env.to_sym] assert_raise(ActiveRecord::ProtectedEnvironmentError) do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! end @@ -46,7 +63,7 @@ module ActiveRecord def test_raises_an_error_if_no_migrations_have_been_made ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false) - ActiveRecord::Migrator.stubs(:current_version).returns(1) + ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1) assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! @@ -93,6 +110,7 @@ module ActiveRecord ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, path) assert File.file?(path) ensure + ActiveRecord::Base.clear_cache! FileUtils.rm_rf(path) end end @@ -329,50 +347,92 @@ module ActiveRecord end end - class DatabaseTasksMigrateTest < ActiveRecord::TestCase - self.use_transactional_tests = false + if current_adapter?(:SQLite3Adapter) && !in_memory_db? + class DatabaseTasksMigrateTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + # Use a memory db here to avoid having to rollback at the end + setup do + migrations_path = MIGRATIONS_ROOT + "/valid" + file = ActiveRecord::Base.connection.raw_connection.filename + @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", + database: ":memory:", migrations_paths: migrations_path + source_db = SQLite3::Database.new file + dest_db = ActiveRecord::Base.connection.raw_connection + backup = SQLite3::Backup.new(dest_db, "main", source_db, "main") + backup.step(-1) + backup.finish + end - def setup - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = "custom/path" - end + teardown do + @conn.release_connection if @conn + ActiveRecord::Base.establish_connection :arunit + end - def teardown - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil - end + def test_migrate_set_and_unset_verbose_and_version_env_vars + verbose, version = ENV["VERBOSE"], ENV["VERSION"] + ENV["VERSION"] = "2" + ENV["VERBOSE"] = "false" - def test_migrate_receives_correct_env_vars - verbose, version = ENV["VERBOSE"], ENV["VERSION"] + # run down migration because it was already run on copied db + assert_empty capture_migration_output - ENV["VERBOSE"] = "false" - ENV["VERSION"] = "4" - ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4) - ActiveRecord::Migration.expects(:verbose=).with(false) - ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose) - ActiveRecord::Tasks::DatabaseTasks.migrate + ENV.delete("VERSION") + ENV.delete("VERBOSE") - ENV.delete("VERBOSE") - ENV.delete("VERSION") - ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil) - ActiveRecord::Migration.expects(:verbose=).with(true) - ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose) - ActiveRecord::Tasks::DatabaseTasks.migrate + # re-run up migration + assert_includes capture_migration_output, "migrating" + ensure + ENV["VERBOSE"], ENV["VERSION"] = verbose, version + end - ENV["VERBOSE"] = "" - ENV["VERSION"] = "" - ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil) - ActiveRecord::Migration.expects(:verbose=).with(true) - ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose) - ActiveRecord::Tasks::DatabaseTasks.migrate + def test_migrate_set_and_unset_empty_values_for_verbose_and_version_env_vars + verbose, version = ENV["VERBOSE"], ENV["VERSION"] - ENV["VERBOSE"] = "yes" - ENV["VERSION"] = "0" - ActiveRecord::Migrator.expects(:migrate).with("custom/path", 0) - ActiveRecord::Migration.expects(:verbose=).with(true) - ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose) - ActiveRecord::Tasks::DatabaseTasks.migrate - ensure - ENV["VERBOSE"], ENV["VERSION"] = verbose, version + ENV["VERSION"] = "2" + ENV["VERBOSE"] = "false" + + # run down migration because it was already run on copied db + assert_empty capture_migration_output + + ENV["VERBOSE"] = "" + ENV["VERSION"] = "" + + # re-run up migration + assert_includes capture_migration_output, "migrating" + ensure + ENV["VERBOSE"], ENV["VERSION"] = verbose, version + end + + def test_migrate_set_and_unset_nonsense_values_for_verbose_and_version_env_vars + verbose, version = ENV["VERBOSE"], ENV["VERSION"] + + # run down migration because it was already run on copied db + ENV["VERSION"] = "2" + ENV["VERBOSE"] = "false" + + assert_empty capture_migration_output + + ENV["VERBOSE"] = "yes" + ENV["VERSION"] = "2" + + # run no migration because 2 was already run + assert_empty capture_migration_output + ensure + ENV["VERBOSE"], ENV["VERSION"] = verbose, version + end + + private + def capture_migration_output + capture(:stdout) do + ActiveRecord::Tasks::DatabaseTasks.migrate + end + end end + end + + class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase + self.use_transactional_tests = false def test_migrate_raise_error_on_invalid_version_format version = ENV["VERSION"] diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 06a8693a7d..024b5bd8a1 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -77,6 +77,10 @@ module ActiveRecord model.reset_column_information model.column_names.include?(column_name.to_s) end + + def frozen_error_class + Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError + end end class PostgreSQLTestCase < TestCase @@ -112,7 +116,7 @@ module ActiveRecord # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im] mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im] - postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] + postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im] sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im] [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql| diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 5d3e2a175c..e95446c0a7 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -90,7 +90,7 @@ class TimestampTest < ActiveRecord::TestCase @developer.touch(:created_at) end - assert !@developer.created_at_changed? , "created_at should not be changed" + assert !@developer.created_at_changed?, "created_at should not be changed" assert !@developer.changed?, "record should not be changed" assert_not_equal previously_created_at, @developer.created_at assert_not_equal @previously_updated_at, @developer.updated_at @@ -139,13 +139,13 @@ class TimestampTest < ActiveRecord::TestCase def test_touching_a_no_touching_object Developer.no_touching do - assert @developer.no_touching? - assert !@owner.no_touching? + assert_predicate @developer, :no_touching? + assert_not_predicate @owner, :no_touching? @developer.touch end - assert !@developer.no_touching? - assert !@owner.no_touching? + assert_not_predicate @developer, :no_touching? + assert_not_predicate @owner, :no_touching? assert_equal @previously_updated_at, @developer.updated_at end @@ -162,26 +162,26 @@ class TimestampTest < ActiveRecord::TestCase def test_global_no_touching ActiveRecord::Base.no_touching do - assert @developer.no_touching? - assert @owner.no_touching? + assert_predicate @developer, :no_touching? + assert_predicate @owner, :no_touching? @developer.touch end - assert !@developer.no_touching? - assert !@owner.no_touching? + assert_not_predicate @developer, :no_touching? + assert_not_predicate @owner, :no_touching? assert_equal @previously_updated_at, @developer.updated_at end def test_no_touching_threadsafe Thread.new do Developer.no_touching do - assert @developer.no_touching? + assert_predicate @developer, :no_touching? sleep(1) end end - assert !@developer.no_touching? + assert_not_predicate @developer, :no_touching? end def test_no_touching_with_callbacks @@ -237,7 +237,7 @@ class TimestampTest < ActiveRecord::TestCase pet = Pet.new(owner: klass.new) pet.save! - assert pet.owner.new_record? + assert_predicate pet.owner, :new_record? end def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb index 1757031371..925a4609a2 100644 --- a/activerecord/test/cases/touch_later_test.rb +++ b/activerecord/test/cases/touch_later_test.rb @@ -13,7 +13,7 @@ class TouchLaterTest < ActiveRecord::TestCase def test_touch_laster_raise_if_non_persisted invoice = Invoice.new Invoice.transaction do - assert_not invoice.persisted? + assert_not_predicate invoice, :persisted? assert_raises(ActiveRecord::ActiveRecordError) do invoice.touch_later end @@ -23,7 +23,7 @@ class TouchLaterTest < ActiveRecord::TestCase def test_touch_later_dont_set_dirty_attributes invoice = Invoice.create! invoice.touch_later - assert_not invoice.changed? + assert_not_predicate invoice, :changed? end def test_touch_later_respects_no_touching_policy diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 1f370a80ee..1c7cec4dad 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -158,13 +158,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase def test_only_call_after_commit_on_top_level_transactions @first.after_commit_block { |r| r.history << :after_commit } - assert @first.history.empty? + assert_empty @first.history @first.transaction do @first.transaction(requires_new: true) do @first.touch end - assert @first.history.empty? + assert_empty @first.history end assert_equal [:after_commit], @first.history end @@ -518,7 +518,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase @topic.content = "foo" @topic.save! end - assert @topic.history.empty? + assert_empty @topic.history end def test_commit_run_transactions_callbacks_with_explicit_enrollment @@ -538,7 +538,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase @topic.save! raise ActiveRecord::Rollback end - assert @topic.history.empty? + assert_empty @topic.history end def test_rollback_run_transactions_callbacks_with_explicit_enrollment diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 5c8ae4d3cb..c70286d52a 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -20,22 +20,22 @@ class TransactionTest < ActiveRecord::TestCase def test_persisted_in_a_model_with_custom_primary_key_after_failed_save movie = Movie.create - assert !movie.persisted? + assert_not_predicate movie, :persisted? end def test_raise_after_destroy - assert_not @first.frozen? + assert_not_predicate @first, :frozen? assert_raises(RuntimeError) { Topic.transaction do @first.destroy - assert @first.frozen? + assert_predicate @first, :frozen? raise end } assert @first.reload - assert_not @first.frozen? + assert_not_predicate @first, :frozen? end def test_successful @@ -152,7 +152,7 @@ class TransactionTest < ActiveRecord::TestCase @first.approved = true e = assert_raises(RuntimeError) { @first.save } assert_equal "Make the transaction rollback", e.message - assert !Topic.find(1).approved? + assert_not_predicate Topic.find(1), :approved? end def test_rolling_back_in_a_callback_rollbacks_before_save @@ -186,7 +186,7 @@ class TransactionTest < ActiveRecord::TestCase author = Author.create! name: "foo" author.name = nil assert_not author.save - assert_not author.new_record? + assert_not_predicate author, :new_record? end def test_update_should_rollback_on_failure @@ -417,8 +417,8 @@ class TransactionTest < ActiveRecord::TestCase end end - assert @first.reload.approved? - assert !@second.reload.approved? + assert_predicate @first.reload, :approved? + assert_not_predicate @second.reload, :approved? end if Topic.connection.supports_savepoints? def test_force_savepoint_on_instance @@ -438,8 +438,8 @@ class TransactionTest < ActiveRecord::TestCase end end - assert @first.reload.approved? - assert !@second.reload.approved? + assert_predicate @first.reload, :approved? + assert_not_predicate @second.reload, :approved? end if Topic.connection.supports_savepoints? def test_no_savepoint_in_nested_transaction_without_force @@ -459,8 +459,8 @@ class TransactionTest < ActiveRecord::TestCase end end - assert !@first.reload.approved? - assert !@second.reload.approved? + assert_not_predicate @first.reload, :approved? + assert_not_predicate @second.reload, :approved? end if Topic.connection.supports_savepoints? def test_many_savepoints @@ -516,12 +516,12 @@ class TransactionTest < ActiveRecord::TestCase @first.approved = false @first.save! Topic.connection.rollback_to_savepoint("first") - assert @first.reload.approved? + assert_predicate @first.reload, :approved? @first.approved = false @first.save! Topic.connection.release_savepoint("first") - assert_not @first.reload.approved? + assert_not_predicate @first.reload, :approved? end end if Topic.connection.supports_savepoints? @@ -576,7 +576,7 @@ class TransactionTest < ActiveRecord::TestCase def test_rollback_when_saving_a_frozen_record topic = Topic.new(title: "test") topic.freeze - e = assert_raise(RuntimeError) { topic.save } + e = assert_raise(frozen_error_class) { topic.save } # Not good enough, but we can't do much # about it since there is no specific error # for frozen objects. @@ -663,8 +663,8 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end - assert_not reply.frozen? - assert_not topic.frozen? + assert_not_predicate reply, :frozen? + assert_not_predicate topic, :frozen? end def test_restore_id_after_rollback @@ -819,28 +819,28 @@ class TransactionTest < ActiveRecord::TestCase connection = Topic.connection transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction - assert transaction.open? - assert !transaction.state.rolledback? - assert !transaction.state.committed? + assert_predicate transaction, :open? + assert_not_predicate transaction.state, :rolledback? + assert_not_predicate transaction.state, :committed? transaction.rollback - assert transaction.state.rolledback? - assert !transaction.state.committed? + assert_predicate transaction.state, :rolledback? + assert_not_predicate transaction.state, :committed? end def test_transactions_state_from_commit connection = Topic.connection transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction - assert transaction.open? - assert !transaction.state.rolledback? - assert !transaction.state.committed? + assert_predicate transaction, :open? + assert_not_predicate transaction.state, :rolledback? + assert_not_predicate transaction.state, :committed? transaction.commit - assert !transaction.state.rolledback? - assert transaction.state.committed? + assert_not_predicate transaction.state, :rolledback? + assert_predicate transaction.state, :committed? end def test_set_state_method_is_deprecated @@ -929,7 +929,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase raise end rescue - assert !@first.reload.approved? + assert_not_predicate @first.reload, :approved? end end @@ -950,7 +950,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase end end - assert !@first.reload.approved? + assert_not_predicate @first.reload, :approved? end end if Topic.connection.supports_savepoints? diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb index 8c51b30fdd..9e7810a6a5 100644 --- a/activerecord/test/cases/type/string_test.rb +++ b/activerecord/test/cases/type/string_test.rb @@ -9,16 +9,16 @@ module ActiveRecord klass.table_name = "authors" author = klass.create!(name: "Sean") - assert_not author.changed? + assert_not_predicate author, :changed? author.name << " Griffin" - assert author.name_changed? + assert_predicate author, :name_changed? author.save! author.reload assert_equal "Sean Griffin", author.name - assert_not author.changed? + assert_not_predicate author, :changed? end end end diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb index a997f8be9c..8235a54d8a 100644 --- a/activerecord/test/cases/validations/absence_validation_test.rb +++ b/activerecord/test/cases/validations/absence_validation_test.rb @@ -13,8 +13,8 @@ class AbsenceValidationTest < ActiveRecord::TestCase validates_absence_of :name end - assert boy_klass.new.valid? - assert_not boy_klass.new(name: "Alex").valid? + assert_predicate boy_klass.new, :valid? + assert_not_predicate boy_klass.new(name: "Alex"), :valid? end def test_has_one_marked_for_destruction @@ -44,7 +44,7 @@ class AbsenceValidationTest < ActiveRecord::TestCase assert_not boy.valid?, "should not be valid if has_many association is present" i2.mark_for_destruction - assert boy.valid? + assert_predicate boy, :valid? end def test_does_not_call_to_a_on_associations @@ -65,11 +65,11 @@ class AbsenceValidationTest < ActiveRecord::TestCase Interest.validates_absence_of(:token) interest = Interest.create!(topic: "Thought Leadering") - assert interest.valid? + assert_predicate interest, :valid? interest.token = "tl" - assert interest.invalid? + assert_predicate interest, :invalid? end end end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 80fe375ae5..ce6d42b34b 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -16,14 +16,14 @@ class AssociationValidationTest < ActiveRecord::TestCase Reply.validates_presence_of(:content) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")] - assert !t.valid? - assert t.errors[:replies].any? + assert_not_predicate t, :valid? + assert_predicate t.errors[:replies], :any? assert_equal 1, r.errors.count # make sure all associated objects have been validated assert_equal 0, r2.errors.count assert_equal 1, r3.errors.count assert_equal 0, r4.errors.count r.content = r3.content = "non-empty" - assert t.valid? + assert_predicate t, :valid? end def test_validates_associated_one @@ -31,10 +31,10 @@ class AssociationValidationTest < ActiveRecord::TestCase Topic.validates_presence_of(:content) r = Reply.new("title" => "A reply", "content" => "with content!") r.topic = Topic.create("title" => "uhohuhoh") - assert !r.valid? - assert r.errors[:topic].any? + assert_not_predicate r, :valid? + assert_predicate r.errors[:topic], :any? r.topic.content = "non-empty" - assert r.valid? + assert_predicate r, :valid? end def test_validates_associated_marked_for_destruction @@ -42,9 +42,9 @@ class AssociationValidationTest < ActiveRecord::TestCase Reply.validates_presence_of(:content) t = Topic.new t.replies << Reply.new - assert t.invalid? + assert_predicate t, :invalid? t.replies.first.mark_for_destruction - assert t.valid? + assert_predicate t, :valid? end def test_validates_associated_without_marked_for_destruction @@ -56,7 +56,7 @@ class AssociationValidationTest < ActiveRecord::TestCase Topic.validates_associated(:replies) t = Topic.new t.define_singleton_method(:replies) { [reply.new] } - assert t.valid? + assert_predicate t, :valid? end def test_validates_associated_with_custom_message_using_quotes @@ -71,11 +71,11 @@ class AssociationValidationTest < ActiveRecord::TestCase def test_validates_associated_missing Reply.validates_presence_of(:topic) r = Reply.create("title" => "A reply", "content" => "with content!") - assert !r.valid? - assert r.errors[:topic].any? + assert_not_predicate r, :valid? + assert_predicate r.errors[:topic], :any? r.topic = Topic.first - assert r.valid? + assert_predicate r, :valid? end def test_validates_presence_of_belongs_to_association__parent_is_new_record diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index 87ce4c6f37..73422a31cd 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -18,47 +18,47 @@ class LengthValidationTest < ActiveRecord::TestCase assert_nothing_raised { @owner.validates_size_of :pets, minimum: 1 } o = @owner.new("name" => "nopets") assert !o.save - assert o.errors[:pets].any? + assert_predicate o.errors[:pets], :any? o.pets.build("name" => "apet") - assert o.valid? + assert_predicate o, :valid? end def test_validates_size_of_association_using_within assert_nothing_raised { @owner.validates_size_of :pets, within: 1..2 } o = @owner.new("name" => "nopets") assert !o.save - assert o.errors[:pets].any? + assert_predicate o.errors[:pets], :any? o.pets.build("name" => "apet") - assert o.valid? + assert_predicate o, :valid? 2.times { o.pets.build("name" => "apet") } assert !o.save - assert o.errors[:pets].any? + assert_predicate o.errors[:pets], :any? end def test_validates_size_of_association_utf8 @owner.validates_size_of :pets, minimum: 1 o = @owner.new("name" => "あいうえおかきくけこ") assert !o.save - assert o.errors[:pets].any? + assert_predicate o.errors[:pets], :any? o.pets.build("name" => "あいうえおかきくけこ") - assert o.valid? + assert_predicate o, :valid? end def test_validates_size_of_respects_records_marked_for_destruction @owner.validates_size_of :pets, minimum: 1 owner = @owner.new assert_not owner.save - assert owner.errors[:pets].any? + assert_predicate owner.errors[:pets], :any? pet = owner.pets.build - assert owner.valid? + assert_predicate owner, :valid? assert owner.save pet_count = Pet.count assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ] - assert_not owner.valid? - assert owner.errors[:pets].any? + assert_not_predicate owner, :valid? + assert_predicate owner.errors[:pets], :any? assert_equal pet_count, Pet.count end @@ -70,11 +70,11 @@ class LengthValidationTest < ActiveRecord::TestCase pet = Pet.create!(name: "Fancy Pants", nickname: "Fancy") - assert pet.valid? + assert_predicate pet, :valid? pet.nickname = "" - assert pet.invalid? + assert_predicate pet, :invalid? end end end diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb index 3ab1567b51..63c3f67da2 100644 --- a/activerecord/test/cases/validations/presence_validation_test.rb +++ b/activerecord/test/cases/validations/presence_validation_test.rb @@ -15,10 +15,10 @@ class PresenceValidationTest < ActiveRecord::TestCase def test_validates_presence_of_non_association Boy.validates_presence_of(:name) b = Boy.new - assert b.invalid? + assert_predicate b, :invalid? b.name = "Alex" - assert b.valid? + assert_predicate b, :valid? end def test_validates_presence_of_has_one @@ -33,23 +33,23 @@ class PresenceValidationTest < ActiveRecord::TestCase b = Boy.new f = Face.new b.face = f - assert b.valid? + assert_predicate b, :valid? f.mark_for_destruction - assert b.invalid? + assert_predicate b, :invalid? end def test_validates_presence_of_has_many_marked_for_destruction Boy.validates_presence_of(:interests) b = Boy.new b.interests << [i1 = Interest.new, i2 = Interest.new] - assert b.valid? + assert_predicate b, :valid? i1.mark_for_destruction - assert b.valid? + assert_predicate b, :valid? i2.mark_for_destruction - assert b.invalid? + assert_predicate b, :invalid? end def test_validates_presence_doesnt_convert_to_array @@ -74,11 +74,11 @@ class PresenceValidationTest < ActiveRecord::TestCase Interest.validates_presence_of(:abbreviation) interest = Interest.create!(topic: "Thought Leadering", abbreviation: "tl") - assert interest.valid? + assert_predicate interest, :valid? interest.abbreviation = "" - assert interest.invalid? + assert_predicate interest, :invalid? end end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index a10567f066..1fa94a5b75 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -96,7 +96,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase Topic.validates_uniqueness_of(:new_title) topic = Topic.new(new_title: "abc") - assert topic.valid? + assert_predicate topic, :valid? end def test_validates_uniqueness_with_nil_value @@ -116,7 +116,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase Topic.create!("title" => "abc") t2 = Topic.new("title" => "abc") - assert !t2.valid? + assert_not_predicate t2, :valid? assert t2.errors[:title] end @@ -259,15 +259,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1) assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" - assert t2.errors[:title].any? - assert t2.errors[:parent_id].any? + assert_predicate t2.errors[:title], :any? + assert_predicate t2.errors[:parent_id], :any? assert_equal ["has already been taken"], t2.errors[:title] t2.title = "I'm truly UNIQUE!" assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" - assert t2.errors[:title].empty? - assert t2.errors[:parent_id].any? + assert_empty t2.errors[:title] + assert_predicate t2.errors[:parent_id], :any? t2.parent_id = 4 assert t2.save, "Should now save t2 as unique" @@ -326,15 +326,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase t2 = Topic.new("title" => "I'M UNIQUE!") assert t2.valid?, "Should be valid" assert t2.save, "Should save t2 as unique" - assert t2.errors[:title].empty? - assert t2.errors[:parent_id].empty? + assert_empty t2.errors[:title] + assert_empty t2.errors[:parent_id] assert_not_equal ["has already been taken"], t2.errors[:title] t3 = Topic.new("title" => "I'M uNiQUe!") assert t3.valid?, "Should be valid" assert t3.save, "Should save t2 as unique" - assert t3.errors[:title].empty? - assert t3.errors[:parent_id].empty? + assert_empty t3.errors[:title] + assert_empty t3.errors[:parent_id] assert_not_equal ["has already been taken"], t3.errors[:title] end @@ -343,7 +343,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase Topic.create!("title" => 101) t2 = Topic.new("title" => 101) - assert !t2.valid? + assert_not_predicate t2, :valid? assert t2.errors[:title] end @@ -360,7 +360,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") assert t1.save t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") - assert !t2.valid? + assert_not_predicate t2, :valid? end end @@ -460,16 +460,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase def test_validate_uniqueness_on_existing_relation event = Event.create - assert TopicWithUniqEvent.create(event: event).valid? + assert_predicate TopicWithUniqEvent.create(event: event), :valid? topic = TopicWithUniqEvent.new(event: event) - assert_not topic.valid? + assert_not_predicate topic, :valid? assert_equal ["has already been taken"], topic.errors[:event] end def test_validate_uniqueness_on_empty_relation topic = TopicWithUniqEvent.new - assert topic.valid? + assert_predicate topic, :valid? end def test_validate_uniqueness_of_custom_primary_key @@ -488,7 +488,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase key2 = klass.create!(key_number: 11) key2.key_number = 10 - assert_not key2.valid? + assert_not_predicate key2, :valid? end def test_validate_uniqueness_without_primary_key @@ -501,8 +501,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase end abc = klass.create!(dashboard_id: "abc") - assert klass.new(dashboard_id: "xyz").valid? - assert_not klass.new(dashboard_id: "abc").valid? + assert_predicate klass.new(dashboard_id: "xyz"), :valid? + assert_not_predicate klass.new(dashboard_id: "abc"), :valid? abc.dashboard_id = "def" @@ -530,7 +530,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert topic.author_name.start_with?("Title1") topic2 = TopicWithAfterCreate.new(title: "Title1") - refute topic2.valid? + assert_not_predicate topic2, :valid? assert_equal(["has already been taken"], topic2.errors[:title]) end @@ -550,7 +550,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert_empty item.errors item2 = CoolTopic.new(id: item.id, title: "MyItem2") - refute item2.valid? + assert_not_predicate item2, :valid? assert_equal(["has already been taken"], item2.errors[:id]) end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 7f84939027..6c83bbd15c 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -19,7 +19,7 @@ class ValidationsTest < ActiveRecord::TestCase def test_valid_uses_create_context_when_new r = WrongReply.new r.title = "Wrong Create" - assert_not r.valid? + assert_not_predicate r, :valid? assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid" assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error" end @@ -139,7 +139,7 @@ class ValidationsTest < ActiveRecord::TestCase def test_throw_away_typing d = Developer.new("name" => "David", "salary" => "100,000") - assert !d.valid? + assert_not_predicate d, :valid? assert_equal 100, d.salary assert_equal "100,000", d.salary_before_type_cast end @@ -166,7 +166,7 @@ class ValidationsTest < ActiveRecord::TestCase topic = klass.new(wibble: "123-4567") topic.wibble.gsub!("-", "") - assert topic.valid? + assert_predicate topic, :valid? end def test_numericality_validation_checks_against_raw_value @@ -175,12 +175,12 @@ class ValidationsTest < ActiveRecord::TestCase ActiveModel::Name.new(self, nil, "Topic") end attribute :wibble, :decimal, scale: 2, precision: 9 - validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal.new("97.18") + validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal("97.18") end - assert_not klass.new(wibble: "97.179").valid? - assert_not klass.new(wibble: 97.179).valid? - assert_not klass.new(wibble: BigDecimal.new("97.179")).valid? + assert_not_predicate klass.new(wibble: "97.179"), :valid? + assert_not_predicate klass.new(wibble: 97.179), :valid? + assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid? end def test_acceptance_validator_doesnt_require_db_connection diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 578881f754..60ebdce178 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -96,7 +96,7 @@ class YamlSerializationTest < ActiveRecord::TestCase def test_deserializing_rails_41_yaml topic = YAML.load(yaml_fixture("rails_4_1")) - assert topic.new_record? + assert_predicate topic, :new_record? assert_nil topic.id assert_equal "The First Topic", topic.title assert_equal({ omg: :lol }, topic.content) @@ -105,7 +105,7 @@ class YamlSerializationTest < ActiveRecord::TestCase def test_deserializing_rails_4_2_0_yaml topic = YAML.load(yaml_fixture("rails_4_2_0")) - assert_not topic.new_record? + assert_not_predicate topic, :new_record? assert_equal 1, topic.id assert_equal "The First Topic", topic.title assert_equal("Have a nice day", topic.content) diff --git a/activerecord/test/fixtures/customers.yml b/activerecord/test/fixtures/customers.yml index 0399ff83b9..7d6c1366d0 100644 --- a/activerecord/test/fixtures/customers.yml +++ b/activerecord/test/fixtures/customers.yml @@ -23,4 +23,13 @@ barney: address_street: Quiet Road address_city: Peaceful Town address_country: Tranquil Land - gps_location: NULL
\ No newline at end of file + gps_location: NULL + +mary: + id: 4 + name: Mary + balance: 1 + address_street: Funny Street + address_city: Peaceful Town + address_country: Nation Land + gps_location: NULL diff --git a/activerecord/test/fixtures/minimalistics.yml b/activerecord/test/fixtures/minimalistics.yml index c3ec546209..83df0551bc 100644 --- a/activerecord/test/fixtures/minimalistics.yml +++ b/activerecord/test/fixtures/minimalistics.yml @@ -1,2 +1,5 @@ +zero: + id: 0 + first: id: 1 diff --git a/activerecord/test/fixtures/reserved_words/values.yml b/activerecord/test/fixtures/reserved_words/values.yml index 7d109609ab..9ed9e5edc5 100644 --- a/activerecord/test/fixtures/reserved_words/values.yml +++ b/activerecord/test/fixtures/reserved_words/values.yml @@ -1,7 +1,7 @@ values1: - id: 1 + as: 1 group_id: 2 values2: - id: 2 + as: 2 group_id: 1 diff --git a/activerecord/test/fixtures/teapots.yml b/activerecord/test/fixtures/teapots.yml deleted file mode 100644 index ff515beb45..0000000000 --- a/activerecord/test/fixtures/teapots.yml +++ /dev/null @@ -1,3 +0,0 @@ -bob: - id: 1 - name: Bob diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index cb8686f315..27da886e1c 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -88,6 +88,9 @@ class Author < ActiveRecord::Base has_many :special_categories, through: :special_categorizations, source: :category has_one :special_category, through: :special_categorizations, source: :category + has_many :special_categories_with_conditions, -> { where(categorizations: { special: true }) }, through: :categorizations, source: :category + has_many :nonspecial_categories_with_conditions, -> { where(categorizations: { special: false }) }, through: :categorizations, source: :category + has_many :categories_like_general, -> { where(name: "General") }, through: :categorizations, source: :category, class_name: "Category" has_many :categorized_posts, through: :categorizations, source: :post diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index bbc5fc2b2d..fc6488f729 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -87,6 +87,8 @@ class Firm < Company has_many :association_with_references, -> { references(:foo) }, class_name: "Client" + has_many :developers_with_select, -> { select("id, name, first_name") }, class_name: "Developer" + has_one :lead_developer, class_name: "Developer" has_many :projects diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb index 9454217e8d..f273badd85 100644 --- a/activerecord/test/models/contract.rb +++ b/activerecord/test/models/contract.rb @@ -2,7 +2,7 @@ class Contract < ActiveRecord::Base belongs_to :company - belongs_to :developer + belongs_to :developer, primary_key: :id belongs_to :firm, foreign_key: "company_id" before_save :hi diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index 524a9d7bd9..bc501a5ce0 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -4,7 +4,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(&:to_money) + composed_of :balance, class_name: "Money", mapping: %w(balance amount) 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/post.rb b/activerecord/test/models/post.rb index 780a2c17f5..54eb5e6783 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -106,6 +106,9 @@ class Post < ActiveRecord::Base end end + has_many :indestructible_taggings, as: :taggable, counter_cache: :indestructible_tags_count + has_many :indestructible_tags, through: :indestructible_taggings, source: :tag + has_many :taggings_with_delete_all, class_name: "Tagging", as: :taggable, dependent: :delete_all, counter_cache: :taggings_with_delete_all_count has_many :taggings_with_destroy, class_name: "Tagging", as: :taggable, dependent: :destroy, counter_cache: :taggings_with_destroy_count @@ -323,5 +326,13 @@ class FakeKlass def enforce_raw_sql_whitelist(*args) # noop end + + def arel_table + Post.arel_table + end + + def predicate_builder + Post.predicate_builder + end end end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index bc13c3a42d..c1a8890a8a 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -11,6 +11,6 @@ end class OrderedTag < Tag self.table_name = "tags" - has_many :taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id" - has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post" + has_many :ordered_taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id", class_name: "Tagging" + has_many :tagged_posts, through: :ordered_taggings, source: "taggable", source_type: "Post" end diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb index 861fde633f..6d4230f6f4 100644 --- a/activerecord/test/models/tagging.rb +++ b/activerecord/test/models/tagging.rb @@ -14,3 +14,7 @@ class Tagging < ActiveRecord::Base belongs_to :taggable, polymorphic: true, counter_cache: :tags_count has_many :things, through: :taggable end + +class IndestructibleTagging < Tagging + before_destroy { throw :abort } +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 2154b50ef7..8cd4dc352a 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -65,6 +65,9 @@ class Topic < ActiveRecord::Base after_initialize :set_email_address + attr_accessor :change_approved_before_save + before_save :change_approved_callback + class_attribute :after_initialize_called after_initialize do self.class.after_initialize_called = true @@ -96,6 +99,10 @@ class Topic < ActiveRecord::Base def before_destroy_for_transaction; end def after_save_for_transaction; end def after_create_for_transaction; end + + def change_approved_callback + self.approved = change_approved_before_save unless change_approved_before_save.nil? + end end class ImportantTopic < Topic diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb index e05fb64477..8db57d181e 100644 --- a/activerecord/test/models/wheel.rb +++ b/activerecord/test/models/wheel.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Wheel < ActiveRecord::Base - belongs_to :wheelable, polymorphic: true, counter_cache: true + belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: true end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index bf66846840..7d008eecd5 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -123,7 +123,7 @@ ActiveRecord::Schema.define do create_table :cars, force: true do |t| t.string :name t.integer :engines_count - t.integer :wheels_count + t.integer :wheels_count, default: 0 t.column :lock_version, :integer, null: false, default: 0 t.timestamps null: false end @@ -690,6 +690,7 @@ ActiveRecord::Schema.define do t.integer :taggings_with_delete_all_count, default: 0 t.integer :taggings_with_destroy_count, default: 0 t.integer :tags_count, default: 0 + t.integer :indestructible_tags_count, default: 0 t.integer :tags_with_destroy_count, default: 0 t.integer :tags_with_nullify_count, default: 0 end @@ -847,6 +848,7 @@ ActiveRecord::Schema.define do t.column :taggable_type, :string t.column :taggable_id, :integer t.string :comment + t.string :type end create_table :tasks, force: true do |t| @@ -964,6 +966,7 @@ ActiveRecord::Schema.define do end create_table :wheels, force: true do |t| + t.integer :size t.references :wheelable, polymorphic: true end |