aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md84
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb5
-rw-r--r--activerecord/lib/active_record/explain.rb3
-rw-r--r--activerecord/lib/active_record/inheritance.rb3
-rw-r--r--activerecord/lib/active_record/integration.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb44
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb96
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb5
-rw-r--r--activerecord/lib/active_record/timestamp.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations_test.rb14
-rw-r--r--activerecord/test/cases/base_test.rb36
-rw-r--r--activerecord/test/cases/dirty_test.rb15
-rw-r--r--activerecord/test/cases/explain_test.rb3
-rw-r--r--activerecord/test/cases/json_serialization_test.rb88
-rw-r--r--activerecord/test/cases/migration/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb42
-rw-r--r--activerecord/test/cases/persistence_test.rb8
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb144
-rw-r--r--activerecord/test/cases/serialization_test.rb4
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb13
-rw-r--r--activerecord/test/fixtures/traffic_lights.yml4
-rw-r--r--activerecord/test/models/traffic_light.rb1
-rw-r--r--activerecord/test/schema/schema.rb3
40 files changed, 591 insertions, 123 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 235a91fafa..3b140b9ec3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,12 +1,88 @@
## Rails 4.0.0 (unreleased) ##
+* Expand `#cache_key` to consult all relevant updated timestamps.
+
+ Previously only `updated_at` column was checked, now it will
+ consult other columns that received updated timestamps on save,
+ such as `updated_on`. When multiple columns are present it will
+ use the most recent timestamp.
+ Fixes #9033.
+
+ *Brendon Murphy*
+
+* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class.
+
+ *Aaron Weiner*
+
+* Warn when `rake db:structure:dump` with a mysl database and
+ `mysqldump` is not in the PATH or fails.
+ Fixes #9518.
+
+ *Yves Senn*
+
+* Remove `connection#structure_dump`, which is no longer used. *Yves Senn*
+
+* Make it possible to execute migrations without a transaction even
+ if the database adapter supports DDL transactions.
+ Fixes #9483.
+
+ Example:
+
+ class ChangeEnum < ActiveRecord::Migration
+ self.disable_ddl_transaction!
+ def up
+ execute "ALTER TYPE model_size ADD VALUE 'new_value'"
+ end
+ end
+
+ *Yves Senn*
+
+* Assigning "0.0" to a nullable numeric column does not make it dirty.
+ Fix #9034.
+
+ Example:
+
+ product = Product.create price: 0.0
+ product.price = '0.0'
+ product.changed? # => false (this used to return true)
+ product.changes # => {} (this used to return { price: [0.0, 0.0] })
+
+ *Yves Senn*
+
+* Added functionality to unscope relations in a relations chain. For
+ instance, if you are passed in a chain of relations as follows:
+
+ User.where(name: "John").order('id DESC')
+
+ but you want to get rid of order, then this feature allows you to do:
+
+ User.where(name: "John").order('id DESC').unscope(:order)
+ == User.where(name: "John")
+
+ The .unscope() function is more general than the .except() method because
+ .except() only works on the relation it is acting on. However, .unscope()
+ works for any relation in the entire relation chain.
+
+ *John Wang*
+
+* Postgresql timestamp with time zone (timestamptz) datatype now returns a
+ ActiveSupport::TimeWithZone instance instead of a string
+
+ *Troy Kruthoff*
+
+* The `#append` method for collection associations behaves like`<<`.
+ `#prepend` is not defined and `<<` or `#append` should be used.
+ Fixes #7364.
+
+ *Yves Senn*
+
* Added support for creating a table via Rails migration generator.
- For example,
+ For example,
rails g migration create_books title:string content:text
- will generate a migration that creates a table called books with
- the listed attributes, without creating a model.
+ will generate a migration that creates a table called books with
+ the listed attributes, without creating a model.
*Sammy Larbi*
@@ -372,7 +448,7 @@
deprecated and removed in future versions of Rails.
*Amparo Luna + Guillermo Iguaran*
-
+
* `after_commit` and `after_rollback` now validate the `:on` option and raise an `ArgumentError`
if it is not one of `:create`, `:destroy` or `:update`
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index 9fc6785d99..ed1e171d58 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -130,7 +130,7 @@ A short rundown of some of the major features:
SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
-* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
+* Logging support for Log4r[http://log4r.rubyforge.org] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
ActiveRecord::Base.logger = Log4r::Logger.new('Application Log')
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 513d1012ba..35e4eb19a4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1115,7 +1115,7 @@ module ActiveRecord
# :dependent behavior may affect other callbacks.
#
# * <tt>:destroy</tt> causes all the associated objects to also be destroyed
- # * <tt>:delete_all</tt> causes all the asssociated objects to be deleted directly from the database (so callbacks will not execute)
+ # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
# * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
# * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records
# * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index e93e700c93..543204abac 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -83,9 +83,9 @@ module ActiveRecord
# # #<Pet id: 3, name: "Choo-Choo">
# # ]
#
- # Be careful because this also means you’re initializing a model
- # object with only the fields that you’ve selected. If you attempt
- # to access a field that is not in the initialized record you’ll
+ # Be careful because this also means you're initializing a model
+ # object with only the fields that you've selected. If you attempt
+ # to access a field that is not in the initialized record you'll
# receive:
#
# person.pets.select(:name).first.person_id
@@ -924,7 +924,7 @@ module ActiveRecord
alias_method :to_a, :to_ary
# Adds one or more +records+ to the collection by setting their foreign keys
- # to the association‘s primary key. Returns +self+, so several appends may be
+ # to the association's primary key. Returns +self+, so several appends may be
# chained together.
#
# class Person < ActiveRecord::Base
@@ -947,6 +947,11 @@ module ActiveRecord
proxy_association.concat(records) && self
end
alias_method :push, :<<
+ alias_method :append, :<<
+
+ def prepend(*args)
+ raise NoMethodError, "prepend on association is not defined. Please use << or append"
+ end
# Equivalent to +delete_all+. The difference is that returns +self+, instead
# of an array with the deleted objects, so methods can be chained. See
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 616ae1631f..6315dd9549 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -107,7 +107,11 @@ module ActiveRecord
def changes_from_zero_to_string?(old, value)
# For columns with old 0 and value non-empty string
- old == 0 && value.is_a?(String) && value.present? && value != '0'
+ old == 0 && value.is_a?(String) && value.present? && non_zero?(value)
+ end
+
+ def non_zero?(value)
+ value !~ /\A0+(\.0+)?\z/
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 0cce8c7596..5b8de184fe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -516,11 +516,6 @@ module ActiveRecord
end
alias :remove_belongs_to :remove_reference
- # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
- # entire structure of the database.
- def structure_dump
- end
-
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
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 9826b18053..be4a30aed9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -332,20 +332,6 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def structure_dump #:nodoc:
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql, 'SCHEMA').map { |table|
- table.delete('Table_type')
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_query(sql, 'SCHEMA').first['Create Table'] + ";\n\n"
- }.join
- end
-
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
def recreate_database(name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 2c683fc3ac..8bad7d0cf5 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -38,7 +38,7 @@ module ActiveRecord
private
def resolve_string_connection(spec) # :nodoc:
hash = configurations.fetch(spec) do |k|
- self.class.connection_url_to_hash(k)
+ connection_url_to_hash(k)
end
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
@@ -65,7 +65,7 @@ module ActiveRecord
ConnectionSpecification.new(spec, adapter_method)
end
- def self.connection_url_to_hash(url) # :nodoc:
+ def connection_url_to_hash(url) # :nodoc:
config = URI.parse url
adapter = config.scheme
adapter = "postgresql" if adapter == "postgres"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index e09319890a..68f2f2ca7b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -317,10 +317,6 @@ module ActiveRecord
alias_type 'macaddr', 'text'
alias_type 'uuid', 'text'
- # FIXME: I don't think this is correct. We should probably be returning a parsed date,
- # but the tests pass with a string returned.
- register_type 'timestamptz', OID::Identity.new
-
register_type 'money', OID::Money.new
register_type 'bytea', OID::Bytea.new
register_type 'bool', OID::Boolean.new
@@ -329,6 +325,7 @@ module ActiveRecord
alias_type 'float8', 'float4'
register_type 'timestamp', OID::Timestamp.new
+ register_type 'timestamptz', OID::Timestamp.new
register_type 'date', OID::Date.new
register_type 'time', OID::Time.new
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index b2a9a54af1..3135465dfe 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -6,7 +6,8 @@ module ActiveRecord
def collecting_queries_for_explain # :nodoc:
current = Thread.current
original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
- return yield, current[:available_queries_for_explain]
+ yield
+ return current[:available_queries_for_explain]
ensure
# Note that the return value above does not depend on this assigment.
current[:available_queries_for_explain] = original
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index e630897a4b..13a8352f13 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -15,6 +15,9 @@ module ActiveRecord
# 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)
+ if abstract_class? || self == Base
+ raise NotImplementedError, "#{self} is an abstract class and can not be instantiated."
+ end
if (attrs = args.first).is_a?(Hash)
if subclass = subclass_from_attrs(attrs)
return subclass.new(*args, &block)
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 32d35f0ec1..48c73d7781 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -49,7 +49,7 @@ module ActiveRecord
case
when new_record?
"#{self.class.model_name.cache_key}/new"
- when timestamp = self[:updated_at]
+ when timestamp = max_updated_column_timestamp
timestamp = timestamp.utc.to_s(cache_timestamp_format)
"#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
else
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 823595a128..62e8881c4c 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -330,6 +330,23 @@ module ActiveRecord
#
# For a list of commands that are reversible, please see
# <tt>ActiveRecord::Migration::CommandRecorder</tt>.
+ #
+ # == Transactional Migrations
+ #
+ # If the database adapter supports DDL transactions, all migrations will
+ # automatically be wrapped in a transaction. There are queries that you
+ # can't execute inside a transaction though, and for these situations
+ # you can turn the automatic transactions off.
+ #
+ # class ChangeEnum < ActiveRecord::Migration
+ # self.disable_ddl_transaction!
+ # def up
+ # execute "ALTER TYPE model_size ADD VALUE 'new_value'"
+ # end
+ # end
+ #
+ # Remember that you can still open your own transactions, even if you
+ # are in a Migration with <tt>self.disable_ddl_transaction!</tt>.
class Migration
autoload :CommandRecorder, 'active_record/migration/command_recorder'
@@ -351,6 +368,7 @@ module ActiveRecord
class << self
attr_accessor :delegate # :nodoc:
+ attr_accessor :disable_ddl_transaction # :nodoc:
end
def self.check_pending!
@@ -365,8 +383,16 @@ module ActiveRecord
new.migrate direction
end
- cattr_accessor :verbose
+ # Disable DDL transactions for this migration.
+ def self.disable_ddl_transaction!
+ @disable_ddl_transaction = true
+ end
+
+ def disable_ddl_transaction # :nodoc:
+ self.class.disable_ddl_transaction
+ end
+ cattr_accessor :verbose
attr_accessor :name, :version
def initialize(name = self.class.name, version = nil)
@@ -375,8 +401,8 @@ module ActiveRecord
@connection = nil
end
+ self.verbose = true
# instantiate the delegate object after initialize is defined
- self.verbose = true
self.delegate = new
# Reverses the migration commands for the given block and
@@ -663,7 +689,7 @@ module ActiveRecord
File.basename(filename)
end
- delegate :migrate, :announce, :write, :to => :migration
+ delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
private
@@ -856,12 +882,12 @@ module ActiveRecord
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
begin
- ddl_transaction do
+ ddl_transaction(migration) do
migration.migrate(@direction)
record_version_state_after_migrating(migration.version)
end
rescue => e
- canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
+ canceled_msg = use_transaction?(migration) ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
end
end
@@ -935,12 +961,16 @@ module ActiveRecord
end
# Wrap the migration in a transaction only if supported by the adapter.
- def ddl_transaction
- if Base.connection.supports_ddl_transactions?
+ def ddl_transaction(migration)
+ if use_transaction?(migration)
Base.transaction { yield }
else
yield
end
end
+
+ def use_transaction?(migration)
+ !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions?
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 64eac3aca7..13f3bf7085 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -152,7 +152,7 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
- self.configurations = app.config.database_configuration
+ self.configurations = app.config.database_configuration || {}
establish_connection
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index f36af7182f..d92e268109 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -2,7 +2,7 @@ require 'active_record'
db_namespace = namespace :db do
task :load_config do
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration
+ ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index bc50802c4a..ad54ba55b6 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -188,8 +188,7 @@ module ActiveRecord
# Please see further details in the
# {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
def explain
- _, queries = collecting_queries_for_explain { exec_queries }
- exec_explain(queries)
+ exec_explain(collecting_queries_for_explain { exec_queries })
end
# Converts relation objects to Array.
@@ -236,8 +235,9 @@ module ActiveRecord
# Scope all queries to the current scope.
#
# Comment.where(post_id: 1).scoping do
- # Comment.first # SELECT * FROM comments WHERE post_id = 1
+ # Comment.first
# end
+ # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
#
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4b8c40592e..b7960936cf 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,7 +5,7 @@ module ActiveRecord
extend ActiveSupport::Concern
# WhereChain objects act as placeholder for queries in which #where does not have any parameter.
- # In this case, #where must be chained with either #not, #like, or #not_like to return a new relation.
+ # In this case, #where must be chained with #not to return a new relation.
class WhereChain
def initialize(scope)
@scope = scope
@@ -317,6 +317,67 @@ module ActiveRecord
self
end
+ VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
+ :limit, :offset, :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
+ # modify the relations without reconstructing the entire chain.
+ #
+ # User.order('email DESC').unscope(:order) == User.all
+ #
+ # The method arguments are symbols which correspond to the names of the methods
+ # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES.
+ # The method can also be called with multiple arguments. For example:
+ #
+ # User.order('email DESC').select('id').where(name: "John")
+ # .unscope(:order, :select, :where) == User.all
+ #
+ # One can additionally pass a hash as an argument to unscope specific :where values.
+ # This is done by passing a hash with a single key-value pair. The key should be
+ # :where and the value should be the where value to unscope. For example:
+ #
+ # User.where(name: "John", active: true).unscope(where: :name)
+ # == User.where(active: true)
+ #
+ # Note that this method is more generalized than ActiveRecord::SpawnMethods#except
+ # because #except will only affect a particular relation's values. It won't wipe
+ # the order, grouping, etc. when that relation is merged. For example:
+ #
+ # Post.comments.except(:order)
+ #
+ # will still have an order if it comes from the default_scope on Comment.
+ def unscope(*args)
+ check_if_method_has_arguments!("unscope", args)
+ spawn.unscope!(*args)
+ end
+
+ def unscope!(*args)
+ args.flatten!
+
+ args.each do |scope|
+ case scope
+ when Symbol
+ symbol_unscoping(scope)
+ when Hash
+ scope.each do |key, target_value|
+ if key != :where
+ raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key."
+ end
+
+ Array(target_value).each do |val|
+ where_unscoping(val)
+ end
+ end
+ else
+ raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example."
+ end
+ end
+
+ self
+ end
+
# Performs a joins on +args+:
#
# User.joins(:posts)
@@ -762,6 +823,39 @@ module ActiveRecord
private
+ def symbol_unscoping(scope)
+ if !VALID_UNSCOPING_VALUES.include?(scope)
+ raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
+ end
+
+ single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope)
+ unscope_code = :"#{scope}_value#{'s' unless single_val_method}="
+
+ case scope
+ when :order
+ self.send(:reverse_order_value=, false)
+ result = []
+ else
+ result = [] unless single_val_method
+ end
+
+ self.send(unscope_code, result)
+ end
+
+ def where_unscoping(target_value)
+ target_value_sym = target_value.to_sym
+
+ where_values.reject! do |rel|
+ case rel
+ when Arel::Nodes::In, Arel::Nodes::Equality
+ subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right)
+ subrelation.name.to_sym == target_value_sym
+ else
+ raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented."
+ end
+ end
+ end
+
def custom_join_ast(table, joins)
joins = joins.reject { |join| join.blank? }
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 6b55af4205..bd9079b596 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -5,7 +5,7 @@ module ActiveRecord #:nodoc:
include ActiveModel::Serializers::JSON
included do
- self.include_root_in_json = true
+ self.include_root_in_json = false
end
def serializable_hash(options = nil)
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 17378969a5..10696258c9 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -57,7 +57,10 @@ module ActiveRecord
args.concat(["--result-file", "#{filename}"])
args.concat(["--no-data"])
args.concat(["#{configuration['database']}"])
- Kernel.system(*args)
+ unless Kernel.system(*args)
+ $stderr.puts "Could not dump the database structure. "\
+ "Make sure `mysqldump` is in your PATH and check the command output for warnings."
+ end
end
def structure_load(filename)
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 8ded6d4a86..ae99cff35e 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -98,6 +98,12 @@ module ActiveRecord
timestamp_attributes_for_create + timestamp_attributes_for_update
end
+ def max_updated_column_timestamp
+ if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present?
+ timestamps.map { |ts| ts.to_time }.max
+ end
+ end
+
def current_time_from_proper_timezone
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 5164acf77f..4cf4bc4c61 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
end
- # dump structure of table with reserved word name
- def test_structure_dump
- assert_nothing_raised { @connection.structure_dump }
- end
-
# introspect table with reserved word name
def test_introspect
assert_nothing_raised { @connection.columns(:group) }
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 1265cb927e..fedd9f603c 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -70,12 +70,6 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
- def test_logs_name_structure_dump
- @connection.structure_dump
- assert_equal "SCHEMA", @connection.logged[0][1]
- assert_equal "SCHEMA", @connection.logged[2][1]
- end
-
def test_logs_name_show_variable
@connection.show_variable 'foo'
assert_equal "SCHEMA", @connection.logged[0][1]
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 1017b0758d..98596a7f62 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -63,11 +63,6 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
end
- # dump structure of table with reserved word name
- def test_structure_dump
- assert_nothing_raised { @connection.structure_dump }
- end
-
# introspect table with reserved word name
def test_introspect
assert_nothing_raised { @connection.columns(:group) }
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 33c796191e..1e6ae85a25 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -573,6 +573,7 @@ _SQL
@first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
+ assert_instance_of Time, @first_timestamp_with_zone.time
ensure
ActiveRecord::Base.default_timezone = old_default_tz
ActiveRecord::Base.time_zone_aware_attributes = old_tz
@@ -590,6 +591,7 @@ _SQL
@first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1)
assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time
+ assert_instance_of Time, @first_timestamp_with_zone.time
ensure
ActiveRecord::Base.default_timezone = old_default_tz
ActiveRecord::Base.time_zone_aware_attributes = old_tz
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 3a6da0e59f..8664ee99d2 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -21,9 +21,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
:posts, :tags, :taggings, :comments, :sponsors, :members
def test_belongs_to
- Client.find(3).firm.name
- assert_equal companies(:first_firm).name, Client.find(3).firm.name
- assert_not_nil Client.find(3).firm, "Microsoft should have a firm"
+ firm = Client.find(3).firm
+ assert_not_nil firm
+ assert_equal companies(:first_firm).name, firm.name
end
def test_belongs_to_with_primary_key
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index d7f25f760e..201fa5d5a9 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -172,6 +172,18 @@ class AssociationProxyTest < ActiveRecord::TestCase
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_equal 1, josh.posts.size
+ end
+
+ def test_prepend_is_not_defined
+ josh = Author.new(:name => "Josh")
+ assert_raises(NoMethodError) { josh.posts.prepend Post.new }
+ end
+
def test_save_on_parent_does_not_load_target
david = developers(:david)
@@ -291,7 +303,7 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
end
def test_requires_symbol_argument
- assert_raises ArgumentError do
+ assert_raises ArgumentError do
Class.new(Post) do
belongs_to "author"
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index af1845c937..e5880a6b7b 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -307,6 +307,20 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal("last_read", ex.errors[0].attribute)
end
+ def test_initialize_abstract_class
+ e = assert_raises(NotImplementedError) do
+ FirstAbstractClass.new
+ end
+ assert_equal("FirstAbstractClass is an abstract class and can not be instantiated.", e.message)
+ end
+
+ def test_initialize_base
+ e = assert_raises(NotImplementedError) do
+ ActiveRecord::Base.new
+ end
+ assert_equal("ActiveRecord::Base is an abstract class and can not be instantiated.", e.message)
+ end
+
def test_create_after_initialize_without_block
cb = CustomBulb.create(:name => 'Dude')
assert_equal('Dude', cb.name)
@@ -1441,12 +1455,30 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal key, car.cache_key
end
- def test_cache_key_format_for_existing_record_with_nil_updated_at
+ def test_cache_key_format_for_existing_record_with_nil_updated_timestamps
dev = Developer.first
- dev.update_columns(updated_at: nil)
+ dev.update_columns(updated_at: nil, updated_on: nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+ def test_cache_key_for_updated_on
+ dev = Developer.first
+ dev.updated_at = nil
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_at
+ dev = Developer.first
+ dev.updated_at += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
+ end
+
+ def test_cache_key_for_newer_updated_on
+ dev = Developer.first
+ dev.updated_on += 3600
+ assert_equal "developers/#{dev.id}-#{dev.updated_on.utc.to_s(:nsec)}", dev.cache_key
+ end
+
def test_touch_should_raise_error_on_a_new_object
company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
assert_raises(ActiveRecord::ActiveRecordError) do
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index c7d2ba6073..7b2034dadf 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -243,6 +243,21 @@ class DirtyTest < ActiveRecord::TestCase
assert !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?
+
+ data.temperature = '0'
+ assert_empty data.changes
+
+ data.temperature = '0.0'
+ assert_empty data.changes
+
+ data.temperature = '0.00'
+ assert_empty data.changes
+ end
def test_zero_to_blank_marked_as_changed
pirate = Pirate.new
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
index b1d276f9eb..6dac5db111 100644
--- a/activerecord/test/cases/explain_test.rb
+++ b/activerecord/test/cases/explain_test.rb
@@ -20,7 +20,7 @@ if ActiveRecord::Base.connection.supports_explain?
end
def test_collecting_queries_for_explain
- result, queries = ActiveRecord::Base.collecting_queries_for_explain do
+ queries = ActiveRecord::Base.collecting_queries_for_explain do
Car.where(:name => 'honda').to_a
end
@@ -28,7 +28,6 @@ if ActiveRecord::Base.connection.supports_explain?
assert_match "SELECT", sql
assert_match "honda", sql
assert_equal [], binds
- assert_equal [cars(:honda)], result
end
def test_exec_explain_with_no_binds
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index a86b165c78..a222675918 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -6,7 +6,21 @@ require 'models/tagging'
require 'models/tag'
require 'models/comment'
+module JsonSerializationHelpers
+ private
+
+ def set_include_root_in_json(value)
+ original_root_in_json = ActiveRecord::Base.include_root_in_json
+ ActiveRecord::Base.include_root_in_json = value
+ yield
+ ensure
+ ActiveRecord::Base.include_root_in_json = original_root_in_json
+ end
+end
+
class JsonSerializationTest < ActiveRecord::TestCase
+ include JsonSerializationHelpers
+
class NamespacedContact < Contact
column :name, :string
end
@@ -23,20 +37,24 @@ class JsonSerializationTest < ActiveRecord::TestCase
end
def test_should_demodulize_root_in_json
- @contact = NamespacedContact.new :name => 'whatever'
- json = @contact.to_json
- assert_match %r{^\{"namespaced_contact":\{}, json
+ set_include_root_in_json(true) do
+ @contact = NamespacedContact.new name: 'whatever'
+ json = @contact.to_json
+ assert_match %r{^\{"namespaced_contact":\{}, json
+ end
end
def test_should_include_root_in_json
- json = @contact.to_json
-
- assert_match %r{^\{"contact":\{}, json
- assert_match %r{"name":"Konata Izumi"}, json
- assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
- assert_match %r{"awesome":true}, json
- assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ set_include_root_in_json(true) do
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
+ end
end
def test_should_encode_all_encodable_attributes
@@ -141,6 +159,8 @@ end
class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
fixtures :authors, :posts, :comments, :tags, :taggings
+ include JsonSerializationHelpers
+
def setup
@david = authors(:david)
@mary = authors(:mary)
@@ -227,23 +247,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
end
def test_should_allow_only_option_for_list_of_authors
- ActiveRecord::Base.include_root_in_json = false
- authors = [@david, @mary]
- assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, :only => :name)
- ensure
- ActiveRecord::Base.include_root_in_json = true
+ set_include_root_in_json(false) do
+ authors = [@david, @mary]
+ assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, only: :name)
+ end
end
def test_should_allow_except_option_for_list_of_authors
- ActiveRecord::Base.include_root_in_json = false
- authors = [@david, @mary]
- encoded = ActiveSupport::JSON.encode(authors, :except => [
- :name, :author_address_id, :author_address_extra_id,
- :organization_id, :owned_essay_id
- ])
- assert_equal %([{"id":1},{"id":2}]), encoded
- ensure
- ActiveRecord::Base.include_root_in_json = true
+ set_include_root_in_json(false) do
+ authors = [@david, @mary]
+ encoded = ActiveSupport::JSON.encode(authors, except: [
+ :name, :author_address_id, :author_address_extra_id,
+ :organization_id, :owned_essay_id
+ ])
+ assert_equal %([{"id":1},{"id":2}]), encoded
+ end
end
def test_should_allow_includes_for_list_of_authors
@@ -262,17 +280,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
end
def test_should_allow_options_for_hash_of_authors
- authors_hash = {
- 1 => @david,
- 2 => @mary
- }
- assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
+ set_include_root_in_json(true) do
+ authors_hash = {
+ 1 => @david,
+ 2 => @mary
+ }
+ assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, only: [1, :name])
+ end
end
def test_should_be_able_to_encode_relation
- authors_relation = Author.where(:id => [@david.id, @mary.id])
+ set_include_root_in_json(true) do
+ authors_relation = Author.where(id: [@david.id, @mary.id])
- json = ActiveSupport::JSON.encode authors_relation, :only => :name
- assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json
+ json = ActiveSupport::JSON.encode authors_relation, only: :name
+ assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json
+ end
end
end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
index ee0c20747e..97efb94b66 100644
--- a/activerecord/test/cases/migration/logger_test.rb
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -7,6 +7,7 @@ module ActiveRecord
self.use_transactional_fixtures = false
Migration = Struct.new(:name, :version) do
+ def disable_ddl_transaction; false end
def migrate direction
# do nothing
end
@@ -34,4 +35,3 @@ module ActiveRecord
end
end
end
-
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index fa8dec0e15..960d28fcf5 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -239,9 +239,13 @@ class MigrationTest < ActiveRecord::TestCase
assert_not Person.column_methods_hash.include?(:last_name)
- migration = Struct.new(:name, :version) {
- def migrate(x); raise 'Something broke'; end
- }.new('zomg', 100)
+ migration = Class.new(ActiveRecord::Migration) {
+ def version; 100 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
@@ -250,7 +254,39 @@ class MigrationTest < ActiveRecord::TestCase
assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
Person.reset_column_information
+ assert_not Person.column_methods_hash.include?(:last_name),
+ "On error, the Migrator should revert schema changes but it did not."
+ end
+
+ def test_migration_without_transaction
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
+ end
+
assert_not Person.column_methods_hash.include?(:last_name)
+
+ migration = Class.new(ActiveRecord::Migration) {
+ self.disable_ddl_transaction!
+
+ def version; 101 end
+ def migrate(x)
+ add_column "people", "last_name", :string
+ raise 'Something broke'
+ end
+ }.new
+
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 101)
+ e = assert_raise(StandardError) { migrator.migrate }
+ assert_equal "An error has occurred, all later migrations canceled:\n\nSomething broke", e.message
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name),
+ "without ddl transactions, the Migrator should not rollback on error but it did."
+ ensure
+ Person.reset_column_information
+ if Person.column_methods_hash.include?(:last_name)
+ Person.connection.remove_column('people', 'last_name')
+ end
end
def test_schema_migrations_table_name
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8156f99037..b936cca875 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -399,14 +399,6 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
end
- def test_string_ids
- # FIXME: Fix this failing test
- skip "Failing test. We need this fixed before 4.0.0"
- mv = Minivan.where(:minivan_id => 1234).first_or_initialize
- assert mv.new_record?
- assert_equal '1234', mv.minivan_id
- end
-
def test_update_attribute_with_one_updated
t = Topic.first
t.update_attribute(:title, 'super_title')
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 7388324a0d..2b4aadc7ed 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -424,6 +424,150 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_unscope_overrides_default_scope
+ expected = Developer.all.collect { |dev| [dev.name, dev.id] }
+ received = Developer.order('name ASC, id DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
+ assert_equal expected, received
+ end
+
+ def test_unscope_after_reordering_and_combining
+ expected = Developer.order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] }
+ received = DeveloperOrderedBySalary.reorder('name DESC').unscope(:order).order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] }
+ assert_equal expected, received
+
+ expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] }
+ received_2 = Developer.order('id DESC, name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
+ assert_equal expected_2, received_2
+
+ expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] }
+ received_3 = Developer.reorder('name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
+ assert_equal expected_3, received_3
+ end
+
+ def test_unscope_with_where_attributes
+ expected = Developer.order('salary DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect { |dev| dev.name }
+ assert_equal expected, received
+
+ expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
+ received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect { |dev| dev.name }
+ assert_equal expected_2, received_2
+
+ expected_3 = Developer.order('salary DESC').collect { |dev| dev.name }
+ received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect { |dev| dev.name }
+ assert_equal expected_3, received_3
+ end
+
+ def test_unscope_multiple_where_clauses
+ expected = Developer.order('salary DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_with_grouping_attributes
+ expected = Developer.order('salary DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name }
+ assert_equal expected, received
+
+ expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
+ received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name }
+ assert_equal expected_2, received_2
+ end
+
+ def test_unscope_with_limit_in_query
+ expected = Developer.order('salary DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_order_to_unscope_reordering
+ expected = DeveloperOrderedBySalary.all.collect { |dev| [dev.name, dev.id] }
+ received = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order).collect { |dev| [dev.name, dev.id] }
+ assert_equal expected, received
+ end
+
+ def test_unscope_reverse_order
+ expected = Developer.all.collect { |dev| dev.name }
+ received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_select
+ expected = Developer.order('salary ASC').collect { |dev| dev.name }
+ received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name }
+ assert_equal expected, received
+
+ expected_2 = Developer.all.collect { |dev| dev.id }
+ received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id }
+ assert_equal expected_2, received_2
+ end
+
+ def test_unscope_offset
+ expected = Developer.all.collect { |dev| dev.name }
+ received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_joins_and_select_on_developers_projects
+ expected = Developer.all.collect { |dev| dev.name }
+ received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_includes
+ expected = Developer.all.collect { |dev| dev.name }
+ received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_having
+ expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_unscope_errors_with_invalid_value
+ assert_raises(ArgumentError) do
+ Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value)
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.all.unscope(:includes, :select, :some_broken_value)
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.order('name DESC').reverse_order.unscope(:reverse_order)
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.order('name DESC').where(name: "Jamis").unscope()
+ end
+ end
+
+ def test_unscope_errors_with_non_where_hash_keys
+ assert_raises(ArgumentError) do
+ Developer.where(name: "Jamis").limit(4).unscope(limit: 4)
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.where(name: "Jamis").unscope("where" => :name)
+ end
+ end
+
+ def test_unscope_errors_with_non_symbol_or_hash_arguments
+ assert_raises(ArgumentError) do
+ Developer.where(name: "Jamis").limit(3).unscope("limit")
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.select("id").unscope("select")
+ end
+
+ assert_raises(ArgumentError) do
+ Developer.select("id").unscope(5)
+ end
+ end
+
def test_order_in_default_scope_should_not_prevail
expected = Developer.all.merge!(:order => 'salary').to_a.collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.all.merge!(:order => 'salary').to_a.collect { |dev| dev.salary }
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index eb9cb91e32..c46060a646 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -18,6 +18,10 @@ class SerializationTest < ActiveRecord::TestCase
}
end
+ def test_include_root_in_json_is_false_by_default
+ assert_equal false, ActiveRecord::Base.include_root_in_json, "include_root_in_json should be false by default but was not"
+ end
+
def test_serialize_should_be_reversible
FORMATS.each do |format|
@serialized = Contact.new.send("to_#{format}")
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 295c7e13fa..726338db14 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -1,6 +1,7 @@
require 'cases/helper'
require 'models/topic'
require 'models/person'
+require 'models/traffic_light'
require 'bcrypt'
class SerializedAttributeTest < ActiveRecord::TestCase
@@ -234,4 +235,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
person = person.reload
assert_equal(insures, person.insures)
end
+
+ def test_regression_serialized_default_on_text_column_with_null_false
+ light = TrafficLight.new
+ assert_equal [], light.state
+ assert_equal [], light.long_state
+ end
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 38b9dd02f0..dadcca5b7f 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -249,10 +249,21 @@ module ActiveRecord
def test_structure_dump
filename = "awesome-file.sql"
- Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db")
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(true)
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
end
+
+ def test_warn_when_external_structure_dump_fails
+ filename = "awesome-file.sql"
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db").returns(false)
+
+ warnings = capture(:stderr) do
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
+ end
+
+ assert_match(/Could not dump the database structure/, warnings)
+ end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
diff --git a/activerecord/test/fixtures/traffic_lights.yml b/activerecord/test/fixtures/traffic_lights.yml
index 6dabd53474..81b4e47959 100644
--- a/activerecord/test/fixtures/traffic_lights.yml
+++ b/activerecord/test/fixtures/traffic_lights.yml
@@ -4,3 +4,7 @@ uk:
- Green
- Red
- Orange
+ long_state:
+ - "Green, go ahead"
+ - "Red, wait"
+ - "Orange, caution light is about to switch" \ No newline at end of file
diff --git a/activerecord/test/models/traffic_light.rb b/activerecord/test/models/traffic_light.rb
index 228f3f7bd4..a6b7edb882 100644
--- a/activerecord/test/models/traffic_light.rb
+++ b/activerecord/test/models/traffic_light.rb
@@ -1,3 +1,4 @@
class TrafficLight < ActiveRecord::Base
serialize :state, Array
+ serialize :long_state, Array
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index cd9835259a..ae779c702a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -219,6 +219,8 @@ ActiveRecord::Schema.define do
t.integer :salary, :default => 70000
t.datetime :created_at
t.datetime :updated_at
+ t.datetime :created_on
+ t.datetime :updated_on
end
create_table :developers_projects, :force => true, :id => false do |t|
@@ -685,6 +687,7 @@ ActiveRecord::Schema.define do
create_table :traffic_lights, :force => true do |t|
t.string :location
t.string :state
+ t.text :long_state, :null => false
t.datetime :created_at
t.datetime :updated_at
end