aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md108
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb2
-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/railties/console_sandbox.rb4
-rw-r--r--activerecord/lib/active_record/scoping/named.rb15
-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/associations/belongs_to_associations_test.rb86
-rw-r--r--activerecord/test/cases/base_test.rb36
-rw-r--r--activerecord/test/cases/migration/logger_test.rb2
-rw-r--r--activerecord/test/cases/migration_test.rb58
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb7
-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
29 files changed, 367 insertions, 116 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 97e81de1ca..d1e3488c1d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -6,6 +6,114 @@
*Dmitry Vorotilin*
+* When `:name` option is provided to `remove_index`, use it if there is no
+ index by the conventional name.
+
+ For example, previously if an index was removed like so
+ `remove_index :values, column: :value, name: 'a_different_name'`
+ the generated SQL would not contain the specified index name,
+ and hence the migration would fail.
+ Fixes #8858.
+
+ *Ezekiel Smithburg*
+
+* Created block to by-pass the prepared statement bindings.
+ This will allow to compose fragments of large SQL statements to
+ avoid multiple round-trips between Ruby and the DB.
+
+ Example:
+
+ sql = Post.connection.unprepared_statement do
+ Post.first.comments.to_sql
+ end
+
+ *Cédric Fabianski*
+
+* Change the semantics of combining scopes to be the same as combining
+ class methods which return scopes. For example:
+
+ class User < ActiveRecord::Base
+ scope :active, -> { where state: 'active' }
+ scope :inactive, -> { where state: 'inactive' }
+ end
+
+ class Post < ActiveRecord::Base
+ def self.active
+ where state: 'active'
+ end
+
+ def self.inactive
+ where state: 'inactive'
+ end
+ end
+
+ ### BEFORE ###
+
+ User.where(state: 'active').where(state: 'inactive')
+ # => SELECT * FROM users WHERE state = 'active' AND state = 'inactive'
+
+ User.active.inactive
+ # => SELECT * FROM users WHERE state = 'inactive'
+
+ Post.active.inactive
+ # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
+
+ ### AFTER ###
+
+ User.active.inactive
+ # => SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'
+
+ Before this change, invoking a scope would merge it into the current
+ scope and return the result. `Relation#merge` applies "last where
+ wins" logic to de-duplicate the conditions, but this lead to
+ confusing and inconsistent behaviour. This fixes that.
+
+ If you really do want the "last where wins" logic, you can opt-in to
+ it like so:
+
+ User.active.merge(User.inactive)
+
+ Fixes #7365.
+
+ *Neeraj Singh* and *Jon Leighton*
+
+* Expand `#cache_key` to consult all relevant updated timestamps.
+
+ Previously only `updated_at` column was checked, now it will
+ consult other columns that received updated timestamps on save,
+ such as `updated_on`. When multiple columns are present it will
+ use the most recent timestamp.
+ Fixes #9033.
+
+ *Brendon Murphy*
+
+* Throw `NotImplementedError` when trying to instantiate `ActiveRecord::Base` or an abstract class.
+
+ *Aaron Weiner*
+
+* Warn when `rake db:structure:dump` with a 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.
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 c3d15ca929..c64b542286 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -125,7 +125,8 @@ module ActiveRecord
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
# http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
- # Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
+ # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8'
+ # supports savepoints.
#
# It is safe to call this method if a database transaction is already open,
# i.e. if #transaction is called within another #transaction block. In case
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..9d624a6648 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
@@ -692,6 +687,12 @@ module ActiveRecord
index_name = index_name(table_name, options)
unless index_name_exists?(table_name, index_name, true)
+ if options.has_key? :name
+ options_without_column = options.dup
+ options_without_column.delete :column
+ index_name_without_column = index_name(table_name, options_without_column)
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ end
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index ff9de712bc..7949bcb5ce 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -118,6 +118,17 @@ module ActiveRecord
@in_use = false
end
+ def unprepared_visitor
+ self.class::BindSubstitution.new self
+ end
+
+ def unprepared_statement
+ old, @visitor = @visitor, unprepared_visitor
+ yield
+ ensure
+ @visitor = old
+ end
+
# Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_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..f88f5742a8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -143,7 +143,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::MySQL.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
end
@@ -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/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index c91e1b3fb9..691b2ab37f 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -489,7 +489,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::PostgreSQL.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
@connection_parameters, @config = connection_parameters, config
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 981c4c96a0..84fa1c7d5a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -113,7 +113,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@visitor = Arel::Visitors::SQLite.new self
else
- @visitor = BindSubstitution.new self
+ @visitor = unprepared_visitor
end
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index e4586f74fc..faef5a9527 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/railties/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
index 90b462fad6..25a591aa43 100644
--- a/activerecord/lib/active_record/railties/console_sandbox.rb
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -1,4 +1,4 @@
-ActiveRecord::Base.connection.begin_db_transaction
+ActiveRecord::Base.connection.begin_transaction
at_exit do
- ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection.rollback_transaction
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 01fbb96b8e..12317601b6 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -159,10 +159,19 @@ module ActiveRecord
end
singleton_class.send(:define_method, name) do |*args|
- options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
- relation = all.merge(options)
+ if body.respond_to?(:call)
+ scope = extension ? body.call(*args).extending(extension) : body.call(*args)
- extension ? relation.extending(extension) : relation
+ if scope
+ default_scoped = scope.default_scoped
+ scope = relation.merge(scope)
+ scope.default_scoped = default_scoped
+ end
+ else
+ scope = body
+ end
+
+ scope || all
end
end
end
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/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 3a6da0e59f..f5316952b8 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
@@ -232,15 +232,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_assigning_nil
- p = Post.find(1)
- c = Comment.find(1)
+ post = Post.find(1)
+ comment = Comment.find(1)
- assert_equal p.id, c.post_id
- assert_equal 2, Post.find(p.id).comments.size
+ assert_equal post.id, comment.post_id
+ assert_equal 2, Post.find(post.id).comments.size
- c.post = nil
+ comment.post = nil
- assert_equal 1, Post.find(p.id).comments.size
+ assert_equal 1, Post.find(post.id).comments.size
end
def test_belongs_to_with_primary_key_counter
@@ -263,56 +263,56 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_belongs_to_counter_with_reassigning
- t1 = Topic.create("title" => "t1")
- t2 = Topic.create("title" => "t2")
- r1 = Reply.new("title" => "r1", "content" => "r1")
- r1.topic = t1
+ topic1 = Topic.create("title" => "t1")
+ topic2 = Topic.create("title" => "t2")
+ reply1 = Reply.new("title" => "r1", "content" => "r1")
+ reply1.topic = topic1
- assert r1.save
- assert_equal 1, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 1, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.topic = Topic.find(t2.id)
+ reply1.topic = Topic.find(topic2.id)
assert_no_queries do
- r1.topic = t2
+ reply1.topic = topic2
end
- assert r1.save
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 1, Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 1, Topic.find(topic2.id).replies.size
- r1.topic = nil
+ reply1.topic = nil
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.topic = t1
+ reply1.topic = topic1
- assert_equal 1, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 1, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
- r1.destroy
+ reply1.destroy
- assert_equal 0, Topic.find(t1.id).replies.size
- assert_equal 0, Topic.find(t2.id).replies.size
+ assert_equal 0, Topic.find(topic1.id).replies.size
+ assert_equal 0, Topic.find(topic2.id).replies.size
end
def test_belongs_to_reassign_with_namespaced_models_and_counters
- t1 = Web::Topic.create("title" => "t1")
- t2 = Web::Topic.create("title" => "t2")
- r1 = Web::Reply.new("title" => "r1", "content" => "r1")
- r1.topic = t1
+ topic1 = Web::Topic.create("title" => "t1")
+ topic2 = Web::Topic.create("title" => "t2")
+ reply1 = Web::Reply.new("title" => "r1", "content" => "r1")
+ reply1.topic = topic1
- assert r1.save
- assert_equal 1, Web::Topic.find(t1.id).replies.size
- assert_equal 0, Web::Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 1, Web::Topic.find(topic1.id).replies.size
+ assert_equal 0, Web::Topic.find(topic2.id).replies.size
- r1.topic = Web::Topic.find(t2.id)
+ reply1.topic = Web::Topic.find(topic2.id)
- assert r1.save
- assert_equal 0, Web::Topic.find(t1.id).replies.size
- assert_equal 1, Web::Topic.find(t2.id).replies.size
+ assert reply1.save
+ assert_equal 0, Web::Topic.find(topic1.id).replies.size
+ assert_equal 1, Web::Topic.find(topic2.id).replies.size
end
def test_belongs_to_counter_after_save
@@ -367,9 +367,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_new_record_with_foreign_key_but_no_object
- c = Client.new("firm_id" => 1)
+ client = Client.new("firm_id" => 1)
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.all.merge!(:order => "id").first, c.firm_with_basic_id
+ assert_equal Firm.all.merge!(:order => "id").first, client.firm_with_basic_id
end
def test_setting_foreign_key_after_nil_target_loaded
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/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..6f90500189 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
@@ -426,6 +462,22 @@ class ReservedWordsMigrationTest < ActiveRecord::TestCase
end
end
+class ExplicitlyNamedIndexMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_by_name
+ connection = Person.connection
+ connection.create_table :values, force: true do |t|
+ t.integer :value
+ end
+
+ assert_nothing_raised ArgumentError do
+ connection.add_index :values, :value, name: 'a_different_name'
+ connection.remove_index :values, column: :value, name: 'a_different_name'
+ end
+
+ connection.drop_table :values rescue nil
+ end
+end
+
if ActiveRecord::Base.connection.supports_bulk_alter?
class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
def setup
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index bd121126e7..b593270352 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -309,7 +309,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size
end
- def test_chaining_should_use_latest_conditions_when_creating
+ def test_chaining_applies_last_conditions_when_creating
post = Topic.rejected.new
assert !post.approved?
@@ -323,13 +323,13 @@ class NamedScopeTest < ActiveRecord::TestCase
assert post.approved?
end
- def test_chaining_should_use_latest_conditions_when_searching
+ def test_chaining_combines_conditions_when_searching
# Normal hash conditions
- assert_equal Topic.where(:approved => true).to_a, Topic.rejected.approved.to_a
- assert_equal Topic.where(:approved => false).to_a, Topic.approved.rejected.to_a
+ assert_equal Topic.where(approved: false).where(approved: true).to_a, Topic.rejected.approved.to_a
+ assert_equal Topic.where(approved: true).where(approved: false).to_a, Topic.approved.rejected.to_a
# Nested hash conditions with same keys
- assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.to_a
+ assert_equal [], Post.with_special_comments.with_very_special_comments.to_a
# Nested hash conditions with different keys
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq
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/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 8298d7534c..26cbb03892 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -714,6 +714,13 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [developers(:poor_jamis)], dev_with_count.to_a
end
+ def test_relation_to_sql
+ sql = Post.connection.unprepared_statement do
+ Post.first.comments.to_sql
+ end
+ assert_no_match(/\?/, sql)
+ end
+
def test_relation_merging_with_arel_equalities_keeps_last_equality
devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
Developer.where(Developer.arel_table[:salary].eq(9000))
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