diff options
Diffstat (limited to 'activerecord/test')
163 files changed, 5196 insertions, 2932 deletions
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb index 1c2942170e..69dfd2503e 100644 --- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb +++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb @@ -9,11 +9,16 @@ module ActiveRecord class FakeAdapter < AbstractAdapter attr_accessor :tables, :primary_keys + @columns = Hash.new { |h,k| h[k] = [] } + class << self + attr_reader :columns + end + def initialize(connection, logger) super @tables = [] @primary_keys = {} - @columns = Hash.new { |h,k| h[k] = [] } + @columns = self.class.columns end def primary_key(table) @@ -28,7 +33,7 @@ module ActiveRecord options[:null]) end - def columns(table_name, message) + def columns(table_name) @columns[table_name] end end diff --git a/activerecord/test/assets/test.txt b/activerecord/test/assets/test.txt new file mode 100644 index 0000000000..6754f0612e --- /dev/null +++ b/activerecord/test/assets/test.txt @@ -0,0 +1 @@ +%00 diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 94497e37c7..852fc0e26e 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -1,170 +1,163 @@ require "cases/helper" -class AdapterTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.connection - end - - def test_tables - tables = @connection.tables - assert tables.include?("accounts") - assert tables.include?("authors") - assert tables.include?("tasks") - assert tables.include?("topics") - end +module ActiveRecord + class AdapterTest < ActiveRecord::TestCase + def setup + @connection = ActiveRecord::Base.connection + end - def test_table_exists? - assert @connection.table_exists?("accounts") - assert !@connection.table_exists?("nonexistingtable") - end + def test_tables + tables = @connection.tables + assert tables.include?("accounts") + assert tables.include?("authors") + assert tables.include?("tasks") + assert tables.include?("topics") + end - def test_indexes - idx_name = "accounts_idx" - - if @connection.respond_to?(:indexes) - indexes = @connection.indexes("accounts") - assert indexes.empty? - - @connection.add_index :accounts, :firm_id, :name => idx_name - indexes = @connection.indexes("accounts") - assert_equal "accounts", indexes.first.table - # OpenBase does not have the concept of a named index - # Indexes are merely properties of columns. - assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter) - assert !indexes.first.unique - assert_equal ["firm_id"], indexes.first.columns - else - warn "#{@connection.class} does not respond to #indexes" + def test_table_exists? + assert @connection.table_exists?("accounts") + assert !@connection.table_exists?("nonexistingtable") + assert !@connection.table_exists?(nil) end - ensure - @connection.remove_index(:accounts, :name => idx_name) rescue nil - end + def test_indexes + idx_name = "accounts_idx" + + if @connection.respond_to?(:indexes) + indexes = @connection.indexes("accounts") + assert indexes.empty? + + @connection.add_index :accounts, :firm_id, :name => idx_name + indexes = @connection.indexes("accounts") + assert_equal "accounts", indexes.first.table + # OpenBase does not have the concept of a named index + # Indexes are merely properties of columns. + assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter) + assert !indexes.first.unique + assert_equal ["firm_id"], indexes.first.columns + else + warn "#{@connection.class} does not respond to #indexes" + end - def test_current_database - if @connection.respond_to?(:current_database) - assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database + ensure + @connection.remove_index(:accounts, :name => idx_name) rescue nil end - end - if current_adapter?(:MysqlAdapter) - def test_charset - assert_not_nil @connection.charset - assert_not_equal 'character_set_database', @connection.charset - assert_equal @connection.show_variable('character_set_database'), @connection.charset + def test_current_database + if @connection.respond_to?(:current_database) + assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database + end end - def test_collation - assert_not_nil @connection.collation - assert_not_equal 'collation_database', @connection.collation - assert_equal @connection.show_variable('collation_database'), @connection.collation - end + if current_adapter?(:MysqlAdapter) + def test_charset + assert_not_nil @connection.charset + assert_not_equal 'character_set_database', @connection.charset + assert_equal @connection.show_variable('character_set_database'), @connection.charset + end - def test_show_nonexistent_variable_returns_nil - assert_nil @connection.show_variable('foo_bar_baz') - end + def test_collation + assert_not_nil @connection.collation + assert_not_equal 'collation_database', @connection.collation + assert_equal @connection.show_variable('collation_database'), @connection.collation + end - def test_not_specifying_database_name_for_cross_database_selects - begin - assert_nothing_raised do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) + def test_show_nonexistent_variable_returns_nil + assert_nil @connection.show_variable('foo_bar_baz') + end - config = ARTest.connection_config - ActiveRecord::Base.connection.execute( - "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ - "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" - ) + def test_not_specifying_database_name_for_cross_database_selects + begin + assert_nothing_raised do + ActiveRecord::Model.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) + + config = ARTest.connection_config + ActiveRecord::Model.connection.execute( + "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ + "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" + ) + end + ensure + ActiveRecord::Model.establish_connection 'arunit' end - ensure - ActiveRecord::Base.establish_connection 'arunit' end end - end - def test_table_alias - def @connection.test_table_alias_length() 10; end - class << @connection - alias_method :old_table_alias_length, :table_alias_length - alias_method :table_alias_length, :test_table_alias_length - end + def test_table_alias + def @connection.test_table_alias_length() 10; end + class << @connection + alias_method :old_table_alias_length, :table_alias_length + alias_method :table_alias_length, :test_table_alias_length + end - assert_equal 'posts', @connection.table_alias_for('posts') - assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') - assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') + assert_equal 'posts', @connection.table_alias_for('posts') + assert_equal 'posts_comm', @connection.table_alias_for('posts_comments') + assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts') - class << @connection - remove_method :table_alias_length - alias_method :table_alias_length, :old_table_alias_length + class << @connection + remove_method :table_alias_length + alias_method :table_alias_length, :old_table_alias_length + end end - end - # test resetting sequences in odd tables in postgreSQL - if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) - require 'models/movie' - require 'models/subscriber' + # test resetting sequences in odd tables in postgreSQL + if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) + require 'models/movie' + require 'models/subscriber' - def test_reset_empty_table_with_custom_pk - Movie.delete_all - Movie.connection.reset_pk_sequence! 'movies' - assert_equal 1, Movie.create(:name => 'fight club').id - end + def test_reset_empty_table_with_custom_pk + Movie.delete_all + Movie.connection.reset_pk_sequence! 'movies' + assert_equal 1, Movie.create(:name => 'fight club').id + end - if ActiveRecord::Base.connection.adapter_name != "FrontBase" - def test_reset_table_with_non_integer_pk - Subscriber.delete_all - Subscriber.connection.reset_pk_sequence! 'subscribers' - sub = Subscriber.new(:name => 'robert drake') - sub.id = 'bob drake' - assert_nothing_raised { sub.save! } + if ActiveRecord::Base.connection.adapter_name != "FrontBase" + def test_reset_table_with_non_integer_pk + Subscriber.delete_all + Subscriber.connection.reset_pk_sequence! 'subscribers' + sub = Subscriber.new(:name => 'robert drake') + sub.id = 'bob drake' + assert_nothing_raised { sub.save! } + end end end - end - def test_uniqueness_violations_are_translated_to_specific_exception - @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" - assert_raises(ActiveRecord::RecordNotUnique) do + def test_uniqueness_violations_are_translated_to_specific_exception @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" + assert_raises(ActiveRecord::RecordNotUnique) do + @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" + end end - end - def test_foreign_key_violations_are_translated_to_specific_exception - unless @connection.adapter_name == 'SQLite' - assert_raises(ActiveRecord::InvalidForeignKey) do - # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method - if @connection.prefetch_primary_key? - id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) - @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" - else - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + def test_foreign_key_violations_are_translated_to_specific_exception + unless @connection.adapter_name == 'SQLite' + assert_raises(ActiveRecord::InvalidForeignKey) do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end end end end - end - def test_disable_referential_integrity - assert_nothing_raised do - @connection.disable_referential_integrity do - # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method - if @connection.prefetch_primary_key? - id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) - @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" - else - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + def test_disable_referential_integrity + assert_nothing_raised do + @connection.disable_referential_integrity do + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end + # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block + # and will fail (at least on Oracle) + @connection.execute "DELETE FROM fk_test_has_fk" end - # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block - # and will fail (at least on Oracle) - @connection.execute "DELETE FROM fk_test_has_fk" end end end - - def test_deprecated_visitor_for - visitor_klass = Class.new(Arel::Visitors::ToSql) - Arel::Visitors::VISITORS['fuuu'] = visitor_klass - pool = stub(:spec => stub(:config => { :adapter => 'fuuu' })) - visitor = assert_deprecated { - ActiveRecord::ConnectionAdapters::AbstractAdapter.visitor_for(pool) - } - assert visitor.is_a?(visitor_klass) - end end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 2a89430da9..fa2ba8d592 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -3,13 +3,13 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup super - @connection = ActiveRecord::Base.connection + @connection = ActiveRecord::Model.connection end def test_mysql_reconnect_attribute_after_connection_with_reconnect_true run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true})) - assert ActiveRecord::Base.connection.raw_connection.reconnect + ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => true})) + assert ActiveRecord::Model.connection.raw_connection.reconnect end end @@ -25,8 +25,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase def test_mysql_reconnect_attribute_after_connection_with_reconnect_false run_without_connection do |orig_connection| - ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false})) - assert !ActiveRecord::Base.connection.raw_connection.reconnect + ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => false})) + assert !ActiveRecord::Model.connection.raw_connection.reconnect end end @@ -114,7 +114,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase # Test that MySQL allows multiple results for stored procedures if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results - rows = ActiveRecord::Base.connection.select_rows('CALL ten();') + rows = ActiveRecord::Model.connection.select_rows('CALL ten();') assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}" assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'" end @@ -123,11 +123,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase private def run_without_connection - original_connection = ActiveRecord::Base.remove_connection + original_connection = ActiveRecord::Model.remove_connection begin yield original_connection ensure - ActiveRecord::Base.establish_connection(original_connection) + ActiveRecord::Model.establish_connection(original_connection) end end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 146b77a95c..7fe2c02c04 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -17,11 +17,7 @@ module ActiveRecord end def test_client_encoding - if "<3".respond_to?(:encoding) - assert_equal Encoding::UTF_8, @conn.client_encoding - else - assert_equal 'utf8', @conn.client_encoding - end + assert_equal Encoding::UTF_8, @conn.client_encoding end def test_exec_insert_number @@ -41,13 +37,11 @@ module ActiveRecord value = result.rows.last.last - if "<3".respond_to?(:encoding) - # FIXME: this should probably be inside the mysql AR adapter? - value.force_encoding(@conn.client_encoding) + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) - # The strings in this file are utf-8, so transcode to utf-8 - value.encode!(Encoding::UTF_8) - end + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) assert_equal str, value end diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index a2155d1dd1..29f885c6e7 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -13,8 +13,8 @@ module ActiveRecord table = Post.table_name @db_name = db - @omgpost = Class.new(Post) do - set_table_name "#{db}.#{table}" + @omgpost = Class.new(ActiveRecord::Base) do + self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end end @@ -23,6 +23,10 @@ module ActiveRecord assert @omgpost.find(:first) end + def test_primary_key + assert_equal 'id', @omgpost.primary_key + end + def test_table_exists? name = @omgpost.table_name assert @connection.table_exists?(name), "#{name} table should exist" diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 26091c713b..8e2b9ca9a5 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -3,7 +3,7 @@ require "cases/helper" class MysqlConnectionTest < ActiveRecord::TestCase def setup super - @connection = ActiveRecord::Base.connection + @connection = ActiveRecord::Model.connection end def test_no_automatic_reconnection_after_timeout @@ -32,11 +32,11 @@ class MysqlConnectionTest < ActiveRecord::TestCase private def run_without_connection - original_connection = ActiveRecord::Base.remove_connection + original_connection = ActiveRecord::Model.remove_connection begin yield original_connection ensure - ActiveRecord::Base.establish_connection(original_connection) + ActiveRecord::Model.establish_connection(original_connection) end end end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb new file mode 100644 index 0000000000..68ed361aeb --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class Mysql2Adapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %(developers | const), explain + end + + def test_explain_with_eager_loading + explain = Developer.where(:id => 1).includes(:audit_logs).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %(developers | const), explain + assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain + assert_match %(audit_logs | ALL), explain + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 858d1da2dd..d5676bc522 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -13,8 +13,8 @@ module ActiveRecord table = Post.table_name @db_name = db - @omgpost = Class.new(Post) do - set_table_name "#{db}.#{table}" + @omgpost = Class.new(ActiveRecord::Base) do + self.table_name = "#{db}.#{table}" def self.name; 'Post'; end end end @@ -23,6 +23,10 @@ module ActiveRecord assert @omgpost.find(:first) end + def test_primary_key + assert_equal 'id', @omgpost.primary_key + end + def test_table_exists? name = @omgpost.table_name assert @connection.table_exists?(name), "#{name} table should exist" diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index e4746d4aa3..447d729e52 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -21,6 +21,18 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) end + def test_add_index + # add_index calls index_name_exists? which can't work since execute is stubbed + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*| + false + end + + expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') + assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'") + + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?) + end + private def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 21b97b3b39..4baec749ff 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -2,6 +2,9 @@ require "cases/helper" module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase + class NonExistentTable < ActiveRecord::Base + end + def setup super @connection = ActiveRecord::Base.connection @@ -10,5 +13,17 @@ module ActiveRecord def test_encoding assert_not_nil @connection.encoding end + + # Ensure, we can set connection params using the example of Generic + # Query Optimizer (geqo). It is 'on' per default. + def test_connection_options + params = ActiveRecord::Base.connection_config.dup + params[:options] = "-c geqo=off" + NonExistentTable.establish_connection(params) + + # Verify the connection param has been applied. + expect = NonExistentTable.connection.query('show geqo').first.first + assert_equal 'off', expect + end end end diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb new file mode 100644 index 0000000000..0b61f61572 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -0,0 +1,28 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLAdapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(QUERY PLAN), explain + assert_match %(Index Scan using developers_pkey on developers), explain + end + + def test_explain_with_eager_loading + explain = Developer.where(:id => 1).includes(:audit_logs).explain + assert_match %(QUERY PLAN), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match %(Index Scan using developers_pkey on developers), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match %(Seq Scan on audit_logs), explain + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb new file mode 100644 index 0000000000..1644a58d92 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -0,0 +1,149 @@ +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlHstoreTest < ActiveRecord::TestCase + class Hstore < ActiveRecord::Base + self.table_name = 'hstores' + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.transaction do + @connection.create_table('hstores') do |t| + t.hstore 'tags', :default => '' + end + end + rescue ActiveRecord::StatementInvalid + return skip "do not test on PG without hstore" + end + @column = Hstore.columns.find { |c| c.name == 'tags' } + end + + def teardown + @connection.execute 'drop table if exists hstores' + end + + def test_column + assert_equal :hstore, @column.type + end + + def test_type_cast_hstore + assert @column + + data = "\"1\"=>\"2\"" + hash = @column.class.string_to_hstore data + assert_equal({'1' => '2'}, hash) + assert_equal({'1' => '2'}, @column.type_cast(data)) + + assert_equal({}, @column.type_cast("")) + assert_equal({'key'=>nil}, @column.type_cast('key => NULL')) + assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b"))) + end + + def test_gen1 + assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''})) + end + + def test_gen2 + assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''})) + end + + def test_gen3 + assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''})) + end + + def test_gen4 + assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''})) + end + + def test_parse1 + assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c')) + end + + def test_parse2 + assert_equal({" " => " "}, @column.type_cast("\\ =>\\ ")) + end + + def test_parse3 + assert_equal({"=" => ">"}, @column.type_cast("==>>")) + end + + def test_parse4 + assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w')) + end + + def test_parse5 + assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w')) + end + + def test_parse6 + assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w')) + end + + def test_parse7 + assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w')) + end + + def test_rewrite + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.find :first + x.tags = { '"a\'' => 'b' } + assert x.save! + end + + + def test_select + @connection.execute "insert into hstores (tags) VALUES ('1=>2')" + x = Hstore.find :first + assert_equal({'1' => '2'}, x.tags) + end + + def test_select_multikey + @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')" + x = Hstore.find :first + assert_equal({'1' => '2', '2' => '3'}, x.tags) + end + + def test_create + assert_cycle('a' => 'b', '1' => '2') + end + + def test_nil + assert_cycle('a' => nil) + end + + def test_quotes + assert_cycle('a' => 'b"ar', '1"foo' => '2') + end + + def test_whitespace + assert_cycle('a b' => 'b ar', '1"foo' => '2') + end + + def test_backslash + assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2') + end + + def test_comma + assert_cycle('a, b' => 'bar', '1"foo' => '2') + end + + def test_arrow + assert_cycle('a=>b' => 'bar', '1"foo' => '2') + end + + private + def assert_cycle hash + x = Hstore.create!(:tags => hash) + x.reload + assert_equal(hash, x.tags) + + # make sure updates work + x.tags = hash + x.save! + x.reload + assert_equal(hash, x.tags) + end +end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index d57794daf8..898d28456b 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -179,6 +179,12 @@ module ActiveRecord assert_equal Arel.sql('$2'), bind end + def test_partial_index + @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100" + index = @connection.indexes('ex').find { |idx| idx.name == 'partial' } + assert_equal "(number > 100)", index.where + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index c8f8714f66..18670b4177 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -10,36 +10,39 @@ class SchemaTest < ActiveRecord::TestCase INDEX_A_NAME = 'a_index_things_on_name' INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema' INDEX_C_NAME = 'c_index_full_text_search' + INDEX_D_NAME = 'd_index_things_on_description_desc' INDEX_A_COLUMN = 'name' INDEX_B_COLUMN_S1 = 'email' INDEX_B_COLUMN_S2 = 'moment' INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))} + INDEX_D_COLUMN = 'description' COLUMNS = [ 'id integer', 'name character varying(50)', 'email character varying(50)', + 'description character varying(100)', 'moment timestamp without time zone default now()' ] PK_TABLE_NAME = 'table_with_pk' class Thing1 < ActiveRecord::Base - set_table_name "test_schema.things" + self.table_name = "test_schema.things" end class Thing2 < ActiveRecord::Base - set_table_name "test_schema2.things" + self.table_name = "test_schema2.things" end class Thing3 < ActiveRecord::Base - set_table_name 'test_schema."things.table"' + self.table_name = 'test_schema."things.table"' end class Thing4 < ActiveRecord::Base - set_table_name 'test_schema."Things"' + self.table_name = 'test_schema."Things"' end class Thing5 < ActiveRecord::Base - set_table_name 'things' + self.table_name = 'things' end def setup @@ -54,6 +57,8 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" + @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" + @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);" @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" end @@ -63,11 +68,15 @@ class SchemaTest < ActiveRecord::TestCase end def test_schema_change_with_prepared_stmt + altered = false @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] @connection.exec_query "alter table developers add column zomg int", 'sql', [] + altered = true @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]] ensure - @connection.exec_query "alter table developers drop column if exists zomg", 'sql', [] + # We are not using DROP COLUMN IF EXISTS because that syntax is only + # supported by pg 9.X + @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered end def test_table_exists? @@ -184,11 +193,15 @@ class SchemaTest < ActiveRecord::TestCase end def test_dump_indexes_for_schema_one - do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1) + do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) end def test_dump_indexes_for_schema_two - do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2) + do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN) + end + + def test_dump_indexes_for_schema_multiple_schemas_in_search_path + do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN) end def test_with_uppercase_index_name @@ -288,13 +301,16 @@ class SchemaTest < ActiveRecord::TestCase @connection.schema_search_path = "'$user', public" end - def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name) + def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name) with_schema_search_path(this_schema_name) do indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name} - assert_equal 2,indexes.size + assert_equal 3,indexes.size do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name) do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name) + do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name) + + assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN] end end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 5f08f79171..9e7b08ef34 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -1,3 +1,5 @@ +require 'cases/helper' + class PostgreSQLUtilsTest < ActiveSupport::TestCase include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 303ba9245a..66e07b71a0 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -14,7 +14,7 @@ class ViewTest < ActiveRecord::TestCase ] class ThingView < ActiveRecord::Base - set_table_name 'test_schema.view_things' + self.table_name = 'test_schema.view_things' end def setup diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb new file mode 100644 index 0000000000..b227bce680 --- /dev/null +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -0,0 +1,26 @@ +require "cases/helper" +require 'models/developer' + +module ActiveRecord + module ConnectionAdapters + class SQLite3Adapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + end + + def test_explain_with_eager_loading + explain = Developer.where(:id => 1).includes(:audit_logs).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain + assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain) + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain + assert_match(/(SCAN )?TABLE audit_logs/, explain) + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index e0152e7ccf..46da1b0a2b 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -70,9 +70,9 @@ module ActiveRecord assert_equal bd.to_f, @conn.type_cast(bd, nil) end - def test_type_cast_unknown + def test_type_cast_unknown_should_raise_error obj = Class.new.new - assert_equal YAML.dump(obj), @conn.type_cast(obj, nil) + assert_raise(TypeError) { @conn.type_cast(obj, nil) } end def test_quoted_id diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index eb6f071dc1..17bde6cb62 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -23,8 +23,6 @@ module ActiveRecord end def test_column_types - return skip('only test encoding on 1.9') unless "<3".encoding_aware? - owner = Owner.create!(:name => "hello".encode('ascii-8bit')) owner.reload select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', ' @@ -144,8 +142,6 @@ module ActiveRecord end def test_quote_binary_column_escapes_it - return unless "<3".respond_to?(:encode) - DualEncoding.connection.execute(<<-eosql) CREATE TABLE dual_encodings ( id integer PRIMARY KEY AUTOINCREMENT, @@ -158,6 +154,7 @@ module ActiveRecord binary.save! assert_equal str, binary.data + ensure DualEncoding.connection.drop_table('dual_encodings') end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index ff376a68d8..6733f3e889 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -61,7 +61,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_duplicated_includes - categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null") + categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.all.size @@ -69,7 +69,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_cascaded_eager_association_loading_with_twice_includes_edge_cases - categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null") + categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts) assert_nothing_raised do assert_equal 3, categories.count assert_equal 3, categories.all.size @@ -127,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase silly.parent_id = 1 assert silly.save - topics = Topic.find(:all, :include => :replies, :order => 'topics.id, replies_topics.id') + topics = Topic.find(:all, :include => :replies, :order => ['topics.id', 'replies_topics.id']) assert_no_queries do assert_equal 2, topics[0].replies.size assert_equal 0, topics[1].replies.size diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index d75791cab9..7965bb404c 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -4,7 +4,7 @@ require 'models/tagging' module Namespaced class Post < ActiveRecord::Base - set_table_name 'posts' + self.table_name = 'posts' has_one :tagging, :as => :taggable, :class_name => 'Tagging' end end diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 2cf9f89c3c..1e1958410c 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -6,7 +6,6 @@ require 'models/comment' require 'models/category' require 'models/categorization' require 'models/tagging' -require 'active_support/core_ext/array/random_access' module Remembered extend ActiveSupport::Concern diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index c6e451fc57..b79c69bbb5 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -61,7 +61,10 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_loading_conditions_with_or - posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'") + posts = authors(:david).posts.find( + :all, :include => :comments, :references => :comments, + :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'" + ) assert_nil posts.detect { |p| p.author_id != authors(:david).id }, "expected to find only david's posts" end @@ -164,7 +167,7 @@ class EagerAssociationTest < ActiveRecord::TestCase car_post.categories << categories(:technology) comment = car_post.comments.create!(:body => "hmm") - categories = Category.find(:all, :conditions => ["posts.id=?", car_post.id], + categories = Category.find(:all, :conditions => { 'posts.id' => car_post.id }, :include => {:posts => :comments}) categories.each do |category| assert_equal [comment], category.posts[0].comments @@ -252,6 +255,50 @@ class EagerAssociationTest < ActiveRecord::TestCase end end + def test_nested_loading_through_has_one_association + aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order + aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'author_addresses.id') + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order_on_association + aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'authors.id') + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_order_on_nested_association + aa = AuthorAddress.find(author_addresses(:david_address).id, :include => {:author => :posts}, :order => 'posts.id') + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions + aa = AuthorAddress.find( + author_addresses(:david_address).id, :include => {:author => :posts}, + :conditions => "author_addresses.id > 0", :references => :author_addresses + ) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions_on_association + aa = AuthorAddress.find( + author_addresses(:david_address).id, :include => {:author => :posts}, + :conditions => "authors.id > 0", :references => :authors + ) + assert_equal aa.author.posts.count, aa.author.posts.length + end + + def test_nested_loading_through_has_one_association_with_conditions_on_nested_association + aa = AuthorAddress.find( + author_addresses(:david_address).id, :include => {:author => :posts}, + :conditions => "posts.id > 0", :references => :posts + ) + assert_equal aa.author.posts.count, aa.author.posts.length + end + def test_eager_association_loading_with_belongs_to_and_foreign_keys pets = Pet.find(:all, :include => :owner) assert_equal 3, pets.length @@ -297,7 +344,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name assert_nothing_raised do - Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4]) + ActiveSupport::Deprecation.silence do + Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4]) + end end end @@ -316,7 +365,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4]) + ActiveSupport::Deprecation.silence do + Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4]) + end end end @@ -329,7 +380,9 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id') assert_nothing_raised do - Comment.find(:all, :include => :post, :order => quoted_posts_id) + ActiveSupport::Deprecation.silence do + Comment.find(:all, :include => :post, :order => quoted_posts_id) + end end end @@ -493,22 +546,27 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + posts = ActiveSupport::Deprecation.silence do + Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + end assert_equal 2, posts.size - count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + count = ActiveSupport::Deprecation.silence do + Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ]) + end assert_equal count, posts.size end def test_eager_with_has_many_and_limit_and_high_offset - posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' }) assert_equal 0, posts.size end def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions assert_queries(1) do posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, - :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]) + :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ], + :references => [:authors, :comments]) assert_equal 0, posts.size end end @@ -522,7 +580,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_count_eager_with_has_many_and_limit_and_high_offset - posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ]) + posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => { 'authors.name' => 'David' }) assert_equal 0, posts end @@ -534,7 +592,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional author = authors(:david) author_posts_without_comments = author.posts.select { |post| post.comments.blank? } - assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null') + assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null', :references => :comments) end def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional @@ -573,6 +631,7 @@ class EagerAssociationTest < ActiveRecord::TestCase posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", + :references => :comments, :limit => 2 ) assert_equal 2, posts.size @@ -580,6 +639,7 @@ class EagerAssociationTest < ActiveRecord::TestCase count = Post.count( :include => [ :comments, :author ], :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", + :references => [:authors, :comments], :limit => 2 ) assert_equal count, posts.size @@ -589,7 +649,8 @@ class EagerAssociationTest < ActiveRecord::TestCase posts = nil Post.send(:with_scope, :find => { :include => :comments, - :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'" + :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", + :references => :comments }) do posts = authors(:david).posts.find(:all, :limit => 2) assert_equal 2, posts.size @@ -597,7 +658,8 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.send(:with_scope, :find => { :include => [ :comments, :author ], - :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')" + :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", + :references => [:authors, :comments] }) do count = Post.count(:limit => 2) assert_equal count, posts.size @@ -609,6 +671,7 @@ class EagerAssociationTest < ActiveRecord::TestCase posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'", + :references => :comments, :limit => 2 ) assert_equal 2, posts.size @@ -616,6 +679,7 @@ class EagerAssociationTest < ActiveRecord::TestCase count = Post.count( :include => [ :comments, :author ], :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')", + :references => [:authors, :comments], :limit => 2 ) assert_equal count, posts.size @@ -623,9 +687,15 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope - posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2) + posts_with_explicit_order = Post.find( + :all, :conditions => 'comments.id is not null', :references => :comments, + :include => :comments, :order => 'posts.id DESC', :limit => 2 + ) posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do - Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2) + Post.find( + :all, :conditions => 'comments.id is not null', + :references => :comments, :include => :comments, :limit => 2 + ) end assert_equal posts_with_explicit_order, posts_with_scoped_order end @@ -738,17 +808,49 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_limited_eager_with_order - assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1) - assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1) + assert_equal( + posts(:thinking, :sti_comments), + Post.find( + :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + :order => 'UPPER(posts.title)', :limit => 2, :offset => 1 + ) + ) + assert_equal( + posts(:sti_post_and_comments, :sti_comments), + Post.find( + :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1 + ) + ) end def test_limited_eager_with_multiple_order_columns - assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1) - assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1) + assert_equal( + posts(:thinking, :sti_comments), + Post.find( + :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1 + ) + ) + assert_equal( + posts(:sti_post_and_comments, :sti_comments), + Post.find( + :all, :include => [:author, :comments], :conditions => { 'authors.name' => 'David' }, + :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1 + ) + ) end def test_limited_eager_with_numeric_in_association - assert_equal people(:david, :susan), Person.find(:all, :include => [:readers, :primary_contact, :number1_fan], :conditions => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0) + assert_equal( + people(:david, :susan), + Person.find( + :all, :include => [:readers, :primary_contact, :number1_fan], + :conditions => "number1_fans_people.first_name like 'M%'", + :references => :number1_fans_people, + :order => 'people.id', :limit => 2, :offset => 0 + ) + ) end def test_preload_with_interpolation @@ -863,11 +965,11 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_count_with_include if current_adapter?(:SybaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15") + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15", :references => :comments) elsif current_adapter?(:OpenBaseAdapter) - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15") + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15", :references => :comments) else - assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15", :references => :comments) end end @@ -878,7 +980,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_conditions_on_join_table_with_include_and_limit - assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size + assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => { 'developers_projects.access_level' => 1 }, :limit => 5).size end def test_order_on_join_table_with_include_and_limit @@ -1060,4 +1162,11 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first end end + + def test_deep_including_through_habtm + posts = Post.find(:all, :include => {:categories => :categorizations}, :order => "posts.id") + assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } + assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } + assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length } + end end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 8dc1423375..d7c489c2b5 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -36,11 +36,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_extensions - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - david = developers(:david) assert_equal projects(:action_controller), david.projects.find_most_recent @@ -51,11 +46,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase end def test_marshalling_named_extensions - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - david = developers(:david) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent @@ -71,6 +61,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', extension_name(MyApplication::Business::Developer) end + def test_proxy_association_after_scoped + post = posts(:welcome) + assert_equal post.association(:comments), post.comments.the_association + assert_equal post.association(:comments), post.comments.scoped.the_association + end + private def extension_name(model) 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 34d90cc395..f457dfb9b3 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 @@ -23,7 +23,7 @@ require 'models/treaty' require 'active_support/core_ext/string/conversions' class ProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'projects' + self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperForProjectWithAfterCreateHook", :join_table => "developers_projects", @@ -39,7 +39,7 @@ class ProjectWithAfterCreateHook < ActiveRecord::Base end class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithAfterCreateHook", :join_table => "developers_projects", @@ -48,7 +48,7 @@ class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base end class ProjectWithSymbolsForKeys < ActiveRecord::Base - set_table_name 'projects' + self.table_name = 'projects' has_and_belongs_to_many :developers, :class_name => "DeveloperWithSymbolsForKeys", :join_table => :developers_projects, @@ -57,7 +57,7 @@ class ProjectWithSymbolsForKeys < ActiveRecord::Base end class DeveloperWithSymbolsForKeys < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "ProjectWithSymbolsForKeys", :join_table => :developers_projects, @@ -66,7 +66,7 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base end class DeveloperWithCounterSQL < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' has_and_belongs_to_many :projects, :class_name => "DeveloperWithCounterSQL", :join_table => "developers_projects", @@ -77,7 +77,7 @@ end class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, - :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings + :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings def setup_data_for_habtm_case ActiveRecord::Base.connection.execute('delete from countries_treaties') @@ -445,6 +445,26 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert david.projects(true).empty? end + def test_destroy_associations_destroys_multiple_associations + george = parrots(:george) + assert !george.pirates.empty? + assert !george.treasures.empty? + + assert_no_difference "Pirate.count" do + assert_no_difference "Treasure.count" do + george.destroy_associations + end + end + + join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}") + assert join_records.empty? + assert george.pirates(true).empty? + + join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}") + assert join_records.empty? + assert george.treasures(true).empty? + end + def test_deprecated_push_with_attributes_was_removed jamis = developers(:jamis) assert_raise(NoMethodError) do @@ -659,7 +679,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_table_alias - assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size + assert_equal( + 3, + Developer.find( + :all, :include => {:projects => :developers}, :references => :developers_projects_join, + :conditions => 'developers_projects_join.joined_on IS NOT NULL' + ).size + ) end def test_join_with_group @@ -669,7 +695,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end Project.columns.each { |c| group << "projects.#{c.name}" } - assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size + assert_equal( + 3, + Developer.find( + :all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', + :references => :developers_projects_join, :group => group.join(",") + ).size + ) end def test_find_grouped @@ -805,12 +837,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase # clear cache possibly created by other tests david.projects.reset_column_information - # One query for columns, one for primary key - assert_queries(2) { david.projects.columns; david.projects.columns } + assert_queries(1) { david.projects.columns; david.projects.columns } ## and again to verify that reset_column_information clears the cache correctly david.projects.reset_column_information - assert_queries(2) { david.projects.columns; david.projects.columns } + assert_queries(1) { david.projects.columns; david.projects.columns } end def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index cddd2a6f8c..3967009c82 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -8,6 +8,7 @@ require 'models/reply' require 'models/category' require 'models/post' require 'models/author' +require 'models/essay' require 'models/comment' require 'models/person' require 'models/reader' @@ -41,12 +42,37 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase end end +class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase + class Invoice < ActiveRecord::Base + has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items" + end + + def test_should_count_distinct_results + invoice = Invoice.new + invoice.custom_line_items << LineItem.new(:amount => 0) + invoice.custom_line_items << LineItem.new(:amount => 0) + invoice.save! + + assert_equal 1, invoice.custom_line_items.count + end +end + +class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase + fixtures :authors, :posts, :comments + + def test_should_generate_valid_sql + author = authors(:david) + # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression + # if the reorder clauses are not correctly handled + assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.taggings_count DESC').last + end +end class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, - :people, :posts, :readers, :taggings, :cars + :people, :posts, :readers, :taggings, :cars, :essays def setup Client.destroyed_client_ids.clear @@ -712,6 +738,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size end + def test_find_or_initialize_returns_the_instantiated_object + client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") + assert_equal client, companies(:first_firm).clients_of_firm[-1] + end + + def test_find_or_initialize_only_instantiates_a_single_object + number_of_clients = Client.count + companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save! + companies(:first_firm).save! + assert_equal number_of_clients+1, Client.count + end + def test_find_or_create_with_hash post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') @@ -1114,16 +1152,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_nil companies(:leetsoft).reload.client_of assert_nil companies(:jadedpixel).reload.client_of - assert_equal num_accounts, Account.count end def test_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.companies.create(:name => 'child') + assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_restrict_when_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.companies.create(:name => 'child') + + assert !firm.companies.empty? + + firm.destroy + + assert !firm.errors.empty? + + assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.companies.exists?(:name => 'child') + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before end def test_included_in_collection @@ -1227,6 +1291,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert company.clients_using_sql.loaded? end + def test_get_ids_for_ordered_association + assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids + end + def test_assign_ids_ignoring_blanks firm = Firm.create!(:name => 'Apple') firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, ''] @@ -1376,6 +1444,32 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_custom_primary_key_on_new_record_should_fetch_with_query + author = Author.new(:name => "David") + assert !author.essays.loaded? + + assert_queries 1 do + assert_equal 1, author.essays.size + end + + assert_equal author.essays, Essay.find_all_by_writer_id("David") + + end + + def test_has_many_custom_primary_key + david = authors(:david) + assert_equal david.essays, Essay.find_all_by_writer_id("David") + end + + def test_blank_custom_primary_key_on_new_record_should_not_run_queries + author = Author.new + assert !author.essays.loaded? + + assert_queries 0 do + assert_equal 0, author.essays.size + end + end + def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query firm = companies(:first_firm) firm.clients.class # force load target @@ -1597,4 +1691,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb2], car.bulbs assert_equal [bulb2], car.reload.bulbs end + + def test_building_has_many_association_with_restrict_dependency + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_many :companies, :dependent => :restrict } + assert_not_deprecated { klass.has_many :companies } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index b703c96ec1..9cc09194dc 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -67,6 +67,31 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_associate_existing_record_twice_should_add_records_twice + post = posts(:thinking) + person = people(:david) + + assert_difference 'post.people.count', 2 do + post.people << person + post.people << person + end + end + + def test_add_two_instance_and_then_deleting + post = posts(:thinking) + person = people(:david) + + post.people << person + post.people << person + + counts = ['post.people.count', 'post.people.to_a.count', 'post.readers.count', 'post.readers.to_a.count'] + assert_difference counts, -2 do + post.people.delete(person) + end + + assert !post.people.reload.include?(person) + end + def test_associating_new assert_queries(1) { posts(:thinking) } new_person = nil # so block binding catches it @@ -503,6 +528,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort end + def test_get_ids_for_has_many_through_with_conditions_should_not_preload + Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc)) + ActiveRecord::Associations::Preloader.expects(:new).never + posts(:welcome).misc_tag_ids + end + def test_get_ids_for_loaded_associations person = people(:michael) person.posts(true) @@ -707,7 +738,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_select_chosen_fields_only author = authors(:david) - assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys + assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort end def test_get_has_many_through_belongs_to_ids_with_conditions @@ -816,7 +847,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_preloading_empty_through_association_via_joins person = Person.create!(:first_name => "Gaga") - person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').includes(:posts).to_a.first + person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first assert person.posts.loaded?, 'person.posts should be loaded' assert_equal [], person.posts @@ -825,4 +856,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_explicitly_joining_join_table assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet end + + def test_has_many_through_with_polymorphic_source + post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar" + assert_equal [tags(:general)], post.reload.tags + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 26931e3e85..246877bbed 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -157,11 +157,62 @@ class HasOneAssociationsTest < ActiveRecord::TestCase end def test_dependence_with_restrict - firm = RestrictedFirm.new(:name => 'restrict') - firm.save! + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + firm = RestrictedFirm.create!(:name => 'restrict') firm.create_account(:credit_limit => 10) + assert_not_nil firm.account + assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.create_account(:credit_limit => 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end + + def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name + old_backend = I18n.backend + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}} + + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = false + + firm = RestrictedFirm.create!(:name => 'restrict') + firm.create_account(:credit_limit => 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first + assert RestrictedFirm.exists?(:name => 'restrict') + assert firm.account.present? + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + I18n.backend = old_backend end def test_successful_build_association @@ -456,4 +507,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal car.id, bulb.attributes_after_initialize['car_id'] end + + def test_building_has_one_association_with_dependent_restrict + option_before = ActiveRecord::Base.dependent_restrict_raises + ActiveRecord::Base.dependent_restrict_raises = true + + klass = Class.new(ActiveRecord::Base) + + assert_deprecated { klass.has_one :account, :dependent => :restrict } + assert_not_deprecated { klass.has_one :account } + ensure + ActiveRecord::Base.dependent_restrict_raises = option_before + end end diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index e5e9ca6131..68a1e62328 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -67,7 +67,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_find_with_implicit_inner_joins_does_not_set_associations authors = Author.joins(:posts).select('authors.*') assert !authors.empty?, "expected authors to be non-empty" - assert authors.all? {|a| !a.send(:instance_variable_names).include?("@posts")}, "expected no authors to have the @posts association loaded" + assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded" end def test_count_honors_implicit_inner_joins diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 4ce8b85098..301755249c 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -362,7 +362,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end assert_raise ActiveRecord::EagerLoadPolymorphicError do - tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1') + tags(:general).taggings.find(:all, :include => :taggable, :references => :bogus_table, :conditions => 'bogus_table.column = 1') end end @@ -733,7 +733,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}" Post.find(post_id).update_column :type, class_name klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) - klass.set_table_name 'posts' + klass.table_name = 'posts' klass.send(association, association_name, :as => :taggable, :dependent => dependency) klass.find(post_id) end diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 530f5212a2..03d99d19f6 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -505,7 +505,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id'), + Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags ) end @@ -526,7 +526,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins # Pointless condition to force single-query loading assert_includes_and_joins_equal( - Author.where('tags.id = tags.id'), + Author.where('tags.id = tags.id').references(:tags), [authors(:bob)], :misc_post_first_blue_tags_2 ) end @@ -545,6 +545,15 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase assert_equal [organizations(:nsa)], organizations end + def test_nested_has_many_through_should_not_be_autosaved + c = Categorization.new + c.author = authors(:david) + c.post_taggings.to_a + assert !c.post_taggings.empty? + c.save + assert !c.post_taggings.empty? + end + private def assert_includes_and_joins_equal(query, expected, association) diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index ffe2993e0f..017905e0ac 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/computer' require 'models/developer' require 'models/project' require 'models/company' @@ -28,7 +29,7 @@ class AssociationsTest < ActiveRecord::TestCase molecule.electrons.create(:name => 'electron_1') molecule.electrons.create(:name => 'electron_2') - liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null') + liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null') assert_equal 1, liquids[0].molecules.length end @@ -128,6 +129,11 @@ class AssociationsTest < ActiveRecord::TestCase end end + def test_association_with_references + firm = companies(:first_firm) + assert_equal ['foo'], firm.association_with_references.scoped.references_values + end + end class AssociationProxyTest < ActiveRecord::TestCase @@ -273,3 +279,18 @@ class OverridingAssociationsTest < ActiveRecord::TestCase ) end end + +class GeneratedMethodsTest < ActiveRecord::TestCase + fixtures :developers, :computers, :posts, :comments + def test_association_methods_override_attribute_methods_of_same_name + assert_equal(developers(:david), computers(:workstation).developer) + # this next line will fail if the attribute methods module is generated lazily + # after the association methods module is generated + assert_equal(developers(:david), computers(:workstation).developer) + assert_equal(developers(:david).id, computers(:workstation)[:developer]) + end + + def test_model_method_overrides_association_method + assert_equal(comments(:greetings).body, posts(:welcome).first_comment) + end +end diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index e03ed33591..764305459d 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'active_support/core_ext/object/inclusion' +require 'thread' module ActiveRecord module AttributeMethods @@ -14,8 +15,18 @@ module ActiveRecord def setup @klass = Class.new do + def self.superclass; Base; end + def self.active_record_super; Base; end + def self.base_class; self; end + include ActiveRecord::AttributeMethods - include ActiveRecord::AttributeMethods::Read + + def self.define_attribute_methods + # Created in the inherited/included hook for "proper" ARs + @attribute_methods_mutex ||= Mutex.new + + super + end def self.column_names %w{ one two three } @@ -33,9 +44,6 @@ module ActiveRecord [name, FakeColumn.new(name)] }] end - - def self.serialized_attributes; {}; end - def self.base_class; self; end end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index b1b41fed0d..4078b7eb0b 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -35,6 +35,30 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert !t.attribute_present?("content") end + def test_attribute_present_with_booleans + b1 = Boolean.new + b1.value = false + assert b1.attribute_present?(:value) + + b2 = Boolean.new + b2.value = true + assert b2.attribute_present?(:value) + + b3 = Boolean.new + assert !b3.attribute_present?(:value) + + b4 = Boolean.new + b4.value = false + b4.save! + assert Boolean.find(b4.id).attribute_present?(:value) + end + + def test_caching_nil_primary_key + klass = Class.new(Minimalistic) + klass.expects(:reset_primary_key).returns(nil).once + 2.times { klass.primary_key } + end + def test_attribute_keys_on_new_instance t = Topic.new assert_equal nil, t.title, "The topics table has a title column, so it should be nil" @@ -78,7 +102,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_respond_to? topic = Topic.find(1) assert_respond_to topic, "title" - assert_respond_to topic, "_title" assert_respond_to topic, "title?" assert_respond_to topic, "title=" assert_respond_to topic, :title @@ -95,9 +118,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_not_nil keyboard.key_number assert_equal keyboard.key_number, keyboard.id assert keyboard.respond_to?('key_number') - assert keyboard.respond_to?('_key_number') assert keyboard.respond_to?('id') - assert keyboard.respond_to?('_id') + end + + def test_id_before_type_cast_with_custom_primary_key + keyboard = Keyboard.create + keyboard.key_number = '10' + assert_equal '10', keyboard.id_before_type_cast + assert_equal nil, keyboard.read_attribute_before_type_cast('id') + assert_equal '10', keyboard.read_attribute_before_type_cast('key_number') end # Syck calls respond_to? before actually calling initialize @@ -237,8 +266,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic.send(:write_attribute, :title, "Still another topic") assert_equal "Still another topic", topic.title - topic.send(:write_attribute, "title", "Still another topic: part 2") + topic[:title] = "Still another topic: part 2" assert_equal "Still another topic: part 2", topic.title + + topic.send(:write_attribute, "title", "Still another topic: part 3") + assert_equal "Still another topic: part 3", topic.title + + topic["title"] = "Still another topic: part 4" + assert_equal "Still another topic: part 4", topic.title end def test_read_attribute @@ -291,6 +326,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase # puts "" end + def test_overridden_write_attribute + topic = Topic.new + def topic.write_attribute(attr_name, value) + super(attr_name, value.downcase) + end + + topic.send(:write_attribute, :title, "Yet another topic") + assert_equal "yet another topic", topic.title + + topic[:title] = "Yet another topic: part 2" + assert_equal "yet another topic: part 2", topic.title + + topic.send(:write_attribute, "title", "Yet another topic: part 3") + assert_equal "yet another topic: part 3", topic.title + + topic["title"] = "Yet another topic: part 4" + assert_equal "yet another topic: part 4", topic.title + end + + def test_overridden_read_attribute + topic = Topic.new + topic.title = "Stop changing the topic" + def topic.read_attribute(attr_name) + super(attr_name).upcase + end + + assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title") + assert_equal "STOP CHANGING THE TOPIC", topic["title"] + + assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title) + assert_equal "STOP CHANGING THE TOPIC", topic[:title] + end + def test_read_overridden_attribute topic = Topic.new(:title => 'a') def topic.title() 'b' end @@ -489,6 +557,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_write_time_to_date_attributes + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new + record.last_read = Time.utc(2010, 1, 1, 10) + assert_equal Date.civil(2010, 1, 1), record.last_read + end + end + def test_time_attributes_are_retrieved_in_current_time_zone in_time_zone "Pacific Time (US & Canada)" do utc_time = Time.utc(2008, 1, 1) @@ -524,6 +600,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_aware_read_attribute + utc_time = Time.utc(2008, 1, 1) + cst_time = utc_time.in_time_zone("Central Time (US & Canada)") + in_time_zone "Pacific Time (US & Canada)" do + record = @target.create(:written_on => cst_time).reload + assert_equal utc_time, record[:written_on] + assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone + assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time + end + end + def test_setting_time_zone_aware_attribute_with_string utc_time = Time.utc(2008, 1, 1) (-11..13).each do |timezone_offset| @@ -543,6 +630,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record = @target.new record.written_on = ' ' assert_nil record.written_on + assert_nil record[:written_on] end end @@ -657,7 +745,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = Topic.new(:id => 5) topic.id = 5 - topic.method(:id).owner.send(:remove_method, :id) + topic.method(:id).owner.send(:undef_method, :id) assert_deprecated do assert_equal 5, topic.id @@ -666,17 +754,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase Topic.undefine_attribute_methods end + def test_read_attribute_with_nil_should_not_asplode + assert_equal nil, Topic.new.read_attribute(nil) + end + + # If B < A, and A defines an accessor for 'foo', we don't want to override + # that by defining a 'foo' method in the generated methods module for B. + # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].) + def test_inherited_custom_accessors + klass = Class.new(ActiveRecord::Base) do + self.table_name = "topics" + self.abstract_class = true + def title; "omg"; end + def title=(val); self.author_name = val; end + end + subklass = Class.new(klass) + [klass, subklass].each(&:define_attribute_methods) + + topic = subklass.find(1) + assert_equal "omg", topic.title + + topic.title = "lol" + assert_equal "lol", topic.author_name + end + private def cached_columns - @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) + Topic.columns.find_all { |column| + !Topic.serialized_attributes.include? column.name + }.map(&:name) end def time_related_columns_on_topic - Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) } - end - - def serialized_columns_on_topic - Topic.columns.select { |c| Topic.serialized_attributes.include?(c.name) } + Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) } end def in_time_zone(zone) diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 4ad2cdfc7e..1376810adf 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -347,6 +347,17 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test client.save! assert_no_queries { assert_equal apple, client.firm } end + + def test_validation_does_not_validate_stale_association_target + valid_developer = Developer.create!(:name => "Dude", :salary => 50_000) + invalid_developer = Developer.new() + + auditlog = AuditLog.new(:message => "foo") + auditlog.developer = invalid_developer + auditlog.developer_id = valid_developer.id + + assert auditlog.valid? + end end class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase @@ -575,6 +586,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end @@ -897,6 +909,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end @@ -1020,6 +1033,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @ship = Ship.create(:name => 'Nights Dirty Lightning') @pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1268,6 +1282,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @association_name = :birds @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @@ -1282,6 +1297,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @association_name = :parrots @habtm = true @@ -1297,6 +1313,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.birds.create(:name => 'cookoo') end @@ -1313,6 +1330,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") @pirate.create_ship(:name => 'titanic') super @@ -1335,6 +1353,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord:: self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1355,6 +1374,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?") end @@ -1377,6 +1397,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas self.use_transactional_fixtures = false unless supports_savepoints? def setup + super @pirate = Pirate.new end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 12c1cfb30e..698c3d0cb1 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -23,10 +23,18 @@ require 'models/edge' require 'models/joke' require 'models/bulb' require 'models/bird' +require 'models/teapot' require 'rexml/document' require 'active_support/core_ext/exception' require 'bcrypt' +class FirstAbstractClass < ActiveRecord::Base + self.abstract_class = true +end +class SecondAbstractClass < FirstAbstractClass + self.abstract_class = true +end +class Photo < SecondAbstractClass; end class Category < ActiveRecord::Base; end class Categorization < ActiveRecord::Base; end class Smarts < ActiveRecord::Base; end @@ -69,6 +77,16 @@ end class BasicsTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + def test_generated_methods_modules + modules = Computer.ancestors + assert modules.include?(Computer::GeneratedFeatureMethods) + assert_equal(Computer::GeneratedFeatureMethods, Computer.generated_feature_methods) + assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods), + "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods") + assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods + assert(modules.index(Computer.generated_attribute_methods) < modules.index(ActiveRecord::Base.ancestors[1])) + end + def test_column_names_are_escaped conn = ActiveRecord::Base.connection classname = conn.class.name[/[^:]*$/] @@ -169,6 +187,31 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_previously_changed + topic = Topic.find :first + topic.title = '<3<3<3' + assert_equal({}, topic.previous_changes) + + topic.save! + expected = ["The First Topic", "<3<3<3"] + assert_equal(expected, topic.previous_changes['title']) + end + + def test_previously_changed_dup + topic = Topic.find :first + topic.title = '<3<3<3' + topic.save! + + t2 = topic.dup + + assert_equal(topic.previous_changes, t2.previous_changes) + + topic.title = "lolwut" + topic.save! + + assert_not_equal(topic.previous_changes, t2.previous_changes) + end + def test_preserving_time_objects assert_kind_of( Time, Topic.find(1).bonus_time, @@ -561,10 +604,12 @@ class BasicsTest < ActiveRecord::TestCase weird = Weird.create('a$b' => 'value') weird.reload assert_equal 'value', weird.send('a$b') + assert_equal 'value', weird.read_attribute('a$b') weird.update_column('a$b', 'value2') weird.reload assert_equal 'value2', weird.send('a$b') + assert_equal 'value2', weird.read_attribute('a$b') end def test_multiparameter_attributes_on_date @@ -644,7 +689,7 @@ class BasicsTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on + assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on end def test_multiparameter_attributes_on_time_with_no_date @@ -893,7 +938,7 @@ class BasicsTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time + assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time end def test_boolean @@ -947,10 +992,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "b", duped_topic.title # test if the attribute values have been duped - topic.title = {"a" => "b"} duped_topic = topic.dup - duped_topic.title["a"] = "c" - assert_equal "b", topic.title["a"] + duped_topic.title.replace "c" + assert_equal "a", topic.title # test if attributes set as part of after_initialize are duped correctly assert_equal topic.author_email_address, duped_topic.author_email_address @@ -961,8 +1005,7 @@ class BasicsTest < ActiveRecord::TestCase assert_not_equal duped_topic.id, topic.id duped_topic.reload - # FIXME: I think this is poor behavior, and will fix it with #5686 - assert_equal({'a' => 'c'}.to_yaml, duped_topic.title) + assert_equal("c", duped_topic.title) end def test_dup_with_aggregate_of_same_name_as_attribute @@ -1061,7 +1104,10 @@ class BasicsTest < ActiveRecord::TestCase # TODO: extend defaults tests to other databases! if current_adapter?(:PostgreSQLAdapter) def test_default + tz = Default.default_timezone + Default.default_timezone = :local default = Default.new + Default.default_timezone = tz # fixed dates / times assert_equal Date.new(2004, 1, 1), default.fixed_date @@ -1104,7 +1150,8 @@ class BasicsTest < ActiveRecord::TestCase # use a geometric function to test for an open path objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id] - assert_equal objs[0].isopen, 't' + + assert_equal true, objs[0].isopen # test alternate formats when defining the geometric types @@ -1132,7 +1179,8 @@ class BasicsTest < ActiveRecord::TestCase # use a geometric function to test for an closed path objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id] - assert_equal objs[0].isclosed, 't' + + assert_equal true, objs[0].isclosed end end @@ -1185,19 +1233,6 @@ class BasicsTest < ActiveRecord::TestCase assert(auto.id > 0) end - def quote_column_name(name) - "<#{name}>" - end - - def test_quote_keys - ar = AutoId.new - source = {"foo" => "bar", "baz" => "quux"} - actual = ar.send(:quote_columns, self, source) - inverted = actual.invert - assert_equal("<foo>", inverted["bar"]) - assert_equal("<baz>", inverted["quux"]) - end - def test_sql_injection_via_find assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do Topic.find("123456 OR id > 0") @@ -1235,6 +1270,42 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end + def test_serialized_attribute_in_base_class + Topic.serialize("content", Hash) + + hash = { 'content1' => 'value1', 'content2' => 'value2' } + important_topic = ImportantTopic.create("content" => hash) + assert_equal(hash, important_topic.content) + + important_topic.reload + assert_equal(hash, important_topic.content) + end + + # This test was added to fix GH #4004. Obviously the value returned + # is not really the value 'before type cast' so we should maybe think + # about changing that in the future. + def test_serialized_attribute_before_type_cast_returns_unserialized_value + klass = Class.new(ActiveRecord::Base) + klass.table_name = "topics" + klass.serialize :content, Hash + + t = klass.new(:content => { :foo => :bar }) + assert_equal({ :foo => :bar }, t.content_before_type_cast) + t.save! + t.reload + assert_equal({ :foo => :bar }, t.content_before_type_cast) + end + + def test_serialized_attribute_declared_in_subclass + hash = { 'important1' => 'value1', 'important2' => 'value2' } + important_topic = ImportantTopic.create("important" => hash) + assert_equal(hash, important_topic.important) + + important_topic.reload + assert_equal(hash, important_topic.important) + assert_equal(hash, important_topic.read_attribute(:important)) + end + def test_serialized_time_attribute myobj = Time.local(2008,1,1,1,0) topic = Topic.create("content" => myobj).reload @@ -1247,11 +1318,24 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end - def test_nil_serialized_attribute_with_class_constraint + def test_nil_serialized_attribute_without_class_constraint topic = Topic.new assert_nil topic.content end + def test_nil_not_serialized_without_class_constraint + assert Topic.new(:content => nil).save + assert_equal 1, Topic.where(:content => nil).count + end + + def test_nil_not_serialized_with_class_constraint + Topic.serialize :content, Hash + assert Topic.new(:content => nil).save + assert_equal 1, Topic.where(:content => nil).count + ensure + Topic.serialize(:content) + end + def test_should_raise_exception_on_serialized_attribute_with_type_mismatch myobj = MyObject.new('value1', 'value2') topic = Topic.new(:content => myobj) @@ -1359,22 +1443,6 @@ class BasicsTest < ActiveRecord::TestCase assert_equal author_name, Topic.find(topic.id).author_name end - if RUBY_VERSION < '1.9' - def test_quote_chars - with_kcode('UTF8') do - str = 'The Narrator' - topic = Topic.create(:author_name => str) - assert_equal str, topic.author_name - - assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars - topic = Topic.find_by_author_name(str.mb_chars) - - assert_kind_of Topic, topic - assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" - end - end - end - def test_toggle_attribute assert !topics(:first).approved? topics(:first).toggle!(:approved) @@ -1401,86 +1469,48 @@ class BasicsTest < ActiveRecord::TestCase assert_equal dev, dev.reload end - def test_define_attr_method_with_value - k = Class.new( ActiveRecord::Base ) - k.send(:define_attr_method, :table_name, "foo") - assert_equal "foo", k.table_name - end - - def test_define_attr_method_with_block - k = Class.new( ActiveRecord::Base ) do - class << self - attr_accessor :foo_key - end - end - k.foo_key = "id" - k.send(:define_attr_method, :foo_key) { "sys_" + original_foo_key } - assert_equal "sys_id", k.foo_key - end - - def test_set_table_name_with_value - k = Class.new( ActiveRecord::Base ) - k.table_name = "foo" - assert_equal "foo", k.table_name - k.set_table_name "bar" - assert_equal "bar", k.table_name - end - def test_switching_between_table_name assert_difference("GoodJoke.count") do - Joke.set_table_name "cold_jokes" + Joke.table_name = "cold_jokes" Joke.create - Joke.set_table_name "funny_jokes" + Joke.table_name = "funny_jokes" Joke.create end end + def test_set_table_name_symbol_converted_to_string + Joke.table_name = :cold_jokes + assert_equal 'cold_jokes', Joke.table_name + end + def test_quoted_table_name_after_set_table_name klass = Class.new(ActiveRecord::Base) - klass.set_table_name "foo" + klass.table_name = "foo" assert_equal "foo", klass.table_name assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name - klass.set_table_name "bar" + klass.table_name = "bar" assert_equal "bar", klass.table_name assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name end - def test_set_table_name_with_block + def test_set_table_name_with_inheritance k = Class.new( ActiveRecord::Base ) - k.set_table_name { "ks" } - assert_equal "ks", k.table_name + def k.name; "Foo"; end + def k.table_name; super + "ks"; end + assert_equal "foosks", k.table_name end - def test_set_primary_key_with_value - k = Class.new( ActiveRecord::Base ) - k.primary_key = "foo" - assert_equal "foo", k.primary_key - k.set_primary_key "bar" - assert_equal "bar", k.primary_key - end - - def test_set_primary_key_with_block - k = Class.new( ActiveRecord::Base ) - k.primary_key = 'id' - k.set_primary_key { "sys_" + original_primary_key } - assert_equal "sys_id", k.primary_key - end - - def test_set_inheritance_column_with_value - k = Class.new( ActiveRecord::Base ) - k.inheritance_column = "foo" - assert_equal "foo", k.inheritance_column - k.set_inheritance_column "bar" - assert_equal "bar", k.inheritance_column - end - - def test_set_inheritance_column_with_block - k = Class.new( ActiveRecord::Base ) - k.set_inheritance_column { original_inheritance_column + "_id" } - assert_equal "type_id", k.inheritance_column + def test_sequence_name_with_abstract_class + ak = Class.new(ActiveRecord::Base) + ak.abstract_class = true + k = Class.new(ak) + k.table_name = "projects" + orig_name = k.sequence_name + return skip "sequences not supported by db" unless orig_name + assert_equal k.reset_sequence_name, orig_name end def test_count_with_join @@ -1582,7 +1612,7 @@ class BasicsTest < ActiveRecord::TestCase Developer.find(:all) end assert developers.size >= 2 - for i in 1...developers.size + (1...developers.size).each do |i| assert developers[i-1].salary >= developers[i].salary end end @@ -1673,10 +1703,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_descends_from_active_record - # Tries to call Object.abstract_class? - assert_raise(NoMethodError) do - ActiveRecord::Base.descends_from_active_record? - end + assert !ActiveRecord::Base.descends_from_active_record? # Abstract subclass of AR::Base. assert LoosePerson.descends_from_active_record? @@ -1699,6 +1726,10 @@ class BasicsTest < ActiveRecord::TestCase # Concrete subclasses an abstract class which has a type column. assert !SubStiPost.descends_from_active_record? + + assert Teapot.descends_from_active_record? + assert !OtherTeapot.descends_from_active_record? + assert CoolTeapot.descends_from_active_record? end def test_find_on_abstract_base_class_doesnt_use_type_condition @@ -1732,7 +1763,7 @@ class BasicsTest < ActiveRecord::TestCase def test_inspect_instance topic = topics(:first) - assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect end def test_inspect_new_instance @@ -1761,10 +1792,18 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "The First Topic", topics(:first).becomes(Reply).title end + def test_becomes_includes_errors + company = Company.new(:name => nil) + assert !company.valid? + original_errors = company.errors + client = company.becomes(Client) + assert_equal original_errors, client.errors + end + def test_silence_sets_log_level_to_error_in_block original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::DEBUG ActiveRecord::Base.silence do ActiveRecord::Base.logger.warn "warn" @@ -1778,7 +1817,7 @@ class BasicsTest < ActiveRecord::TestCase def test_silence_sets_log_level_back_to_level_before_yield original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.silence do end @@ -1790,7 +1829,7 @@ class BasicsTest < ActiveRecord::TestCase def test_benchmark_with_log_level original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } @@ -1805,10 +1844,8 @@ class BasicsTest < ActiveRecord::TestCase def test_benchmark_with_use_silence original_logger = ActiveRecord::Base.logger log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" } + ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } - assert_no_match(/Loud/, log.string) assert_match(/Quiet/, log.string) ensure ActiveRecord::Base.logger = original_logger @@ -1840,9 +1877,9 @@ class BasicsTest < ActiveRecord::TestCase def test_clear_cache! # preheat cache - c1 = Post.columns + c1 = Post.connection.schema_cache.columns['posts'] ActiveRecord::Base.clear_cache! - c2 = Post.columns + c2 = Post.connection.schema_cache.columns['posts'] assert_not_equal c1, c2 end @@ -1859,11 +1896,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_round_trip - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - expected = posts(:welcome) marshalled = Marshal.dump(expected) actual = Marshal.load(marshalled) @@ -1872,11 +1904,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshal_new_record_round_trip - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - marshalled = Marshal.dump(Post.new) post = Marshal.load(marshalled) @@ -1884,11 +1911,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_marshalling_with_associations - if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7" - return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \ - "to be a Ruby bug.") - end - post = Post.new post.comments.build @@ -1907,7 +1929,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [], NonExistentTable.attribute_names end - def test_attribtue_names_on_abstract_class + def test_attribute_names_on_abstract_class assert_equal [], AbstractCompany.attribute_names end @@ -1935,4 +1957,47 @@ class BasicsTest < ActiveRecord::TestCase dev.update_attribute(:updated_at, nil) assert_match(/\/#{dev.id}$/, dev.cache_key) end + + def test_uniq_delegates_to_scoped + scope = stub + Bird.stubs(:scoped).returns(mock(:uniq => scope)) + assert_equal scope, Bird.uniq + end + + def test_active_record_super + assert_equal ActiveRecord::Model, ActiveRecord::Base.active_record_super + assert_equal ActiveRecord::Base, Topic.active_record_super + assert_equal Topic, ImportantTopic.active_record_super + assert_equal ActiveRecord::Model, Teapot.active_record_super + assert_equal Teapot, OtherTeapot.active_record_super + assert_equal ActiveRecord::Model, CoolTeapot.active_record_super + end + + def test_table_name_with_2_abstract_subclasses + assert_equal "photos", Photo.table_name + end + + def test_column_types_typecast + topic = Topic.first + refute_equal 't.lo', topic.author_name + + attrs = topic.attributes.dup + attrs.delete 'id' + + typecast = Class.new { + def type_cast value + "t.lo" + end + } + + types = { 'author_name' => typecast.new } + topic = Topic.allocate.init_with 'attributes' => attrs, + 'column_types' => types + + assert_equal 't.lo', topic.author_name + end + + def test_typecasting_aliases + assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove + end end diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb index 06c14cb108..25d2896ab0 100644 --- a/activerecord/test/cases/binary_test.rb +++ b/activerecord/test/cases/binary_test.rb @@ -8,11 +8,11 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) require 'models/binary' class BinaryTest < ActiveRecord::TestCase - FIXTURES = %w(flowers.jpg example.log) + FIXTURES = %w(flowers.jpg example.log test.txt) def test_mixed_encoding str = "\x80" - str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding) + str.force_encoding('ASCII-8BIT') binary = Binary.new :name => 'いただきます!', :data => str binary.save! @@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) # Mysql adapter doesn't properly encode things, so we have to do it if current_adapter?(:MysqlAdapter) - name.force_encoding('UTF-8') if name.respond_to?(:force_encoding) + name.force_encoding('UTF-8') end assert_equal 'いただきます!', name end @@ -33,7 +33,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter) FIXTURES.each do |filename| data = File.read(ASSETS_ROOT + "/#{filename}") - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze bin = Binary.new(:data => data) diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c38814713a..0391319a00 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/company' +require "models/contract" require 'models/topic' require 'models/edge' require 'models/club' @@ -48,11 +49,11 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_get_maximum_of_field_with_include - assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") + assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'") end def test_should_get_maximum_of_field_with_scoped_include - Account.send :with_scope, :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do + Account.send :with_scope, :find => { :include => :firm, :references => :companies, :conditions => "companies.name != 'Summit'" } do assert_equal 55, Account.maximum(:credit_limit) end end @@ -269,7 +270,7 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_not_modify_options_when_using_includes - options = {:conditions => 'companies.id > 1', :include => :firm} + options = {:conditions => 'companies.id > 1', :include => :firm, :references => :companies} options_copy = options.dup Account.count(:all, options) @@ -446,4 +447,45 @@ class CalculationsTest < ActiveRecord::TestCase distinct_authors_for_approved_count = Topic.group(:approved).count(:author_name, :distinct => true)[true] assert_equal distinct_authors_for_approved_count, 2 end + + def test_pluck + assert_equal [1,2,3,4], Topic.order(:id).pluck(:id) + end + + def test_pluck_type_cast + topic = topics(:first) + relation = Topic.where(:id => topic.id) + assert_equal [ topic.approved ], relation.pluck(:approved) + assert_equal [ topic.last_read ], relation.pluck(:last_read) + assert_equal [ topic.written_on ], relation.pluck(:written_on) + end + + def test_pluck_and_uniq + assert_equal [50, 53, 55, 60], Account.order(:credit_limit).uniq.pluck(:credit_limit) + end + + def test_pluck_in_relation + company = Company.first + contract = company.contracts.create! + assert_equal [contract.id], company.contracts.pluck(:id) + end + + def test_pluck_with_serialization + t = Topic.create!(:content => { :foo => :bar }) + assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) + end + + def test_pluck_with_qualified_column_name + assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id") + end + + def test_pluck_auto_table_name_prefix + c = Company.create!(:name => "test", :contracts => [Contract.new]) + assert_equal [c.id], Company.joins(:contracts).pluck(:id) + end + + def test_pluck_not_auto_table_name_prefix_if_column_joined + Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) + assert_equal [7], Company.joins(:contracts).pluck(:developer_id) + end end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 7f4d25790b..7690769226 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -1,7 +1,7 @@ require "cases/helper" class CallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' class << self def callback_string(callback_method) @@ -48,7 +48,7 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper end class ParentDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' attr_accessor :after_save_called before_validation {|record| record.after_save_called = true} end @@ -58,7 +58,7 @@ class ChildDeveloper < ParentDeveloper end class RecursiveCallbackDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' before_save :on_before_save after_save :on_after_save @@ -79,7 +79,7 @@ class RecursiveCallbackDeveloper < ActiveRecord::Base end class ImmutableDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 @@ -98,7 +98,7 @@ class ImmutableDeveloper < ActiveRecord::Base end class ImmutableMethodDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' validates_inclusion_of :salary, :in => 50000..200000 @@ -118,7 +118,7 @@ class ImmutableMethodDeveloper < ActiveRecord::Base end class OnCallbacksDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' before_validation { history << :before_validation } before_validation(:on => :create){ history << :before_validation_on_create } @@ -138,7 +138,7 @@ class OnCallbacksDeveloper < ActiveRecord::Base end class CallbackCancellationDeveloper < ActiveRecord::Base - set_table_name 'developers' + self.table_name = 'developers' attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 14884e42af..a44b49466f 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -126,17 +126,20 @@ module ActiveRecord if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer - bigint_column = PostgreSQLColumn.new('number', nil, "bigint") + oid = PostgreSQLAdapter::OID::Identity.new + bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint") assert_equal :integer, bigint_column.type end def test_smallint_column_should_map_to_integer - smallint_column = PostgreSQLColumn.new('number', nil, "smallint") + oid = PostgreSQLAdapter::OID::Identity.new + smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint") assert_equal :integer, smallint_column.type end def test_uuid_column_should_map_to_string - uuid_column = PostgreSQLColumn.new('unique_id', nil, "uuid") + oid = PostgreSQLAdapter::OID::Identity.new + uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid") assert_equal :string, uuid_column.type end end diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb new file mode 100644 index 0000000000..ccc57cb876 --- /dev/null +++ b/activerecord/test/cases/column_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ColumnTest < ActiveRecord::TestCase + def test_type_cast_boolean + column = Column.new("field", nil, "boolean") + assert column.type_cast(true) + assert column.type_cast(1) + assert column.type_cast('1') + assert column.type_cast('t') + assert column.type_cast('T') + assert column.type_cast('true') + assert column.type_cast('TRUE') + assert column.type_cast('on') + assert column.type_cast('ON') + assert !column.type_cast(false) + assert !column.type_cast(0) + assert !column.type_cast('0') + assert !column.type_cast('f') + assert !column.type_cast('F') + assert !column.type_cast('false') + assert !column.type_cast('FALSE') + assert !column.type_cast('off') + assert !column.type_cast('OFF') + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb new file mode 100644 index 0000000000..7dc6e8afcb --- /dev/null +++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb @@ -0,0 +1,54 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class AbstractAdapterTest < ActiveRecord::TestCase + attr_reader :adapter + + def setup + @adapter = AbstractAdapter.new nil, nil + end + + def test_in_use? + # FIXME: change to refute in Rails 4.0 / mt + assert !adapter.in_use?, 'adapter is not in use' + assert adapter.lease, 'lease adapter' + assert adapter.in_use?, 'adapter is in use' + end + + def test_lease_twice + assert adapter.lease, 'should lease adapter' + assert !adapter.lease, 'should not lease adapter' + end + + def test_last_use + assert !adapter.last_use + adapter.lease + assert adapter.last_use + end + + def test_expire_mutates_in_use + assert adapter.lease, 'lease adapter' + assert adapter.in_use?, 'adapter is in use' + adapter.expire + assert !adapter.in_use?, 'adapter is in use' + end + + def test_close + pool = ConnectionPool.new(ConnectionSpecification.new({}, nil)) + pool.connections << adapter + adapter.pool = pool + + # Make sure the pool marks the connection in use + assert_equal adapter, pool.connection + assert adapter.in_use? + + # Close should put the adapter back in the pool + adapter.close + assert !adapter.in_use? + + assert_equal adapter, pool.connection + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index bd0d161838..dc99ac665c 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -8,6 +8,9 @@ module ActiveRecord @handler.establish_connection 'america', Base.connection_pool.spec @klass = Class.new do def self.name; 'america'; end + class << self + alias active_record_super superclass + end end @subklass = Class.new(@klass) do def self.name; 'north america'; end @@ -40,8 +43,6 @@ module ActiveRecord def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove @handler.establish_connection 'north america', Base.connection_pool.spec - assert_not_same @handler.retrieve_connection_pool(@klass), - @handler.retrieve_connection_pool(@subklass) @handler.remove_connection @subklass assert_same @handler.retrieve_connection_pool(@klass), diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb new file mode 100644 index 0000000000..ea2196cda2 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb @@ -0,0 +1,12 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionSpecificationTest < ActiveRecord::TestCase + def test_dup_deep_copy_config + spec = ConnectionSpecification.new({ :a => :b }, "bar") + assert_not_equal(spec.config.object_id, spec.dup.config.object_id) + end + 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 new file mode 100644 index 0000000000..42e39d534c --- /dev/null +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -0,0 +1,44 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class SchemaCacheTest < ActiveRecord::TestCase + def setup + connection = ActiveRecord::Base.connection + @cache = SchemaCache.new connection + end + + def test_primary_key + assert_equal 'id', @cache.primary_keys['posts'] + end + + def test_primary_key_for_non_existent_table + assert_nil @cache.primary_keys['omgponies'] + end + + def test_caches_columns + columns = @cache.columns['posts'] + assert_equal columns, @cache.columns['posts'] + end + + def test_caches_columns_hash + columns_hash = @cache.columns_hash['posts'] + assert_equal columns_hash, @cache.columns_hash['posts'] + end + + def test_clearing + @cache.columns['posts'] + @cache.columns_hash['posts'] + @cache.tables['posts'] + @cache.primary_keys['posts'] + + @cache.clear! + + assert_equal 0, @cache.columns.size + assert_equal 0, @cache.columns_hash.size + assert_equal 0, @cache.tables.size + assert_equal 0, @cache.primary_keys.size + end + end + end +end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index f554ceef35..fe1b40d884 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "rack" module ActiveRecord module ConnectionAdapters @@ -25,38 +26,25 @@ module ActiveRecord assert ActiveRecord::Base.connection_handler.active_connections? end - class FakeBase < ActiveRecord::Base - def self.establish_connection spec - String === spec ? super : spec - end - end + def test_connection_pool_per_pid + return skip('must support fork') unless Process.respond_to?(:fork) - def test_url_host_no_db - spec = FakeBase.establish_connection 'postgres://foo?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "", - :host => "foo", - :encoding => "utf8" }, spec) - end + object_id = ActiveRecord::Base.connection.object_id - def test_url_host_db - spec = FakeBase.establish_connection 'postgres://foo/bar?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "bar", - :host => "foo", - :encoding => "utf8" }, spec) - end + rd, wr = IO.pipe + + pid = fork { + rd.close + wr.write Marshal.dump ActiveRecord::Base.connection.object_id + wr.close + exit! + } + + wr.close - def test_url_port - spec = FakeBase.establish_connection 'postgres://foo:123?encoding=utf8' - assert_equal({ - :adapter => "postgresql", - :database => "", - :port => 123, - :host => "foo", - :encoding => "utf8" }, spec) + Process.waitpid pid + assert_not_equal object_id, Marshal.load(rd.read) + rd.close end def test_app_delegation @@ -114,9 +102,10 @@ module ActiveRecord test "proxy is polite to it's body and responds to it" do body = Class.new(String) { def to_path; "/path"; end }.new - proxy = ConnectionManagement::Proxy.new(body) - assert proxy.respond_to?(:to_path) - assert_equal proxy.to_path, "/path" + app = lambda { |_| [200, {}, body] } + response_body = ConnectionManagement.new(app).call(@env)[2] + assert response_body.respond_to?(:to_path) + assert_equal response_body.to_path, "/path" end end end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 8a0f453127..2c69bfde5b 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -4,6 +4,8 @@ module ActiveRecord module ConnectionAdapters class ConnectionPoolTest < ActiveRecord::TestCase def setup + super + # Keep a duplicate pool so we do not bother others @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec @@ -18,73 +20,76 @@ module ActiveRecord end end - def test_active_connection? - assert !@pool.active_connection? - assert @pool.connection - assert @pool.active_connection? - @pool.release_connection - assert !@pool.active_connection? + def teardown + super + @pool.disconnect! end - def test_pool_caches_columns - columns = @pool.columns['posts'] - assert_equal columns, @pool.columns['posts'] + def test_full_pool_exception + assert_raises(PoolFullError) do + (@pool.size + 1).times do + @pool.checkout + end + end end - def test_pool_caches_columns_hash - columns_hash = @pool.columns_hash['posts'] - assert_equal columns_hash, @pool.columns_hash['posts'] - end + def test_reap_and_active + @pool.checkout + @pool.checkout + @pool.checkout + @pool.timeout = 0 - def test_clearing_column_cache - @pool.columns['posts'] - @pool.columns_hash['posts'] + connections = @pool.connections.dup - @pool.clear_cache! + @pool.reap - assert_equal 0, @pool.columns.size - assert_equal 0, @pool.columns_hash.size + assert_equal connections.length, @pool.connections.length end - def test_primary_key - assert_equal 'id', @pool.primary_keys['posts'] - end + def test_reap_inactive + @pool.checkout + @pool.checkout + @pool.checkout + @pool.timeout = 0 - def test_primary_key_for_non_existent_table - assert_equal 'id', @pool.primary_keys['omgponies'] - end + connections = @pool.connections.dup + connections.each do |conn| + conn.extend(Module.new { def active?; false; end; }) + end - def test_primary_key_is_set_on_columns - posts_columns = @pool.columns_hash['posts'] - assert posts_columns['id'].primary + @pool.reap - (posts_columns.keys - ['id']).each do |key| - assert !posts_columns[key].primary - end + assert_equal 0, @pool.connections.length + ensure + connections.each(&:close) end - def test_clear_stale_cached_connections! - pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec - - threads = [ - Thread.new { pool.connection }, - Thread.new { pool.connection }] + def test_remove_connection + conn = @pool.checkout + assert conn.in_use? - threads.map { |t| t.join } + length = @pool.connections.length + @pool.remove conn + assert conn.in_use? + assert_equal(length - 1, @pool.connections.length) + ensure + conn.close + end - pool.extend Module.new { - attr_accessor :checkins - def checkin conn - @checkins << conn - conn.object_id - end - } - pool.checkins = [] + def test_remove_connection_for_thread + conn = @pool.connection + @pool.remove conn + assert_not_equal(conn, @pool.connection) + ensure + conn.close if conn + end - cleared_threads = pool.clear_stale_cached_connections! - assert((cleared_threads - threads.map { |x| x.object_id }).empty?, - "threads should have been removed") - assert_equal pool.checkins.length, threads.length + def test_active_connection? + assert !@pool.active_connection? + assert @pool.connection + assert @pool.active_connection? + @pool.release_connection + assert !@pool.active_connection? end def test_checkout_behaviour @@ -96,24 +101,16 @@ module ActiveRecord threads << Thread.new(i) do |pool_count| connection = pool.connection assert_not_nil connection + connection.close end end - threads.each {|t| t.join} + threads.each(&:join) Thread.new do - threads.each do |t| - thread_ids = pool.instance_variable_get(:@reserved_connections).keys - assert thread_ids.include?(t.object_id) - end - - pool.connection - threads.each do |t| - thread_ids = pool.instance_variable_get(:@reserved_connections).keys - assert !thread_ids.include?(t.object_id) - end - end.join() - + assert pool.connection + pool.connection.close + end.join end def test_automatic_reconnect= diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb new file mode 100644 index 0000000000..e6cb1b9521 --- /dev/null +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -0,0 +1,41 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ConnectionSpecification + class ResolverTest < ActiveRecord::TestCase + def resolve(spec) + Resolver.new(spec, {}).spec.config + end + + def test_url_host_no_db + spec = resolve 'mysql://foo?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_host_db + spec = resolve 'mysql://foo/bar?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "bar", + :host => "foo", + :encoding => "utf8" }, spec) + end + + def test_url_port + spec = resolve 'mysql://foo:123?encoding=utf8' + assert_equal({ + :adapter => "mysql", + :database => "", + :port => 123, + :host => "foo", + :encoding => "utf8" }, spec) + end + end + end + end +end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 3ed96a3ec8..cd3d19e783 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -6,9 +6,11 @@ require 'models/engine' require 'models/reply' require 'models/category' require 'models/categorization' +require 'models/dog' +require 'models/dog_lover' class CounterCacheTest < ActiveRecord::TestCase - fixtures :topics, :categories, :categorizations, :cars + fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' @@ -61,7 +63,7 @@ class CounterCacheTest < ActiveRecord::TestCase end end - test "reset counter should with belongs_to which has class_name" do + test "reset counter with belongs_to which has class_name" do car = cars(:honda) assert_nothing_raised do Car.reset_counters(car.id, :engines) @@ -71,6 +73,20 @@ class CounterCacheTest < ActiveRecord::TestCase end end + test "reset the right counter if two have the same class_name" do + david = dog_lovers(:david) + + DogLover.increment_counter(:bred_dogs_count, david.id) + DogLover.increment_counter(:trained_dogs_count, david.id) + + assert_difference 'david.reload.bred_dogs_count', -1 do + DogLover.reset_counters(david.id, :bred_dogs) + end + assert_difference 'david.reload.trained_dogs_count', -1 do + DogLover.reset_counters(david.id, :trained_dogs) + end + end + test "update counter with initial null value" do category = categories(:general) assert_equal 2, category.categorizations.count diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index b1ce846218..54e0b40b4f 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -497,6 +497,20 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.previous_changes.key?('created_on') end + if ActiveRecord::Base.connection.supports_migrations? + class Testings < ActiveRecord::Base; end + def test_field_named_field + ActiveRecord::Base.connection.create_table :testings do |t| + t.string :field + end + assert_nothing_raised do + Testings.new.attributes + end + ensure + ActiveRecord::Base.connection.drop_table :testings rescue nil + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index 0236f9b0a1..9705a11387 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -99,5 +99,13 @@ module ActiveRecord assert_not_nil new_topic.created_at end + def test_dup_after_initialize_callbacks + topic = Topic.new + assert Topic.after_initialize_called + Topic.after_initialize_called = false + topic.dup + assert Topic.after_initialize_called + end + end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb new file mode 100644 index 0000000000..e118add44c --- /dev/null +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -0,0 +1,48 @@ +require 'cases/helper' + +if ActiveRecord::Base.connection.supports_explain? + class ExplainSubscriberTest < ActiveRecord::TestCase + SUBSCRIBER = ActiveRecord::ExplainSubscriber.new + + def test_collects_nothing_if_available_queries_for_explain_is_nil + with_queries(nil) do + SUBSCRIBER.call + assert_nil Thread.current[:available_queries_for_explain] + end + end + + def test_collects_nothing_if_the_payload_has_an_exception + with_queries([]) do |queries| + SUBSCRIBER.call(:exception => Exception.new) + assert queries.empty? + end + end + + def test_collects_nothing_for_ignored_payloads + with_queries([]) do |queries| + ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| + SUBSCRIBER.call(:name => ip) + end + assert queries.empty? + end + end + + def test_collects_pairs_of_queries_and_binds + sql = 'select 1 from users' + binds = [1, 2] + with_queries([]) do |queries| + SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds) + assert_equal 1, queries.size + assert_equal sql, queries[0][0] + assert_equal binds, queries[0][1] + end + end + + def with_queries(queries) + Thread.current[:available_queries_for_explain] = queries + yield queries + ensure + Thread.current[:available_queries_for_explain] = nil + end + end +end
\ No newline at end of file diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb new file mode 100644 index 0000000000..83c9b6e107 --- /dev/null +++ b/activerecord/test/cases/explain_test.rb @@ -0,0 +1,115 @@ +require 'cases/helper' +require 'models/car' +require 'active_support/core_ext/string/strip' + +if ActiveRecord::Base.connection.supports_explain? + class ExplainTest < ActiveRecord::TestCase + fixtures :cars + + def base + ActiveRecord::Base + end + + def connection + base.connection + end + + def test_logging_query_plan_with_logger + base.logger.expects(:warn).with do |message| + message.starts_with?('EXPLAIN for:') + end + + with_threshold(0) do + Car.where(:name => 'honda').all + end + end + + def test_logging_query_plan_without_logger + original = base.logger + base.logger = nil + + base.logger.expects(:warn).never + + with_threshold(0) do + car = Car.where(:name => 'honda').first + assert_equal 'honda', car.name + end + ensure + base.logger = original + end + + def test_collect_queries_for_explain + base.auto_explain_threshold_in_seconds = nil + queries = Thread.current[:available_queries_for_explain] = [] + + with_threshold(0) do + Car.where(:name => 'honda').all + end + + sql, binds = queries[0] + assert_match "SELECT", sql + assert_match "honda", sql + assert_equal [], binds + ensure + Thread.current[:available_queries_for_explain] = nil + end + + def test_collecting_queries_for_explain + result, queries = ActiveRecord::Base.collecting_queries_for_explain do + Car.where(:name => 'honda').all + end + + sql, binds = queries[0] + assert_match "SELECT", sql + assert_match "honda", sql + assert_equal [], binds + assert_equal [cars(:honda)], result + end + + def test_exec_explain_with_no_binds + sqls = %w(foo bar) + binds = [[], []] + queries = sqls.zip(binds) + + connection.stubs(:explain).returns('query plan foo', 'query plan bar') + expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") + assert_equal expected, base.exec_explain(queries) + end + + def test_exec_explain_with_binds + cols = [Object.new, Object.new] + cols[0].expects(:name).returns('wadus') + cols[1].expects(:name).returns('chaflan') + + sqls = %w(foo bar) + binds = [[[cols[0], 1]], [[cols[1], 2]]] + queries = sqls.zip(binds) + + connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n") + expected = <<-SQL.strip_heredoc + EXPLAIN for: #{sqls[0]} [["wadus", 1]] + query plan foo + + EXPLAIN for: #{sqls[1]} [["chaflan", 2]] + query plan bar + SQL + assert_equal expected, base.exec_explain(queries) + end + + def test_silence_auto_explain + base.expects(:collecting_sqls_for_explain).never + base.logger.expects(:warn).never + base.silence_auto_explain do + with_threshold(0) { Car.all } + end + end + + def with_threshold(threshold) + current_threshold = base.auto_explain_threshold_in_seconds + base.auto_explain_threshold_in_seconds = threshold + yield + ensure + base.auto_explain_threshold_in_seconds = current_threshold + end + end +end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 3088ab012f..76c041397a 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -57,6 +57,11 @@ class FinderTest < ActiveRecord::TestCase assert Topic.first.replies.exists? end + # ensures +exists?+ runs valid SQL by excluding order value + def test_exists_with_order + assert Topic.order(:id).uniq.exists? + end + def test_does_not_exist_with_empty_table_and_no_args_given Topic.delete_all assert !Topic.exists? @@ -369,6 +374,10 @@ class FinderTest < ActiveRecord::TestCase assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort end + def test_find_on_hash_conditions_with_array_of_integers_and_ranges + assert_equal [1,2,3,5,6,7,8,9], Comment.find(:all, :conditions => {:id => [1..2, 3, 5, 6..8, 9]}).map(&:id).sort + end + def test_find_on_multiple_hash_conditions assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }) assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } @@ -872,6 +881,17 @@ class FinderTest < ActiveRecord::TestCase assert_equal 23, sig38.client_of end + def test_find_or_create_from_two_attributes_and_hash + number_of_companies = Company.count + sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert_equal number_of_companies + 1, Company.count + assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23}) + assert sig38.persisted? + assert_equal "38signals", sig38.name + assert_equal 17, sig38.firm_id + assert_equal 23, sig38.client_of + end + def test_find_or_create_from_one_aggregate_attribute number_of_customers = Customer.count created_customer = Customer.find_or_create_by_balance(Money.new(123)) @@ -1110,10 +1130,10 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size + assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).size assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address}, - :order => ' author_addresses_authors.id DESC ', :limit => 3).size + :order => 'author_addresses_authors.id DESC ', :limit => 3).size end def test_find_with_nil_inside_set_passed_for_one_attribute @@ -1140,7 +1160,10 @@ class FinderTest < ActiveRecord::TestCase end def test_with_limiting_with_custom_select - posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id') + posts = Post.find( + :all, :include => :author, :select => ' posts.*, authors.id as "author_id"', + :references => :authors, :limit => 3, :order => 'posts.id' + ) assert_equal 3, posts.size assert_equal [0, 1, 1], posts.map(&:author_id).sort end @@ -1154,7 +1177,7 @@ class FinderTest < ActiveRecord::TestCase end def test_find_one_message_with_custom_primary_key - Toy.set_primary_key :name + Toy.primary_key = :name begin Toy.find 'Hello World!' rescue ActiveRecord::RecordNotFound => e diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 7e2dafcd01..562b370c97 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -1,31 +1,34 @@ -require "cases/helper" -require 'models/post' +require 'cases/helper' +require 'models/admin' +require 'models/admin/account' +require 'models/admin/randomly_named_c1' +require 'models/admin/user' require 'models/binary' -require 'models/topic' +require 'models/book' +require 'models/category' +require 'models/company' require 'models/computer' +require 'models/course' require 'models/developer' -require 'models/company' -require 'models/task' -require 'models/reply' require 'models/joke' -require 'models/course' -require 'models/category' +require 'models/matey' require 'models/parrot' require 'models/pirate' -require 'models/treasure' -require 'models/traffic_light' -require 'models/matey' +require 'models/post' +require 'models/randomly_named_c1' +require 'models/reply' require 'models/ship' -require 'models/book' -require 'models/admin' -require 'models/admin/account' -require 'models/admin/user' +require 'models/task' +require 'models/topic' +require 'models/traffic_light' +require 'models/treasure' require 'tempfile' class FixturesTest < ActiveRecord::TestCase self.use_instantiated_fixtures = true self.use_transactional_fixtures = false + # other_topics fixture should not be included here fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries, :traffic_lights FIXTURES = %w( accounts binaries companies customers @@ -62,8 +65,9 @@ class FixturesTest < ActiveRecord::TestCase end def test_create_fixtures - ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") - assert Parrot.find_by_name('Curious George'), 'George is in the database' + fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") + assert Parrot.find_by_name('Curious George'), 'George is not in the database' + assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" end def test_multiple_clean_fixtures @@ -73,6 +77,13 @@ class FixturesTest < ActiveRecord::TestCase fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) } end + def test_create_symbol_fixtures + fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } + + assert Course.find_by_name('Collection'), 'course is not in the database' + assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" + end + def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) @@ -93,7 +104,7 @@ class FixturesTest < ActiveRecord::TestCase # Reset cache to make finds on the new table work ActiveRecord::Fixtures.reset_cache - ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t| + ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| t.column :title, :string t.column :author_name, :string t.column :author_email_address, :string @@ -115,23 +126,36 @@ class FixturesTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_prefix = 'prefix_' ActiveRecord::Base.table_name_suffix = '_suffix' - topics = create_fixtures("topics") - - first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'") - assert_equal("The First Topic", first_row["title"]) + other_topic_klass = Class.new(ActiveRecord::Base) do + def self.name + "OtherTopic" + end + end - second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'") - assert_nil(second_row["author_email_address"]) + topics = [create_fixtures("other_topics")].flatten.first # This checks for a caching problem which causes a bug in the fixtures # class-level configuration helper. assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create" + + first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'") + assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found" + assert_equal("The First Topic", first_row["title"]) + + second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'") + assert_nil(second_row["author_email_address"]) + + assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym + # This assertion should preferably be the last in the list, because calling + # other_topic_klass.table_name sets a class-level instance variable + assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym + ensure # Restore prefix/suffix to its previous values ActiveRecord::Base.table_name_prefix = old_prefix ActiveRecord::Base.table_name_suffix = old_suffix - ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil + ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil end end @@ -179,7 +203,7 @@ class FixturesTest < ActiveRecord::TestCase #sanity check to make sure that this file never exists assert Dir[nonexistent_fixture_path+"*"].empty? - assert_raise(FixturesFileNotFound) do + assert_raise(Errno::ENOENT) do ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) end end @@ -213,7 +237,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } - data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) + data.force_encoding('ASCII-8BIT') data.freeze assert_equal data, @flowers.data end @@ -573,7 +597,7 @@ class FasterFixturesTest < ActiveRecord::TestCase load_extra_fixture('posts') assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') - self.class.setup_fixture_accessors('posts') + self.class.setup_fixture_accessors :posts assert_equal 'Welcome to the weblog', posts(:welcome).title end end @@ -731,3 +755,34 @@ class FixtureLoadingTest < ActiveRecord::TestCase ActiveRecord::TestCase.try_to_load_dependency(:works_out_fine) end end + +class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase + ActiveRecord::Fixtures.reset_cache + + set_fixture_class :randomly_named_a9 => + ClassNameThatDoesNotFollowCONVENTIONS, + :'admin/randomly_named_a9' => + Admin::ClassNameThatDoesNotFollowCONVENTIONS, + 'admin/randomly_named_b0' => + Admin::ClassNameThatDoesNotFollowCONVENTIONS + + fixtures :randomly_named_a9, 'admin/randomly_named_a9', + :'admin/randomly_named_b0' + + def test_named_accessor_for_randomly_named_fixture_and_class + assert_kind_of ClassNameThatDoesNotFollowCONVENTIONS, + randomly_named_a9(:first_instance) + end + + def test_named_accessor_for_randomly_named_namespaced_fixture_and_class + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + admin_randomly_named_a9(:first_instance) + assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS, + admin_randomly_named_b0(:second_instance) + end + + def test_table_name_is_defined_in_the_model + assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name + end +end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 6735bc521b..9f5f012073 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -2,12 +2,14 @@ require File.expand_path('../../../../load_paths', __FILE__) require 'config' -require 'test/unit' +require 'minitest/autorun' require 'stringio' require 'mocha' +require 'cases/test_case' require 'active_record' require 'active_support/dependencies' +require 'active_support/logger' require 'support/config' require 'support/connection' @@ -20,6 +22,9 @@ ActiveSupport::Deprecation.debug = true # Enable Identity Map only when ENV['IM'] is set to "true" ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true +ActiveRecord::Base.dependent_restrict_raises = false + # Connect to the database ARTest.connect @@ -56,36 +61,13 @@ ensure ActiveRecord::Base.default_timezone = old_zone end -module ActiveRecord - class SQLCounter - cattr_accessor :ignored_sql - self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] - - # FIXME: this needs to be refactored so specific database can add their own - # ignored SQL. This ignored SQL is for Oracle. - ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - - cattr_accessor :log - self.log = [] - - def call(name, start, finish, message_id, values) - sql = values[:sql] - - # FIXME: this seems bad. we should probably have a better way to indicate - # the query was cached - unless 'CACHE' == values[:name] - self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r } - end - end - end - - ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) -end - unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) - ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)} + old = ActiveRecord::Base.logger.level + ActiveRecord::Base.logger.level = ActiveSupport::Logger::ERROR + try_to_load_dependency_without_silence(*args) + ActiveRecord::Base.logger.level = old end alias_method_chain :try_to_load_dependency, :silence diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb index 60dcad4586..5b32a1c6d2 100644 --- a/activerecord/test/cases/identity_map/middleware_test.rb +++ b/activerecord/test/cases/identity_map/middleware_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "rack" module ActiveRecord module IdentityMap @@ -19,6 +20,7 @@ module ActiveRecord called = false mw = Middleware.new lambda { |env| called = true + [200, {}, nil] } mw.call({}) assert called, 'middleware delegated' @@ -27,6 +29,7 @@ module ActiveRecord def test_im_enabled_during_delegation mw = Middleware.new lambda { |env| assert IdentityMap.enabled?, 'identity map should be enabled' + [200, {}, nil] } mw.call({}) end diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb new file mode 100644 index 0000000000..8726ba5e51 --- /dev/null +++ b/activerecord/test/cases/inclusion_test.rb @@ -0,0 +1,113 @@ +require 'cases/helper' +require 'models/teapot' + +class BasicInclusionModelTest < ActiveRecord::TestCase + def test_basic_model + Teapot.create!(:name => "Ronnie Kemper") + assert_equal "Ronnie Kemper", Teapot.find(1).name + end + + def test_initialization + t = Teapot.new(:name => "Bob") + assert_equal "Bob", t.name + end + + def test_inherited_model + teapot = CoolTeapot.create!(:name => "Bob") + teapot.reload + + assert_equal "Bob", teapot.name + assert_equal "mmm", teapot.aaahhh + end + + def test_generated_feature_methods + assert Teapot < Teapot::GeneratedFeatureMethods + end + + def test_exists + t = Teapot.create!(:name => "Ronnie Kemper") + assert Teapot.exists?(t) + end + + def test_predicate_builder + t = Teapot.create!(:name => "Bob") + assert_equal "Bob", Teapot.where(:id => [t]).first.name + assert_equal "Bob", Teapot.where(:id => t).first.name + end + + def test_nested_model + assert_equal "ceiling_teapots", Ceiling::Teapot.table_name + end +end + +class InclusionUnitTest < ActiveRecord::TestCase + def setup + @klass = Class.new { include ActiveRecord::Model } + end + + def test_non_abstract_class + assert !@klass.abstract_class? + end + + def test_abstract_class + @klass.abstract_class = true + assert @klass.abstract_class? + end + + def test_establish_connection + assert @klass.respond_to?(:establish_connection) + assert ActiveRecord::Model.respond_to?(:establish_connection) + end + + def test_adapter_connection + name = "#{ActiveRecord::Base.connection_config[:adapter]}_connection" + assert @klass.respond_to?(name) + assert ActiveRecord::Model.respond_to?(name) + end + + def test_connection_handler + assert_equal ActiveRecord::Base.connection_handler, @klass.connection_handler + end + + def test_mirrored_configuration + ActiveRecord::Base.time_zone_aware_attributes = true + assert @klass.time_zone_aware_attributes + ActiveRecord::Base.time_zone_aware_attributes = false + assert !@klass.time_zone_aware_attributes + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + + # Doesn't really test anything, but this is here to ensure warnings don't occur + def test_included_twice + @klass.send :include, ActiveRecord::Model + end + + def test_deprecation_proxy + assert_equal ActiveRecord::Model.name, ActiveRecord::Model::DeprecationProxy.name + assert_equal ActiveRecord::Base.superclass, assert_deprecated { ActiveRecord::Model::DeprecationProxy.superclass } + + sup, sup2 = nil, nil + ActiveSupport.on_load(:__test_active_record_model_deprecation) do + sup = superclass + sup2 = send(:superclass) + end + assert_deprecated do + ActiveSupport.run_load_hooks(:__test_active_record_model_deprecation, ActiveRecord::Model::DeprecationProxy) + end + assert_equal ActiveRecord::Base.superclass, sup + assert_equal ActiveRecord::Base.superclass, sup2 + end +end + +class InclusionFixturesTest < ActiveRecord::TestCase + fixtures :teapots + + def test_fixtured_record + assert_equal "Bob", teapots(:bob).name + end + + def test_timestamped_fixture + assert_not_nil teapots(:bob).created_at + end +end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index b5d8314541..02df464469 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -65,7 +65,6 @@ class InheritanceTest < ActiveRecord::TestCase end def test_company_descends_from_active_record - assert_raise(NoMethodError) { 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' @@ -236,11 +235,11 @@ class InheritanceTest < ActiveRecord::TestCase c.save end [ Company, Firm, Client].each { |klass| klass.reset_column_information } - Company.set_inheritance_column('ruby_type') + Company.inheritance_column = 'ruby_type' end def switch_to_default_inheritance_column [ Company, Firm, Client].each { |klass| klass.reset_column_information } - Company.set_inheritance_column('type') + Company.inheritance_column = 'type' end end diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 3ae7b63dff..8f1cdd47ea 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -88,5 +88,17 @@ module ActiveRecord LegacyMigration.down assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" end + + def test_migrate_down_with_table_name_prefix + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + migration = InvertibleMigration.new + migration.migrate(:up) + assert_nothing_raised { migration.migrate(:down) } + assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" + ensure + ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' + end + end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e9bd7f07b6..807274ca67 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -6,12 +6,15 @@ require 'models/reader' require 'models/legacy_thing' require 'models/reference' require 'models/string_key_object' +require 'models/car' +require 'models/engine' +require 'models/wheel' class LockWithoutDefault < ActiveRecord::Base; end class LockWithCustomColumnWithoutDefault < ActiveRecord::Base - set_table_name :lock_without_defaults_cust - set_locking_column :custom_lock_version + self.table_name = :lock_without_defaults_cust + self.locking_column = :custom_lock_version end class ReadonlyFirstNamePerson < Person @@ -224,6 +227,18 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal lock_version, p1.lock_version end end + + def test_polymorphic_destroy_with_dependencies_and_lock_version + car = Car.create! + + assert_difference 'car.wheels.count' do + car.wheels << Wheel.create! + end + assert_difference 'car.wheels.count', -1 do + car.destroy + end + assert car.destroyed? + end end class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase @@ -278,6 +293,8 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase assert_equal true, p1.frozen? assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) } assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) } + ensure + remove_counter_column_from(Person, 'legacy_things_count') end private @@ -289,8 +306,8 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter) end - def remove_counter_column_from(model) - model.connection.remove_column model.table_name, :test_count + def remove_counter_column_from(model, col = :test_count) + model.connection.remove_column model.table_name, col model.reset_column_information end @@ -371,22 +388,32 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db? end end + def test_with_lock_commits_transaction + person = Person.find 1 + person.with_lock do + person.first_name = 'fooman' + person.save! + end + assert_equal 'fooman', person.reload.first_name + end + + def test_with_lock_rolls_back_transaction + person = Person.find 1 + old = person.first_name + person.with_lock do + person.first_name = 'fooman' + person.save! + raise 'oops' + end rescue nil + assert_equal old, person.reload.first_name + end + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) def test_no_locks_no_wait first, second = duel { Person.find 1 } assert first.end > second.end end - # Hit by ruby deadlock detection since connection checkout is mutexed. - if RUBY_VERSION < '1.9.0' - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } - end - end - protected def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 9e8475465e..d1f0ace184 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -5,7 +5,7 @@ require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper - include ActiveSupport::BufferedLogger::Severity + include ActiveSupport::Logger::Severity fixtures :posts @@ -13,6 +13,7 @@ class LogSubscriberTest < ActiveRecord::TestCase @old_logger = ActiveRecord::Base.logger @using_identity_map = ActiveRecord::IdentityMap.enabled? ActiveRecord::IdentityMap.enabled = false + Developer.primary_key super ActiveRecord::LogSubscriber.attach_to(:active_record) end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 9fff50edcb..8122857f52 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -50,6 +50,13 @@ module MassAssignmentTestHelpers assert_equal 'm', person.gender assert_equal 'rides a sweet bike', person.comments end + + def with_strict_sanitizer + ActiveRecord::Base.mass_assignment_sanitizer = :strict + yield + ensure + ActiveRecord::Base.mass_assignment_sanitizer = :logger + end end module MassAssignmentRelationTestHelpers @@ -323,6 +330,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.build_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_one_create_with_attr_protected_attributes @@ -350,6 +364,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_one_create_with_bang_with_attr_protected_attributes @@ -377,6 +398,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_one_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end @@ -438,6 +466,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + # create! def test_belongs_to_create_with_bang_with_attr_protected_attributes @@ -465,6 +500,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_belongs_to_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.create_best_friend_of!(attributes_hash.except(:id, :comments)) + assert_equal best_friend.id, @person.best_friend_of_id + end + end + end @@ -499,6 +541,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_build_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.build(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create def test_has_many_create_with_attr_protected_attributes @@ -526,6 +575,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + # create! def test_has_many_create_with_bang_with_attr_protected_attributes @@ -553,6 +609,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase assert_all_attributes(best_friend) end + def test_has_many_create_with_bang_with_strict_sanitizer + with_strict_sanitizer do + best_friend = @person.best_friends.create!(attributes_hash.except(:id, :comments)) + assert_equal @person.id, best_friend.best_friend_id + end + end + end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 0ab4f30363..ebf6e26385 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -104,7 +104,7 @@ class MethodScopingTest < ActiveRecord::TestCase def test_scoped_find_include # with the include, will retrieve only developers for the given project scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.find(:all, :conditions => 'projects.id = 2') + Developer.find(:all, :conditions => { 'projects.id' => 2 }) end assert scoped_developers.include?(developers(:david)) assert !scoped_developers.include?(developers(:jamis)) @@ -204,7 +204,7 @@ class MethodScopingTest < ActiveRecord::TestCase def test_scoped_count_include # with the include, will retrieve only developers for the given project Developer.send(:with_scope, :find => { :include => :projects }) do - assert_equal 1, Developer.count(:conditions => 'projects.id = 2') + assert_equal 1, Developer.count(:conditions => { 'projects.id' => 2 }) end end @@ -339,7 +339,7 @@ class NestedScopingTest < ActiveRecord::TestCase def test_nested_scoped_find_include Developer.send(:with_scope, :find => { :include => :projects }) do - Developer.send(:with_scope, :find => { :conditions => "projects.id = 2" }) do + Developer.send(:with_scope, :find => { :conditions => { 'projects.id' => 2 } }) do assert_nothing_raised { Developer.find(1) } assert_equal('David', Developer.find(:first).name) end @@ -348,7 +348,7 @@ class NestedScopingTest < ActiveRecord::TestCase def test_nested_scoped_find_merged_include # :include's remain unique and don't "double up" when merging - Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do Developer.send(:with_scope, :find => { :include => :projects }) do assert_equal 1, Developer.scoped.includes_values.uniq.length assert_equal 'David', Developer.find(:first).name @@ -356,7 +356,7 @@ class NestedScopingTest < ActiveRecord::TestCase end # the nested scope doesn't remove the first :include - Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do + Developer.send(:with_scope, :find => { :include => :projects, :conditions => { 'projects.id' => 2 } }) do Developer.send(:with_scope, :find => { :include => [] }) do assert_equal 1, Developer.scoped.includes_values.uniq.length assert_equal('David', Developer.find(:first).name) @@ -364,7 +364,7 @@ class NestedScopingTest < ActiveRecord::TestCase end # mixing array and symbol include's will merge correctly - Developer.send(:with_scope, :find => { :include => [:projects], :conditions => "projects.id = 2" }) do + Developer.send(:with_scope, :find => { :include => [:projects], :conditions => { 'projects.id' => 2 } }) do Developer.send(:with_scope, :find => { :include => :projects }) do assert_equal 1, Developer.scoped.includes_values.uniq.length assert_equal('David', Developer.find(:first).name) diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb new file mode 100644 index 0000000000..f0b1f74bd3 --- /dev/null +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -0,0 +1,328 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ChangeSchemaTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_create_table_without_id + testing_table_with_only_foo_attribute do + assert_equal connection.columns(:testings).size, 1 + end + end + + def test_add_column_with_primary_key_attribute + testing_table_with_only_foo_attribute do + connection.add_column :testings, :id, :primary_key + assert_equal connection.columns(:testings).size, 2 + end + end + + def test_create_table_adds_id + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_not_null_column + connection.create_table :testings do |t| + t.column :foo, :string, :null => false + end + + assert_raises(ActiveRecord::StatementInvalid) do + connection.execute "insert into testings (foo) values (NULL)" + end + end + + def test_create_table_with_defaults + # MySQL doesn't allow defaults on TEXT or BLOB columns. + mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) + + connection.create_table :testings do |t| + t.column :one, :string, :default => "hello" + t.column :two, :boolean, :default => true + t.column :three, :boolean, :default => false + t.column :four, :integer, :default => 1 + t.column :five, :text, :default => "hello" unless mysql + end + + columns = connection.columns(:testings) + one = columns.detect { |c| c.name == "one" } + two = columns.detect { |c| c.name == "two" } + three = columns.detect { |c| c.name == "three" } + four = columns.detect { |c| c.name == "four" } + five = columns.detect { |c| c.name == "five" } unless mysql + + assert_equal "hello", one.default + assert_equal true, two.default + assert_equal false, three.default + assert_equal 1, four.default + assert_equal "hello", five.default unless mysql + end + + def test_create_table_with_limits + connection.create_table :testings do |t| + t.column :foo, :string, :limit => 255 + + t.column :default_int, :integer + + t.column :one_int, :integer, :limit => 1 + t.column :four_int, :integer, :limit => 4 + t.column :eight_int, :integer, :limit => 8 + t.column :eleven_int, :integer, :limit => 11 + end + + columns = connection.columns(:testings) + foo = columns.detect { |c| c.name == "foo" } + assert_equal 255, foo.limit + + default = columns.detect { |c| c.name == "default_int" } + one = columns.detect { |c| c.name == "one_int" } + four = columns.detect { |c| c.name == "four_int" } + eight = columns.detect { |c| c.name == "eight_int" } + eleven = columns.detect { |c| c.name == "eleven_int" } + + if current_adapter?(:PostgreSQLAdapter) + assert_equal 'integer', default.sql_type + assert_equal 'smallint', one.sql_type + assert_equal 'integer', four.sql_type + assert_equal 'bigint', eight.sql_type + assert_equal 'integer', eleven.sql_type + elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + assert_match 'int(11)', default.sql_type + assert_match 'tinyint', one.sql_type + assert_match 'int', four.sql_type + assert_match 'bigint', eight.sql_type + assert_match 'int(11)', eleven.sql_type + elsif current_adapter?(:OracleAdapter) + assert_equal 'NUMBER(38)', default.sql_type + assert_equal 'NUMBER(1)', one.sql_type + assert_equal 'NUMBER(4)', four.sql_type + assert_equal 'NUMBER(8)', eight.sql_type + end + end + + def test_create_table_with_primary_key_prefix_as_table_name_with_underscore + ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore + + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_primary_key_prefix_as_table_name + ActiveRecord::Base.primary_key_prefix_type = :table_name + + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort + end + + def test_create_table_with_timestamps_should_create_datetime_columns + connection.create_table table_name do |t| + t.timestamps + end + created_columns = connection.columns(table_name) + + created_at_column = created_columns.detect {|c| c.name == 'created_at' } + updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + + assert !created_at_column.null + assert !updated_at_column.null + end + + def test_create_table_with_timestamps_should_create_datetime_columns_with_options + connection.create_table table_name do |t| + t.timestamps :null => false + end + created_columns = connection.columns(table_name) + + created_at_column = created_columns.detect {|c| c.name == 'created_at' } + updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + + assert !created_at_column.null + assert !updated_at_column.null + end + + def test_create_table_without_a_block + connection.create_table table_name + end + + def test_add_column_not_null_without_default + # Sybase, and SQLite3 will not allow you to add a NOT NULL + # column to a table without a default value. + if current_adapter?(:SybaseAdapter, :SQLite3Adapter) + skip "not supported on #{connection.class}" + end + + connection.create_table :testings do |t| + t.column :foo, :string + end + connection.add_column :testings, :bar, :string, :null => false + + assert_raise(ActiveRecord::StatementInvalid) do + connection.execute "insert into testings (foo, bar) values ('hello', NULL)" + end + end + + def test_add_column_not_null_with_default + connection.create_table :testings do |t| + t.column :foo, :string + end + + con = connection + connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) + connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" + connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) + assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" } + + assert_raises(ActiveRecord::StatementInvalid) do + unless current_adapter?(:OpenBaseAdapter) + connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" + else + connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", + "Testing Insert","id",2) + end + end + end + + def test_change_column_quotes_column_names + connection.create_table :testings do |t| + t.column :select, :string + end + + connection.change_column :testings, :select, :string, :limit => 10 + + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + connection.execute "insert into testings (id, #{connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" + else + connection.execute "insert into testings (#{connection.quote_column_name('select')}) values ('7 chars')" + end + end + + def test_keeping_default_and_notnull_constaint_on_change + connection.create_table :testings do |t| + t.column :title, :string + end + person_klass = Class.new(ActiveRecord::Base) + person_klass.table_name = 'testings' + + person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 + person_klass.reset_column_information + assert_equal 99, person_klass.columns_hash["wealth"].default + assert_equal false, person_klass.columns_hash["wealth"].null + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + else + assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + end + + # change column default to see that column doesn't lose its not null definition + person_klass.connection.change_column_default "testings", "wealth", 100 + person_klass.reset_column_information + assert_equal 100, person_klass.columns_hash["wealth"].default + assert_equal false, person_klass.columns_hash["wealth"].null + + # rename column to see that column doesn't lose its not null and/or default definition + person_klass.connection.rename_column "testings", "wealth", "money" + person_klass.reset_column_information + assert_nil person_klass.columns_hash["wealth"] + assert_equal 100, person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + + # change column + person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 + person_klass.reset_column_information + assert_equal 1000, person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + + # change column, make it nullable and clear default + person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil + person_klass.reset_column_information + assert_nil person_klass.columns_hash["money"].default + assert_equal true, person_klass.columns_hash["money"].null + + # change_column_null, make it not nullable and set null values to a default value + person_klass.connection.execute('UPDATE testings SET money = NULL') + person_klass.connection.change_column_null "testings", "money", false, 2000 + person_klass.reset_column_information + assert_nil person_klass.columns_hash["money"].default + assert_equal false, person_klass.columns_hash["money"].null + assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i + end + + def test_column_exists + connection.create_table :testings do |t| + t.column :foo, :string + end + + assert connection.column_exists?(:testings, :foo) + refute connection.column_exists?(:testings, :bar) + end + + def test_column_exists_with_type + connection.create_table :testings do |t| + t.column :foo, :string + t.column :bar, :decimal, :precision => 8, :scale => 2 + end + + assert connection.column_exists?(:testings, :foo, :string) + refute connection.column_exists?(:testings, :foo, :integer) + + assert connection.column_exists?(:testings, :bar, :decimal) + refute connection.column_exists?(:testings, :bar, :integer) + end + + def test_column_exists_with_definition + connection.create_table :testings do |t| + t.column :foo, :string, :limit => 100 + t.column :bar, :decimal, :precision => 8, :scale => 2 + end + + assert connection.column_exists?(:testings, :foo, :string, :limit => 100) + refute connection.column_exists?(:testings, :foo, :string, :limit => 50) + assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2) + refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) + end + + def test_column_exists_on_table_with_no_options_parameter_supplied + connection.create_table :testings do |t| + t.string :foo + end + connection.change_table :testings do |t| + assert t.column_exists?(:foo) + assert !(t.column_exists?(:bar)) + end + end + + private + def testing_table_with_only_foo_attribute + connection.create_table :testings, :id => false do |t| + t.column :foo, :string + end + + yield + end + end + end +end diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb new file mode 100644 index 0000000000..040445ef12 --- /dev/null +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -0,0 +1,188 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class ColumnAttributesTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def test_add_remove_single_field_using_string_arguments + refute TestModel.column_methods_hash.key?(:last_name) + + add_column 'test_models', 'last_name', :string + + TestModel.reset_column_information + + assert TestModel.column_methods_hash.key?(:last_name) + + remove_column 'test_models', 'last_name' + + TestModel.reset_column_information + refute TestModel.column_methods_hash.key?(:last_name) + end + + def test_add_remove_single_field_using_symbol_arguments + refute TestModel.column_methods_hash.key?(:last_name) + + add_column :test_models, :last_name, :string + + TestModel.reset_column_information + assert TestModel.column_methods_hash.key?(:last_name) + + remove_column :test_models, :last_name + + TestModel.reset_column_information + refute TestModel.column_methods_hash.key?(:last_name) + end + + def test_unabstracted_database_dependent_types + skip "not supported" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) + + add_column :test_models, :intelligence_quotient, :tinyint + TestModel.reset_column_information + assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type) + end + + # We specifically do a manual INSERT here, and then test only the SELECT + # functionality. This allows us to more easily catch INSERT being broken, + # but SELECT actually working fine. + def test_native_decimal_insert_manual_vs_automatic + correct_value = '0012345678901234567890.0123456789'.to_d + + connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + + # Do a manual insertion + if current_adapter?(:OracleAdapter) + connection.execute "insert into test_models (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)" + elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings + connection.execute "insert into test_models (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" + elsif current_adapter?(:PostgreSQLAdapter) + connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" + else + connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" + end + + # SELECT + row = TestModel.find(:first) + assert_kind_of BigDecimal, row.wealth + + # If this assert fails, that means the SELECT is broken! + unless current_adapter?(:SQLite3Adapter) + assert_equal correct_value, row.wealth + end + + # Reset to old state + TestModel.delete_all + + # Now use the Rails insertion + TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789") + + # SELECT + row = TestModel.find(:first) + assert_kind_of BigDecimal, row.wealth + + # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! + unless current_adapter?(:SQLite3Adapter) + assert_equal correct_value, row.wealth + end + end + + def test_add_column_with_precision_and_scale + connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + + def test_change_column_preserve_other_column_precision_and_scale + skip "only on sqlite3" unless current_adapter?(:SQLite3Adapter) + + connection.add_column 'test_models', 'last_name', :string + connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7 + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + + connection.change_column 'test_models', 'last_name', :string, :null => false + TestModel.reset_column_information + + wealth_column = TestModel.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + + def test_native_types + add_column "test_models", "first_name", :string + add_column "test_models", "last_name", :string + add_column "test_models", "bio", :text + add_column "test_models", "age", :integer + add_column "test_models", "height", :float + add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10' + add_column "test_models", "birthday", :datetime + add_column "test_models", "favorite_day", :date + add_column "test_models", "moment_of_truth", :datetime + add_column "test_models", "male", :boolean + + TestModel.create :first_name => 'bob', :last_name => 'bobsen', + :bio => "I was born ....", :age => 18, :height => 1.78, + :wealth => BigDecimal.new("12345678901234567890.0123456789"), + :birthday => 18.years.ago, :favorite_day => 10.days.ago, + :moment_of_truth => "1782-10-10 21:40:18", :male => true + + bob = TestModel.find(:first) + assert_equal 'bob', bob.first_name + assert_equal 'bobsen', bob.last_name + assert_equal "I was born ....", bob.bio + assert_equal 18, bob.age + + # Test for 30 significant digits (beyond the 16 of float), 10 of them + # after the decimal place. + + unless current_adapter?(:SQLite3Adapter) + assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth + end + + assert_equal true, bob.male? + + assert_equal String, bob.first_name.class + assert_equal String, bob.last_name.class + assert_equal String, bob.bio.class + assert_equal Fixnum, bob.age.class + assert_equal Time, bob.birthday.class + + if current_adapter?(:OracleAdapter, :SybaseAdapter) + # Sybase, and Oracle don't differentiate between date/time + assert_equal Time, bob.favorite_day.class + else + assert_equal Date, bob.favorite_day.class + end + + # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column + # therefore no timezone change is done afterwards when default timezone is changed + unless current_adapter?(:OracleAdapter) + # Test DateTime column and defaults, including timezone. + # FIXME: moment of truth may be Time on 64-bit platforms. + if bob.moment_of_truth.is_a?(DateTime) + + with_env_tz 'US/Eastern' do + bob.reload + assert_equal DateTime.local_offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + # US/Eastern is -5 hours from GMT + assert_equal Rational(-5, 24), bob.moment_of_truth.offset + assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end + end + end + + assert_instance_of TrueClass, bob.male? + assert_kind_of BigDecimal, bob.wealth + end + end + end +end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb new file mode 100644 index 0000000000..913d935f7c --- /dev/null +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -0,0 +1,60 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class ColumnPositioningTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + alias :conn :connection + + def setup + super + + unless current_adapter?(:MysqlAdapter, :Mysql2Adapter) + skip "not supported on #{connection.class}" + end + + @connection = ActiveRecord::Base.connection + + connection.create_table :testings, :id => false do |t| + t.column :first, :integer + t.column :second, :integer + t.column :third, :integer + end + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_column_positioning + assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning + conn.add_column :testings, :new_col, :integer + assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning_first + conn.add_column :testings, :new_col, :integer, :first => true + assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } + end + + def test_add_column_with_positioning_after + conn.add_column :testings, :new_col, :integer, :after => :first + assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } + end + + def test_change_column_with_positioning + conn.change_column :testings, :second, :integer, :first => true + assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } + + conn.change_column :testings, :second, :integer, :after => :third + assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } + end + + end + end +end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index d108b456f0..7d026961be 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -67,6 +67,24 @@ module ActiveRecord assert_equal [:drop_table, [:system_settings]], drop_table end + def test_invert_create_table_with_options + @recorder.record :create_table, [:people_reminders, {:id => false}] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:people_reminders]], drop_table + end + + def test_invert_create_join_table + @recorder.record :create_join_table, [:musics, :artists] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:artists_musics]], drop_table + end + + def test_invert_create_join_table_with_table_name + @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}] + drop_table = @recorder.inverse.first + assert_equal [:drop_table, [:catalog]], drop_table + end + def test_invert_rename_table @recorder.record :rename_table, [:old, :new] rename = @recorder.inverse.first diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb new file mode 100644 index 0000000000..0428d9ba76 --- /dev/null +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -0,0 +1,70 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class CreateJoinTableTest < ActiveRecord::TestCase + attr_reader :connection + + def setup + super + @connection = ActiveRecord::Base.connection + end + + def test_create_join_table + connection.create_join_table :artists, :musics + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_set_not_null_by_default + connection.create_join_table :artists, :musics + + assert_equal [false, false], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_strings + connection.create_join_table 'artists', 'musics' + + assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort + ensure + connection.drop_table :artists_musics + end + + def test_create_join_table_with_the_proper_order + connection.create_join_table :videos, :musics + + assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort + ensure + connection.drop_table :musics_videos + end + + def test_create_join_table_with_the_table_name + connection.create_join_table :artists, :musics, :table_name => :catalog + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_the_table_name_as_string + connection.create_join_table :artists, :musics, :table_name => 'catalog' + + assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort + ensure + connection.drop_table :catalog + end + + def test_create_join_table_with_column_options + connection.create_join_table :artists, :musics, :column_options => {:null => true} + + assert_equal [true, true], connection.columns(:artists_musics).map(&:null) + ensure + connection.drop_table :artists_musics + end + end + end +end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb new file mode 100644 index 0000000000..fe53510ba2 --- /dev/null +++ b/activerecord/test/cases/migration/helper.rb @@ -0,0 +1,64 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class << self + attr_accessor :message_count + end + + def puts(text="") + ActiveRecord::Migration.message_count ||= 0 + ActiveRecord::Migration.message_count += 1 + end + + module TestHelper + attr_reader :connection, :table_name + + class TestModel < ActiveRecord::Base + self.table_name = 'test_models' + end + + def setup + super + @connection = ActiveRecord::Base.connection + connection.create_table :test_models do |t| + t.timestamps + end + + TestModel.reset_column_information + end + + def teardown + super + TestModel.reset_table_name + TestModel.reset_sequence_name + connection.drop_table :test_models rescue nil + end + + private + def add_column(*args) + connection.add_column(*args) + end + + def remove_column(*args) + connection.remove_column(*args) + end + + def rename_column(*args) + connection.rename_column(*args) + end + + def add_index(*args) + connection.add_index(*args) + end + + def change_column(*args) + connection.change_column(*args) + end + + def rename_table(*args) + connection.rename_table(*args) + end + end + end +end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb new file mode 100644 index 0000000000..dd9492924c --- /dev/null +++ b/activerecord/test/cases/migration/index_test.rb @@ -0,0 +1,185 @@ +require 'cases/helper' + +module ActiveRecord + class Migration + class IndexTest < ActiveRecord::TestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + + connection.create_table table_name do |t| + t.column :foo, :string, :limit => 100 + t.column :bar, :string, :limit => 100 + + t.string :first_name + t.string :last_name, :limit => 100 + t.string :key, :limit => 100 + t.boolean :administrator + end + end + + def teardown + super + connection.drop_table :testings rescue nil + ActiveRecord::Base.primary_key_prefix_type = nil + end + + def test_rename_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + # keep the names short to make Oracle and similar behave + connection.add_index(table_name, [:foo], :name => 'old_idx') + connection.rename_index(table_name, 'old_idx', 'new_idx') + + # if the adapter doesn't support the indexes call, pick defaults that let the test pass + refute connection.index_name_exists?(table_name, 'old_idx', false) + assert connection.index_name_exists?(table_name, 'new_idx', true) + end + + def test_double_add_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + connection.add_index(table_name, [:foo], :name => 'some_idx') + assert_raises(ArgumentError) { + connection.add_index(table_name, [:foo], :name => 'some_idx') + } + end + + def test_remove_nonexistent_index + skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter) + + # we do this by name, so OpenBase is a wash as noted above + assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") } + end + + def test_add_index_name_length_limit + good_index_name = 'x' * connection.index_name_length + too_long_index_name = good_index_name + 'x' + + assert_raises(ArgumentError) { + connection.add_index(table_name, "foo", :name => too_long_index_name) + } + + refute connection.index_name_exists?(table_name, too_long_index_name, false) + connection.add_index(table_name, "foo", :name => good_index_name) + + assert connection.index_name_exists?(table_name, good_index_name, false) + connection.remove_index(table_name, :name => good_index_name) + end + + def test_index_symbol_names + connection.add_index table_name, :foo, :name => :symbol_index_name + assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + + connection.remove_index table_name, :name => :symbol_index_name + refute connection.index_exists?(table_name, :foo, :name => :symbol_index_name) + end + + def test_index_exists + connection.add_index :testings, :foo + + assert connection.index_exists?(:testings, :foo) + assert !connection.index_exists?(:testings, :bar) + end + + def test_index_exists_on_multiple_columns + connection.add_index :testings, [:foo, :bar] + + assert connection.index_exists?(:testings, [:foo, :bar]) + end + + def test_unique_index_exists + connection.add_index :testings, :foo, :unique => true + + assert connection.index_exists?(:testings, :foo, :unique => true) + end + + def test_named_index_exists + connection.add_index :testings, :foo, :name => "custom_index_name" + + assert connection.index_exists?(:testings, :foo, :name => "custom_index_name") + end + + def test_add_index_attribute_length_limit + connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil} + + assert connection.index_exists?(:testings, [:foo, :bar]) + end + + def test_add_index + connection.add_index("testings", "last_name") + connection.remove_index("testings", "last_name") + + # Orcl nds shrt indx nms. Sybs 2. + # OpenBase does not have named indexes. You must specify a single column name + unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", :column => ["last_name", "first_name"]) + + # Oracle adapter cannot have specified index name larger than 30 characters + # Oracle adapter is shortening index name when just column list is given + unless current_adapter?(:OracleAdapter) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name) + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", "last_name_and_first_name") + end + connection.add_index("testings", ["last_name", "first_name"]) + connection.remove_index("testings", ["last_name", "first_name"]) + + connection.add_index("testings", ["last_name"], :length => 10) + connection.remove_index("testings", "last_name") + + connection.add_index("testings", ["last_name"], :length => {:last_name => 10}) + connection.remove_index("testings", ["last_name"]) + + connection.add_index("testings", ["last_name", "first_name"], :length => 10) + connection.remove_index("testings", ["last_name", "first_name"]) + + connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) + connection.remove_index("testings", ["last_name", "first_name"]) + end + + # quoting + # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word + # OpenBase does not have named indexes. You must specify a single column name + unless current_adapter?(:OpenBaseAdapter) + connection.add_index("testings", ["key"], :name => "key_idx", :unique => true) + connection.remove_index("testings", :name => "key_idx", :unique => true) + end + + # Sybase adapter does not support indexes on :boolean columns + # OpenBase does not have named indexes. You must specify a single column + unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) + connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin") + connection.remove_index("testings", :name => "named_admin") + end + + # Selected adapters support index sort order + if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + connection.add_index("testings", ["last_name"], :order => {:last_name => :desc}) + connection.remove_index("testings", ["last_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc}) + connection.remove_index("testings", ["last_name", "first_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) + connection.remove_index("testings", ["last_name", "first_name"]) + connection.add_index("testings", ["last_name", "first_name"], :order => :desc) + connection.remove_index("testings", ["last_name", "first_name"]) + end + end + + def test_add_partial_index + skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter) + + connection.add_index("testings", "last_name", :where => "first_name = 'john doe'") + assert connection.index_exists?("testings", "last_name") + + connection.remove_index("testings", "last_name") + assert !connection.index_exists?("testings", "last_name") + end + end + end +end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb new file mode 100644 index 0000000000..ee0c20747e --- /dev/null +++ b/activerecord/test/cases/migration/logger_test.rb @@ -0,0 +1,37 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class LoggerTest < ActiveRecord::TestCase + # mysql can't roll back ddl changes + self.use_transactional_fixtures = false + + Migration = Struct.new(:name, :version) do + def migrate direction + # do nothing + end + end + + def setup + super + ActiveRecord::SchemaMigration.create_table + ActiveRecord::SchemaMigration.delete_all + end + + def teardown + super + ActiveRecord::SchemaMigration.drop_table + end + + def test_migration_should_be_run_without_logger + previous_logger = ActiveRecord::Base.logger + ActiveRecord::Base.logger = nil + migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)] + ActiveRecord::Migrator.new(:up, migrations).migrate + ensure + ActiveRecord::Base.logger = previous_logger + end + end + end +end + diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb new file mode 100644 index 0000000000..16e09fd80e --- /dev/null +++ b/activerecord/test/cases/migration/rename_column_test.rb @@ -0,0 +1,194 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class RenameColumnTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + # FIXME: this is more of an integration test with AR::Base and the + # schema modifications. Maybe we should move this? + def test_add_rename + add_column "test_models", "girlfriend", :string + TestModel.reset_column_information + + TestModel.create :girlfriend => 'bobette' + + rename_column "test_models", "girlfriend", "exgirlfriend" + + TestModel.reset_column_information + bob = TestModel.find(:first) + + assert_equal "bobette", bob.exgirlfriend + end + + # FIXME: another integration test. We should decouple this from the + # AR::Base implementation. + def test_rename_column_using_symbol_arguments + add_column :test_models, :first_name, :string + + TestModel.create :first_name => 'foo' + + rename_column :test_models, :first_name, :nick_name + TestModel.reset_column_information + assert TestModel.column_names.include?("nick_name") + assert_equal ['foo'], TestModel.find(:all).map(&:nick_name) + end + + # FIXME: another integration test. We should decouple this from the + # AR::Base implementation. + def test_rename_column + add_column "test_models", "first_name", "string" + + TestModel.create :first_name => 'foo' + + rename_column "test_models", "first_name", "nick_name" + TestModel.reset_column_information + assert TestModel.column_names.include?("nick_name") + assert_equal ['foo'], TestModel.find(:all).map(&:nick_name) + end + + def test_rename_column_preserves_default_value_not_null + add_column 'test_models', 'salary', :integer, :default => 70000 + + default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default + assert_equal 70000, default_before + + rename_column "test_models", "salary", "anual_salary" + + assert TestModel.column_names.include?("anual_salary") + default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default + assert_equal 70000, default_after + end + + def test_rename_nonexistent_column + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) + ActiveRecord::StatementInvalid + else + ActiveRecord::ActiveRecordError + end + assert_raise(exception) do + rename_column "test_models", "nonexistent", "should_fail" + end + end + + def test_rename_column_with_sql_reserved_word + add_column 'test_models', 'first_name', :string + rename_column "test_models", "first_name", "group" + + assert TestModel.column_names.include?("group") + end + + def test_rename_column_with_an_index + add_column "test_models", :hat_name, :string + add_index :test_models, :hat_name + + # FIXME: we should test that the index goes away + rename_column "test_models", "hat_name", "name" + end + + def test_remove_column_with_index + add_column "test_models", :hat_name, :string + add_index :test_models, :hat_name + + # FIXME: we should test that the index goes away + remove_column("test_models", "hat_name") + end + + def test_remove_column_with_multi_column_index + add_column "test_models", :hat_size, :integer + add_column "test_models", :hat_style, :string, :limit => 100 + add_index "test_models", ["hat_style", "hat_size"], :unique => true + + # FIXME: we should test that the index goes away + remove_column("test_models", "hat_size") + end + + # FIXME: we need to test that these calls do something + def test_change_type_of_not_null_column + change_column "test_models", "updated_at", :datetime, :null => false + change_column "test_models", "updated_at", :datetime, :null => false + change_column "test_models", "updated_at", :datetime, :null => true + end + + def test_change_column_nullability + add_column "test_models", "funny", :boolean + assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" + + change_column "test_models", "funny", :boolean, :null => false, :default => true + + TestModel.reset_column_information + refute TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" + + change_column "test_models", "funny", :boolean, :null => true + TestModel.reset_column_information + assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" + end + + def test_change_column + add_column 'test_models', 'age', :integer + add_column 'test_models', 'approved', :boolean, :default => true + + old_columns = connection.columns(TestModel.table_name) + + assert old_columns.find { |c| c.name == 'age' && c.type == :integer } + + change_column "test_models", "age", :string + + new_columns = connection.columns(TestModel.table_name) + + refute new_columns.find { |c| c.name == 'age' and c.type == :integer } + assert new_columns.find { |c| c.name == 'age' and c.type == :string } + + old_columns = connection.columns(TestModel.table_name) + assert old_columns.find { |c| + c.name == 'approved' && c.type == :boolean && c.default == true + } + + change_column :test_models, :approved, :boolean, :default => false + new_columns = connection.columns(TestModel.table_name) + + refute new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } + assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } + change_column :test_models, :approved, :boolean, :default => true + end + + def test_change_column_with_nil_default + add_column "test_models", "contributor", :boolean, :default => true + assert TestModel.new.contributor? + + change_column "test_models", "contributor", :boolean, :default => nil + TestModel.reset_column_information + refute 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? + + change_column "test_models", "administrator", :boolean, :default => false + TestModel.reset_column_information + refute TestModel.new.administrator? + end + + def test_change_column_default + add_column "test_models", "first_name", :string + connection.change_column_default "test_models", "first_name", "Tester" + + assert_equal "Tester", TestModel.new.first_name + end + + def test_change_column_default_to_null + add_column "test_models", "first_name", :string + connection.change_column_default "test_models", "first_name", nil + assert_nil TestModel.new.first_name + end + + def test_remove_column_no_second_parameter_raises_exception + assert_raise(ArgumentError) { connection.remove_column("funny") } + end + end + end +end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb new file mode 100644 index 0000000000..d5ff2c607f --- /dev/null +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -0,0 +1,72 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class RenameTableTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def setup + super + add_column 'test_models', :url, :string + remove_column 'test_models', :created_at + remove_column 'test_models', :updated_at + end + + def test_rename_table_for_sqlite_should_work_with_reserved_words + renamed = false + + skip "not supported" unless current_adapter?(:SQLite3Adapter) + + add_column :test_models, :url, :string + connection.rename_table :references, :old_references + connection.rename_table :test_models, :references + + renamed = true + + # Using explicit id in insert for compatibility across all databases + con = connection + con.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)" + assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1") + ensure + return unless renamed + connection.rename_table :references, :test_models + connection.rename_table :old_references, :references + end + + def test_rename_table + rename_table :test_models, :octopi + + # Using explicit id in insert for compatibility across all databases + con = connection + con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) + + con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + + con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + + rename_table :octopi, :test_models + end + + def test_rename_table_with_an_index + add_index :test_models, :url + + rename_table :test_models, :octopi + + # Using explicit id in insert for compatibility across all databases + con = ActiveRecord::Base.connection + con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) + con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" + con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + + assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1") + assert connection.indexes(:octopi).first.columns.include?("url") + + rename_table :octopi, :test_models + end + end + end +end diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb new file mode 100644 index 0000000000..8fd770abd1 --- /dev/null +++ b/activerecord/test/cases/migration/table_and_index_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" + +module ActiveRecord + class Migration + class TableAndIndexTest < ActiveRecord::TestCase + def test_add_schema_info_respects_prefix_and_suffix + conn = ActiveRecord::Base.connection + + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + + conn.initialize_schema_migrations_table + + assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end + end + end +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 49944eced9..92dc150104 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "cases/migration/helper" require 'bigdecimal/util' require 'models/person' @@ -6,2273 +7,941 @@ require 'models/topic' require 'models/developer' require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" +require MIGRATIONS_ROOT + "/rename/1_we_need_things" +require MIGRATIONS_ROOT + "/rename/2_rename_things" require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" -if ActiveRecord::Base.connection.supports_migrations? - class BigNumber < ActiveRecord::Base; end +class BigNumber < ActiveRecord::Base; end - class Reminder < ActiveRecord::Base; end +class Reminder < ActiveRecord::Base; end - class ActiveRecord::Migration - class << self - attr_accessor :message_count - end - - def puts(text="") - ActiveRecord::Migration.message_count ||= 0 - ActiveRecord::Migration.message_count += 1 - end - end - - class MigrationTableAndIndexTest < ActiveRecord::TestCase - def test_add_schema_info_respects_prefix_and_suffix - conn = ActiveRecord::Base.connection +class Thing < ActiveRecord::Base; end - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) - # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) +class MigrationTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false - conn.initialize_schema_migrations_table + fixtures :people - assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" + def setup + super + %w(reminders people_reminders prefix_reminders_suffix).each do |table| + Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_column_information + ActiveRecord::Migration.verbose = true + ActiveRecord::Migration.message_count = 0 end - class MigrationTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false - - fixtures :people - - def setup - ActiveRecord::Migration.verbose = true - ActiveRecord::Migration.message_count = 0 - end - - def teardown - ActiveRecord::Base.connection.initialize_schema_migrations_table - ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - - %w(reminders people_reminders prefix_reminders_suffix).each do |table| - Reminder.connection.drop_table(table) rescue nil - end - Reminder.reset_column_information - - %w(last_name key bio age height wealth birthday favorite_day - moment_of_truth male administrator funny).each do |column| - Person.connection.remove_column('people', column) rescue nil - end - Person.connection.remove_column("people", "first_name") rescue nil - Person.connection.remove_column("people", "middle_name") rescue nil - Person.connection.add_column("people", "first_name", :string, :limit => 40) - Person.reset_column_information - end - - def test_add_index - # Limit size of last_name and key columns to support Firebird index limitations - Person.connection.add_column "people", "last_name", :string, :limit => 100 - Person.connection.add_column "people", "key", :string, :limit => 100 - Person.connection.add_column "people", "administrator", :boolean - - assert_nothing_raised { Person.connection.add_index("people", "last_name") } - assert_nothing_raised { Person.connection.remove_index("people", "last_name") } - - # Orcl nds shrt indx nms. Sybs 2. - # OpenBase does not have named indexes. You must specify a single column name - unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) } - # Oracle adapter cannot have specified index name larger than 30 characters - # Oracle adapter is shortening index name when just column list is given - unless current_adapter?(:OracleAdapter) - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", :name => :index_people_on_last_name_and_first_name) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") } - end - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) } - assert_nothing_raised { Person.connection.remove_index("people", "last_name") } - assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) } - assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } - end - - # quoting - # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word - # OpenBase does not have named indexes. You must specify a single column name - unless current_adapter?(:OpenBaseAdapter) - Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'id'}" #some databases (including sqlite2 won't add a unique index if existing data non unique) - assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) } - assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) } - end - - # Sybase adapter does not support indexes on :boolean columns - # OpenBase does not have named indexes. You must specify a single column - unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) - assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") } - assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") } - end - end - - def test_index_symbol_names - assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name } - assert Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name) - assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name } - assert !Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name) - end - - def test_add_index_length_limit - good_index_name = 'x' * Person.connection.index_name_length - too_long_index_name = good_index_name + 'x' - assert_raise(ArgumentError) { Person.connection.add_index("people", "first_name", :name => too_long_index_name) } - assert !Person.connection.index_name_exists?("people", too_long_index_name, false) - assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) } - assert Person.connection.index_name_exists?("people", good_index_name, false) - Person.connection.remove_index("people", :name => good_index_name) - end - - def test_remove_nonexistent_index - # we do this by name, so OpenBase is a wash as noted above - unless current_adapter?(:OpenBaseAdapter) - assert_raise(ArgumentError) { Person.connection.remove_index("people", "no_such_index") } - end - end - - def test_rename_index - unless current_adapter?(:OpenBaseAdapter) - # keep the names short to make Oracle and similar behave - Person.connection.add_index('people', [:first_name], :name => 'old_idx') - assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') } - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert !Person.connection.index_name_exists?('people', 'old_idx', false) - assert Person.connection.index_name_exists?('people', 'new_idx', true) - end - end - - def test_double_add_index - unless current_adapter?(:OpenBaseAdapter) - Person.connection.add_index('people', [:first_name], :name => 'some_idx') - assert_raise(ArgumentError) { Person.connection.add_index('people', [:first_name], :name => 'some_idx') } - end - end - - def test_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo - - assert Person.connection.index_exists?(:testings, :foo) - assert !Person.connection.index_exists?(:testings, :bar) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_index_exists_on_multiple_columns - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :string, :limit => 100 - end - Person.connection.add_index :testings, [:foo, :bar] - - assert Person.connection.index_exists?(:testings, [:foo, :bar]) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_unique_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo, :unique => true - - assert Person.connection.index_exists?(:testings, :foo, :unique => true) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_named_index_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - end - Person.connection.add_index :testings, :foo, :name => "custom_index_name" - - assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name") - ensure - Person.connection.drop_table :testings rescue nil - end - - def testing_table_with_only_foo_attribute - Person.connection.create_table :testings, :id => false do |t| - t.column :foo, :string - end + def teardown + ActiveRecord::Base.connection.initialize_schema_migrations_table + ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - yield Person.connection - ensure - Person.connection.drop_table :testings rescue nil - end - protected :testing_table_with_only_foo_attribute - - def test_create_table_without_id - testing_table_with_only_foo_attribute do |connection| - assert_equal connection.columns(:testings).size, 1 - end + %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table| + Thing.connection.drop_table(table) rescue nil end + Thing.reset_column_information - def test_add_column_with_primary_key_attribute - testing_table_with_only_foo_attribute do |connection| - assert_nothing_raised { connection.add_column :testings, :id, :primary_key } - assert_equal connection.columns(:testings).size, 2 - end + %w(reminders people_reminders prefix_reminders_suffix).each do |table| + Reminder.connection.drop_table(table) rescue nil end + Reminder.reset_column_information - def test_create_table_adds_id - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - assert_equal %w(foo id), - Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil + %w(last_name key bio age height wealth birthday favorite_day + moment_of_truth male administrator funny).each do |column| + Person.connection.remove_column('people', column) rescue nil end + Person.connection.remove_column("people", "first_name") rescue nil + Person.connection.remove_column("people", "middle_name") rescue nil + Person.connection.add_column("people", "first_name", :string, :limit => 40) + Person.reset_column_information + end - def test_create_table_with_not_null_column - assert_nothing_raised do - Person.connection.create_table :testings do |t| - t.column :foo, :string, :null => false - end - end - - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute "insert into testings (foo) values (NULL)" - end - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_create_table_with_defaults - # MySQL doesn't allow defaults on TEXT or BLOB columns. - mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) - - Person.connection.create_table :testings do |t| - t.column :one, :string, :default => "hello" - t.column :two, :boolean, :default => true - t.column :three, :boolean, :default => false - t.column :four, :integer, :default => 1 - t.column :five, :text, :default => "hello" unless mysql - end - - columns = Person.connection.columns(:testings) - one = columns.detect { |c| c.name == "one" } - two = columns.detect { |c| c.name == "two" } - three = columns.detect { |c| c.name == "three" } - four = columns.detect { |c| c.name == "four" } - five = columns.detect { |c| c.name == "five" } unless mysql - - assert_equal "hello", one.default - assert_equal true, two.default - assert_equal false, three.default - assert_equal 1, four.default - assert_equal "hello", five.default unless mysql - - ensure - Person.connection.drop_table :testings rescue nil + def test_create_table_with_force_true_does_not_drop_nonexisting_table + if Person.connection.table_exists?(:testings2) + Person.connection.drop_table :testings2 end - def test_create_table_with_limits - assert_nothing_raised do - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 255 + # using a copy as we need the drop_table method to + # continue to work for the ensure block of the test + temp_conn = Person.connection.dup - t.column :default_int, :integer + assert_not_equal temp_conn, Person.connection - t.column :one_int, :integer, :limit => 1 - t.column :four_int, :integer, :limit => 4 - t.column :eight_int, :integer, :limit => 8 - t.column :eleven_int, :integer, :limit => 11 - end - end - - columns = Person.connection.columns(:testings) - foo = columns.detect { |c| c.name == "foo" } - assert_equal 255, foo.limit - - default = columns.detect { |c| c.name == "default_int" } - one = columns.detect { |c| c.name == "one_int" } - four = columns.detect { |c| c.name == "four_int" } - eight = columns.detect { |c| c.name == "eight_int" } - eleven = columns.detect { |c| c.name == "eleven_int" } - - if current_adapter?(:PostgreSQLAdapter) - assert_equal 'integer', default.sql_type - assert_equal 'smallint', one.sql_type - assert_equal 'integer', four.sql_type - assert_equal 'bigint', eight.sql_type - assert_equal 'integer', eleven.sql_type - elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - assert_match 'int(11)', default.sql_type - assert_match 'tinyint', one.sql_type - assert_match 'int', four.sql_type - assert_match 'bigint', eight.sql_type - assert_match 'int(11)', eleven.sql_type - elsif current_adapter?(:OracleAdapter) - assert_equal 'NUMBER(38)', default.sql_type - assert_equal 'NUMBER(1)', one.sql_type - assert_equal 'NUMBER(4)', four.sql_type - assert_equal 'NUMBER(8)', eight.sql_type - end - ensure - Person.connection.drop_table :testings rescue nil + temp_conn.create_table :testings2, :force => true do |t| + t.column :foo, :string end + ensure + Person.connection.drop_table :testings2 rescue nil + end - def test_create_table_with_primary_key_prefix_as_table_name_with_underscore - ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore - - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil - ActiveRecord::Base.primary_key_prefix_type = nil - end + def connection + ActiveRecord::Base.connection + end - def test_create_table_with_primary_key_prefix_as_table_name - ActiveRecord::Base.primary_key_prefix_type = :table_name + def test_migration_instance_has_connection + migration = Class.new(ActiveRecord::Migration).new + assert_equal connection, migration.connection + end - Person.connection.create_table :testings do |t| - t.column :foo, :string + def test_method_missing_delegates_to_connection + migration = Class.new(ActiveRecord::Migration) { + def connection + Class.new { + def create_table; "hi mom!"; end + }.new end + }.new - assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort - ensure - Person.connection.drop_table :testings rescue nil - ActiveRecord::Base.primary_key_prefix_type = nil - end - - def test_create_table_with_force_true_does_not_drop_nonexisting_table - if Person.connection.table_exists?(:testings2) - Person.connection.drop_table :testings2 - end + assert_equal "hi mom!", migration.method_missing(:create_table) + end - # using a copy as we need the drop_table method to - # continue to work for the ensure block of the test - temp_conn = Person.connection.dup - temp_conn.expects(:drop_table).never - temp_conn.create_table :testings2, :force => true do |t| - t.column :foo, :string - end - ensure - Person.connection.drop_table :testings2 rescue nil + def test_add_table_with_decimals + Person.connection.drop_table :big_numbers rescue nil + + assert !BigNumber.table_exists? + GiveMeBigNumbers.up + + assert BigNumber.create( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3, + :value_of_e => BigDecimal("2.7182818284590452353602875") + ) + + b = BigNumber.find(:first) + assert_not_nil b + + assert_not_nil b.bank_balance + assert_not_nil b.big_bank_balance + assert_not_nil b.world_population + assert_not_nil b.my_house_population + assert_not_nil b.value_of_e + + # TODO: set world_population >= 2**62 to cover 64-bit platforms and test + # is_a?(Bignum) + assert_kind_of Integer, b.world_population + assert_equal 6000000000, b.world_population + assert_kind_of Fixnum, b.my_house_population + assert_equal 3, b.my_house_population + assert_kind_of BigDecimal, b.bank_balance + assert_equal BigDecimal("1586.43"), b.bank_balance + assert_kind_of BigDecimal, b.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance + + # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with + # precision/scale explicitly left out. By the SQL standard, numbers + # assigned to this field should be truncated but that's seldom respected. + if current_adapter?(:PostgreSQLAdapter) + # - PostgreSQL changes the SQL spec on columns declared simply as + # "decimal" to something more useful: instead of being given a scale + # of 0, they take on the compile-time limit for precision and scale, + # so the following should succeed unless you have used really wacky + # compilation options + # - SQLite2 has the default behavior of preserving all data sent in, + # so this happens there too + assert_kind_of BigDecimal, b.value_of_e + assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e + elsif current_adapter?(:SQLite3Adapter) + # - SQLite3 stores a float, in violation of SQL + assert_kind_of BigDecimal, b.value_of_e + assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 + else + # - SQL standard is an integer + assert_kind_of Fixnum, b.value_of_e + assert_equal 2, b.value_of_e end - def test_create_table_with_timestamps_should_create_datetime_columns - table_name = :testings + GiveMeBigNumbers.down + assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } + end - Person.connection.create_table table_name do |t| - t.timestamps - end - created_columns = Person.connection.columns(table_name) + def test_filtering_migrations + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } + ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) - assert !created_at_column.null - assert !updated_at_column.null - ensure - Person.connection.drop_table table_name rescue nil - end - - def test_create_table_with_timestamps_should_create_datetime_columns_with_options - table_name = :testings + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - Person.connection.create_table table_name do |t| - t.timestamps :null => false - end - created_columns = Person.connection.columns(table_name) + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) - created_at_column = created_columns.detect {|c| c.name == 'created_at' } - updated_at_column = created_columns.detect {|c| c.name == 'updated_at' } + Person.reset_column_information + assert !Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + end - assert !created_at_column.null - assert !updated_at_column.null - ensure - Person.connection.drop_table table_name rescue nil + class MockMigration < ActiveRecord::Migration + attr_reader :went_up, :went_down + def initialize + @went_up = false + @went_down = false end - def test_create_table_without_a_block - table_name = :testings - Person.connection.create_table table_name - ensure - Person.connection.drop_table table_name rescue nil + def up + @went_up = true + super end - # Sybase, and SQLite3 will not allow you to add a NOT NULL - # column to a table without a default value. - unless current_adapter?(:SybaseAdapter, :SQLite3Adapter) - def test_add_column_not_null_without_default - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - Person.connection.add_column :testings, :bar, :string, :null => false - - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)" - end - ensure - Person.connection.drop_table :testings rescue nil - end + def down + @went_down = true + super end + end - def test_add_column_not_null_with_default - Person.connection.create_table :testings do |t| - t.column :foo, :string - end - - con = Person.connection - Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) - Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" - Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) - assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" } - - assert_raise(ActiveRecord::StatementInvalid) do - unless current_adapter?(:OpenBaseAdapter) - Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" - else - Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)", - "Testing Insert","id",2) - end - end - ensure - Person.connection.drop_table :testings rescue nil - end - - # We specifically do a manual INSERT here, and then test only the SELECT - # functionality. This allows us to more easily catch INSERT being broken, - # but SELECT actually working fine. - def test_native_decimal_insert_manual_vs_automatic - correct_value = '0012345678901234567890.0123456789'.to_d - - Person.delete_all - Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' - Person.reset_column_information - - # Do a manual insertion - if current_adapter?(:OracleAdapter) - Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, 0, 0)" - elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings - Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)" - elsif current_adapter?(:PostgreSQLAdapter) - Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())" - else - Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)" - end - - # SELECT - row = Person.find(:first) - assert_kind_of BigDecimal, row.wealth - - # If this assert fails, that means the SELECT is broken! - unless current_adapter?(:SQLite3Adapter) - assert_equal correct_value, row.wealth - end - - # Reset to old state - Person.delete_all - - # Now use the Rails insertion - assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") } - - # SELECT - row = Person.find(:first) - assert_kind_of BigDecimal, row.wealth + def test_instance_based_migration_up + migration = MockMigration.new + assert !migration.went_up, 'have not gone up' + assert !migration.went_down, 'have not gone down' - # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! - unless current_adapter?(:SQLite3Adapter) - assert_equal correct_value, row.wealth - end + migration.migrate :up + assert migration.went_up, 'have gone up' + assert !migration.went_down, 'have not gone down' + end - # Reset to old state - Person.connection.del_column "people", "wealth" rescue nil - Person.reset_column_information - end + def test_instance_based_migration_down + migration = MockMigration.new + assert !migration.went_up, 'have not gone up' + assert !migration.went_down, 'have not gone down' - def test_add_column_with_precision_and_scale - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information + migration.migrate :down + assert !migration.went_up, 'have gone up' + assert migration.went_down, 'have not gone down' + end - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale + def test_migrator_one_up_with_exception_and_rollback + unless ActiveRecord::Base.connection.supports_ddl_transactions? + skip "not supported on #{ActiveRecord::Base.connection.class}" end - # Test SQLite adapter specifically for decimal types with precision and scale - # attributes, since these need to be maintained in schema but aren't actually - # used in SQLite itself - if current_adapter?(:SQLite3Adapter) - def test_change_column_with_new_precision_and_scale - Person.delete_all - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information + refute Person.column_methods_hash.include?(:last_name) - Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8 - Person.reset_column_information + migration = Struct.new(:name, :version) { + def migrate(x); raise 'Something broke'; end + }.new('zomg', 100) - wealth_column = Person.columns_hash['wealth'] - assert_equal 12, wealth_column.precision - assert_equal 8, wealth_column.scale - end - - def test_change_column_preserve_other_column_precision_and_scale - Person.delete_all - Person.connection.add_column 'people', 'last_name', :string - Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 - Person.reset_column_information - - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale - - Person.connection.change_column 'people', 'last_name', :string, :null => false - Person.reset_column_information - - wealth_column = Person.columns_hash['wealth'] - assert_equal 9, wealth_column.precision - assert_equal 7, wealth_column.scale - end - end + migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - def test_native_types - Person.delete_all - Person.connection.add_column "people", "last_name", :string - Person.connection.add_column "people", "bio", :text - Person.connection.add_column "people", "age", :integer - Person.connection.add_column "people", "height", :float - Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' - Person.connection.add_column "people", "birthday", :datetime - Person.connection.add_column "people", "favorite_day", :date - Person.connection.add_column "people", "moment_of_truth", :datetime - Person.connection.add_column "people", "male", :boolean - Person.reset_column_information - - assert_nothing_raised do - Person.create :first_name => 'bob', :last_name => 'bobsen', - :bio => "I was born ....", :age => 18, :height => 1.78, - :wealth => BigDecimal.new("12345678901234567890.0123456789"), - :birthday => 18.years.ago, :favorite_day => 10.days.ago, - :moment_of_truth => "1782-10-10 21:40:18", :male => true - end + e = assert_raise(StandardError) { migrator.migrate } - bob = Person.find(:first) - assert_equal 'bob', bob.first_name - assert_equal 'bobsen', bob.last_name - assert_equal "I was born ....", bob.bio - assert_equal 18, bob.age + assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - # Test for 30 significant digits (beyond the 16 of float), 10 of them - # after the decimal place. - - unless current_adapter?(:SQLite3Adapter) - assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth - end - - assert_equal true, bob.male? + Person.reset_column_information + refute Person.column_methods_hash.include?(:last_name) + end - assert_equal String, bob.first_name.class - assert_equal String, bob.last_name.class - assert_equal String, bob.bio.class - assert_equal Fixnum, bob.age.class - assert_equal Time, bob.birthday.class + def test_schema_migrations_table_name + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end - if current_adapter?(:OracleAdapter, :SybaseAdapter) - # Sybase, and Oracle don't differentiate between date/time - assert_equal Time, bob.favorite_day.class - else - assert_equal Date, bob.favorite_day.class - end + def test_proper_table_name + assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') + assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) + assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + Reminder.reset_table_name + assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + + # Use the model's own prefix/suffix if a model is given + ActiveRecord::Base.table_name_prefix = "ARprefix_" + ActiveRecord::Base.table_name_suffix = "_ARsuffix" + Reminder.table_name_prefix = 'prefix_' + Reminder.table_name_suffix = '_suffix' + Reminder.reset_table_name + assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) + Reminder.table_name_prefix = '' + Reminder.table_name_suffix = '' + Reminder.reset_table_name + + # Use AR::Base's prefix/suffix if string or symbol is given + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + end - # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column - # therefore no timezone change is done afterwards when default timezone is changed - unless current_adapter?(:OracleAdapter) - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - bob.reload - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM - assert_equal DateTime::ITALY, bob.moment_of_truth.start - end - end - end + def test_rename_table_with_prefix_and_suffix + assert !Thing.table_exists? + ActiveRecord::Base.table_name_prefix = 'prefix_' + ActiveRecord::Base.table_name_suffix = '_suffix' + Thing.reset_table_name + Thing.reset_sequence_name + WeNeedThings.up + + assert Thing.create("content" => "hello world") + assert_equal "hello world", Thing.find(:first).content + + RenameThings.up + Thing.table_name = "prefix_awesome_things_suffix" + + assert_equal "hello world", Thing.find(:first).content + ensure + ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = '' + Thing.reset_table_name + Thing.reset_sequence_name + end - assert_instance_of TrueClass, bob.male? - assert_kind_of BigDecimal, bob.wealth - end + def test_add_drop_table_with_prefix_and_suffix + assert !Reminder.table_exists? + ActiveRecord::Base.table_name_prefix = 'prefix_' + ActiveRecord::Base.table_name_suffix = '_suffix' + Reminder.reset_table_name + Reminder.reset_sequence_name + WeNeedReminders.up + assert Reminder.create("content" => "hello world", "remind_at" => Time.now) + assert_equal "hello world", Reminder.find(:first).content + + WeNeedReminders.down + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + ensure + ActiveRecord::Base.table_name_prefix = '' + ActiveRecord::Base.table_name_suffix = '' + Reminder.reset_table_name + Reminder.reset_sequence_name + end - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - def test_unabstracted_database_dependent_types - Person.delete_all + def test_create_table_with_binary_column + Person.connection.drop_table :binary_testings rescue nil - ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint - Person.reset_column_information - assert_match(/tinyint/, Person.columns_hash['intelligence_quotient'].sql_type) - ensure - ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil + assert_nothing_raised { + Person.connection.create_table :binary_testings do |t| + t.column "data", :binary, :null => false end - end - - def test_add_remove_single_field_using_string_arguments - assert !Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.add_column 'people', 'last_name', :string - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.remove_column 'people', 'last_name' - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end + } - def test_add_remove_single_field_using_symbol_arguments - assert !Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.add_column :people, :last_name, :string - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - - ActiveRecord::Migration.remove_column :people, :last_name - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end + columns = Person.connection.columns(:binary_testings) + data_column = columns.detect { |c| c.name == "data" } if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - def testing_table_for_positioning - Person.connection.create_table :testings, :id => false do |t| - t.column :first, :integer - t.column :second, :integer - t.column :third, :integer - end - - yield Person.connection - ensure - Person.connection.drop_table :testings rescue nil - end - protected :testing_table_for_positioning - - def test_column_positioning - testing_table_for_positioning do |conn| - assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name } - end - end - - def test_add_column_with_positioning - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer - assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer, :first => true - assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.add_column :testings, :new_col, :integer, :after => :first - assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name } - end - end - - def test_change_column_with_positioning - testing_table_for_positioning do |conn| - conn.change_column :testings, :second, :integer, :first => true - assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name } - end - testing_table_for_positioning do |conn| - conn.change_column :testings, :second, :integer, :after => :third - assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } - end - end - end - - def test_add_rename - Person.delete_all - - begin - Person.connection.add_column "people", "girlfriend", :string - Person.reset_column_information - Person.create :girlfriend => 'bobette' - - Person.connection.rename_column "people", "girlfriend", "exgirlfriend" - - Person.reset_column_information - bob = Person.find(:first) - - assert_equal "bobette", bob.exgirlfriend - ensure - Person.connection.remove_column("people", "girlfriend") rescue nil - Person.connection.remove_column("people", "exgirlfriend") rescue nil - end - - end - - def test_rename_column_using_symbol_arguments - begin - names_before = Person.find(:all).map(&:first_name) - Person.connection.rename_column :people, :first_name, :nick_name - Person.reset_column_information - assert Person.column_names.include?("nick_name") - assert_equal names_before, Person.find(:all).map(&:nick_name) - ensure - Person.connection.remove_column("people","nick_name") - Person.connection.add_column("people","first_name", :string) - end - end - - def test_rename_column - begin - names_before = Person.find(:all).map(&:first_name) - Person.connection.rename_column "people", "first_name", "nick_name" - Person.reset_column_information - assert Person.column_names.include?("nick_name") - assert_equal names_before, Person.find(:all).map(&:nick_name) - ensure - Person.connection.remove_column("people","nick_name") - Person.connection.add_column("people","first_name", :string) - end - end - - def test_rename_column_preserves_default_value_not_null - begin - default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default - assert_equal 70000, default_before - Developer.connection.rename_column "developers", "salary", "anual_salary" - Developer.reset_column_information - assert Developer.column_names.include?("anual_salary") - default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default - assert_equal 70000, default_after - ensure - Developer.connection.rename_column "developers", "anual_salary", "salary" - Developer.reset_column_information - end - end - - def test_rename_nonexistent_column - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :default => nil - end - exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) - ActiveRecord::StatementInvalid - else - ActiveRecord::ActiveRecordError - end - assert_raise(exception) do - Person.connection.rename_column "hats", "nonexistent", "should_fail" - end - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_rename_column_with_sql_reserved_word - begin - assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" } - Person.reset_column_information - assert Person.column_names.include?("group") - ensure - Person.connection.remove_column("people", "group") rescue nil - Person.connection.add_column("people", "first_name", :string) rescue nil - end - end - - def test_rename_column_with_an_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - end - Person.connection.add_index :hats, :hat_name - assert_nothing_raised do - Person.connection.rename_column "hats", "hat_name", "name" - end - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_with_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - end - ActiveRecord::Base.connection.add_index "hats", "hat_size" - - assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_with_multi_column_index - ActiveRecord::Base.connection.create_table(:hats) do |table| - table.column :hat_name, :string, :limit => 100 - table.column :hat_size, :integer - table.column :hat_style, :string, :limit => 100 - end - ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true - - assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } - ensure - ActiveRecord::Base.connection.drop_table(:hats) - end - - def test_remove_column_no_second_parameter_raises_exception - assert_raise(ArgumentError) { Person.connection.remove_column("funny") } + assert_equal '', data_column.default + else + assert_nil data_column.default end - def test_change_type_of_not_null_column - assert_nothing_raised do - Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information - - Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information + Person.connection.drop_table :binary_testings rescue nil + end - Topic.connection.change_column "topics", "written_on", :datetime, :null => true - Topic.reset_column_information - end - end + def test_create_table_with_custom_sequence_name + skip "not supported" unless current_adapter? :OracleAdapter - if current_adapter?(:SQLite3Adapter) - def test_rename_table_for_sqlite_should_work_with_reserved_words - begin - assert_nothing_raised do - ActiveRecord::Base.connection.rename_table :references, :old_references - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string - end - end - - assert_nothing_raised { ActiveRecord::Base.connection.rename_table :octopuses, :references } - - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - assert_nothing_raised do - con.execute "INSERT INTO 'references' (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://rubyonrails.com')" - end - assert_equal 'http://rubyonrails.com', ActiveRecord::Base.connection.select_value("SELECT url FROM 'references' WHERE id=1") - - ensure - ActiveRecord::Base.connection.drop_table :references - ActiveRecord::Base.connection.rename_table :old_references, :references - end - end - end - - def test_rename_table + # table name is 29 chars, the standard sequence name will + # be 33 chars and should be shortened + assert_nothing_raised do begin - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string + Person.connection.create_table :table_with_name_thats_just_ok do |t| + t.column :foo, :string, :null => false end - ActiveRecord::Base.connection.rename_table :octopuses, :octopi - - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) - assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" } - con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) - - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1") - ensure - ActiveRecord::Base.connection.drop_table :octopuses rescue nil - ActiveRecord::Base.connection.drop_table :octopi rescue nil + Person.connection.drop_table :table_with_name_thats_just_ok rescue nil end end - def test_change_column_nullability - Person.delete_all - Person.connection.add_column "people", "funny", :boolean - Person.reset_column_information - assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls" - Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true - Person.reset_column_information - assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" - Person.connection.change_column "people", "funny", :boolean, :null => true - Person.reset_column_information - assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point" - end - - def test_rename_table_with_an_index + # should be all good w/ a custom sequence name + assert_nothing_raised do begin - ActiveRecord::Base.connection.create_table :octopuses do |t| - t.column :url, :string + Person.connection.create_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' do |t| + t.column :foo, :string, :null => false end - ActiveRecord::Base.connection.add_index :octopuses, :url - ActiveRecord::Base.connection.rename_table :octopuses, :octopi - - # Using explicit id in insert for compatibility across all databases - con = ActiveRecord::Base.connection - con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter) - assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" } - con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter) + Person.connection.execute("select suitably_short_seq.nextval from dual") - assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1") - assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url") ensure - ActiveRecord::Base.connection.drop_table :octopuses rescue nil - ActiveRecord::Base.connection.drop_table :octopi rescue nil - end - end - - def test_change_column - Person.connection.add_column 'people', 'age', :integer - label = "test_change_column Columns" - old_columns = Person.connection.columns(Person.table_name, label) - assert old_columns.find { |c| c.name == 'age' and c.type == :integer } - - assert_nothing_raised { Person.connection.change_column "people", "age", :string } - - new_columns = Person.connection.columns(Person.table_name, label) - assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer } - assert new_columns.find { |c| c.name == 'age' and c.type == :string } - - old_columns = Topic.connection.columns(Topic.table_name, label) - assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } - assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } - new_columns = Topic.connection.columns(Topic.table_name, label) - assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } - assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } - assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } - end - - def test_change_column_with_nil_default - Person.connection.add_column "people", "contributor", :boolean, :default => true - Person.reset_column_information - assert Person.new.contributor? - - assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil } - Person.reset_column_information - assert !Person.new.contributor? - assert_nil Person.new.contributor - ensure - Person.connection.remove_column("people", "contributor") rescue nil - end - - def test_change_column_with_new_default - Person.connection.add_column "people", "administrator", :boolean, :default => true - Person.reset_column_information - assert Person.new.administrator? - - assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false } - Person.reset_column_information - assert !Person.new.administrator? - ensure - Person.connection.remove_column("people", "administrator") rescue nil - end - - def test_change_column_default - Person.connection.change_column_default "people", "first_name", "Tester" - Person.reset_column_information - assert_equal "Tester", Person.new.first_name - end - - def test_change_column_quotes_column_names - Person.connection.create_table :testings do |t| - t.column :select, :string - end - - assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } - - # Oracle needs primary key value from sequence - if current_adapter?(:OracleAdapter) - assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" } - else - assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } - end - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_keeping_default_and_notnull_constaint_on_change - Person.connection.create_table :testings do |t| - t.column :title, :string - end - person_klass = Class.new(Person) - person_klass.set_table_name 'testings' - - person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99 - person_klass.reset_column_information - assert_equal 99, person_klass.columns_hash["wealth"].default - assert_equal false, person_klass.columns_hash["wealth"].null - # Oracle needs primary key value from sequence - if current_adapter?(:OracleAdapter) - assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} - else - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} - end - - # change column default to see that column doesn't lose its not null definition - person_klass.connection.change_column_default "testings", "wealth", 100 - person_klass.reset_column_information - assert_equal 100, person_klass.columns_hash["wealth"].default - assert_equal false, person_klass.columns_hash["wealth"].null - - # rename column to see that column doesn't lose its not null and/or default definition - person_klass.connection.rename_column "testings", "wealth", "money" - person_klass.reset_column_information - assert_nil person_klass.columns_hash["wealth"] - assert_equal 100, person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - - # change column - person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000 - person_klass.reset_column_information - assert_equal 1000, person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - - # change column, make it nullable and clear default - person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil - person_klass.reset_column_information - assert_nil person_klass.columns_hash["money"].default - assert_equal true, person_klass.columns_hash["money"].null - - # change_column_null, make it not nullable and set null values to a default value - person_klass.connection.execute('UPDATE testings SET money = NULL') - person_klass.connection.change_column_null "testings", "money", false, 2000 - person_klass.reset_column_information - assert_nil person_klass.columns_hash["money"].default - assert_equal false, person_klass.columns_hash["money"].null - assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_change_column_default_to_null - Person.connection.change_column_default "people", "first_name", nil - Person.reset_column_information - assert_nil Person.new.first_name - end - - def test_column_exists - Person.connection.create_table :testings do |t| - t.column :foo, :string + Person.connection.drop_table :table_with_name_thats_just_ok, + :sequence_name => 'suitably_short_seq' rescue nil end - - assert Person.connection.column_exists?(:testings, :foo) - assert !Person.connection.column_exists?(:testings, :bar) - ensure - Person.connection.drop_table :testings rescue nil end - def test_column_exists_with_type - Person.connection.create_table :testings do |t| - t.column :foo, :string - t.column :bar, :decimal, :precision => 8, :scale => 2 - end - - assert Person.connection.column_exists?(:testings, :foo, :string) - assert !Person.connection.column_exists?(:testings, :foo, :integer) - assert Person.connection.column_exists?(:testings, :bar, :decimal) - assert !Person.connection.column_exists?(:testings, :bar, :integer) - ensure - Person.connection.drop_table :testings rescue nil - end - - def test_column_exists_with_definition - Person.connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :decimal, :precision => 8, :scale => 2 - end - - assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100) - assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50) - assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2) - assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) - ensure - Person.connection.drop_table :testings rescue nil + # confirm the custom sequence got dropped + assert_raise(ActiveRecord::StatementInvalid) do + Person.connection.execute("select suitably_short_seq.nextval from dual") end + end - def test_column_exists_on_table_with_no_options_parameter_supplied - Person.connection.create_table :testings do |t| - t.string :foo - end - Person.connection.change_table :testings do |t| - assert t.column_exists?(:foo) - assert !(t.column_exists?(:bar)) - end + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield ensure - Person.connection.drop_table :testings rescue nil - end - - def test_add_table - assert !Reminder.table_exists? - - WeNeedReminders.up - - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - WeNeedReminders.down - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - end - - def test_add_table_with_decimals - Person.connection.drop_table :big_numbers rescue nil - - assert !BigNumber.table_exists? - GiveMeBigNumbers.up - - assert BigNumber.create( - :bank_balance => 1586.43, - :big_bank_balance => BigDecimal("1000234000567.95"), - :world_population => 6000000000, - :my_house_population => 3, - :value_of_e => BigDecimal("2.7182818284590452353602875") - ) - - b = BigNumber.find(:first) - assert_not_nil b - - assert_not_nil b.bank_balance - assert_not_nil b.big_bank_balance - assert_not_nil b.world_population - assert_not_nil b.my_house_population - assert_not_nil b.value_of_e - - # TODO: set world_population >= 2**62 to cover 64-bit platforms and test - # is_a?(Bignum) - assert_kind_of Integer, b.world_population - assert_equal 6000000000, b.world_population - assert_kind_of Fixnum, b.my_house_population - assert_equal 3, b.my_house_population - assert_kind_of BigDecimal, b.bank_balance - assert_equal BigDecimal("1586.43"), b.bank_balance - assert_kind_of BigDecimal, b.big_bank_balance - assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance - - # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with - # precision/scale explicitly left out. By the SQL standard, numbers - # assigned to this field should be truncated but that's seldom respected. - if current_adapter?(:PostgreSQLAdapter) - # - PostgreSQL changes the SQL spec on columns declared simply as - # "decimal" to something more useful: instead of being given a scale - # of 0, they take on the compile-time limit for precision and scale, - # so the following should succeed unless you have used really wacky - # compilation options - # - SQLite2 has the default behavior of preserving all data sent in, - # so this happens there too - assert_kind_of BigDecimal, b.value_of_e - assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e - elsif current_adapter?(:SQLite3Adapter) - # - SQLite3 stores a float, in violation of SQL - assert_kind_of BigDecimal, b.value_of_e - assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001 - else - # - SQL standard is an integer - assert_kind_of Fixnum, b.value_of_e - assert_equal 2, b.value_of_e - end - - GiveMeBigNumbers.down - assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } - end - - def test_migrator - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - - assert_equal 3, ActiveRecord::Migrator.current_version - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid") - - assert_equal 0, ActiveRecord::Migrator.current_version - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - end - - class MockMigration < ActiveRecord::Migration - attr_reader :went_up, :went_down - def initialize - @went_up = false - @went_down = false - end - - def up - @went_up = true - super - end - - def down - @went_down = true - super - end - end - - def test_instance_based_migration_up - migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' - - migration.migrate :up - assert migration.went_up, 'have gone up' - assert !migration.went_down, 'have not gone down' - end - - def test_instance_based_migration_down - migration = MockMigration.new - assert !migration.went_up, 'have not gone up' - assert !migration.went_down, 'have not gone down' - - migration.migrate :down - assert !migration.went_up, 'have gone up' - assert migration.went_down, 'have not gone down' - end - - def test_migrator_one_up - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2) - - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - end - - def test_migrator_one_down - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end +end - def test_migrator_one_up_one_down - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - end - - def test_migrator_double_up - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) - assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) } - assert_equal(1, ActiveRecord::Migrator.current_version) - end - - def test_migrator_double_down - assert_equal(0, ActiveRecord::Migrator.current_version) - ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) - assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) } - assert_equal(0, ActiveRecord::Migrator.current_version) - end - - if ActiveRecord::Base.connection.supports_ddl_transactions? - def test_migrator_one_up_with_exception_and_rollback - assert !Person.column_methods_hash.include?(:last_name) - - e = assert_raise(StandardError) do - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100) - end - - assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message - - Person.reset_column_information - assert !Person.column_methods_hash.include?(:last_name) - end +class ReservedWordsMigrationTest < ActiveRecord::TestCase + def test_drop_index_from_table_named_values + connection = Person.connection + connection.create_table :values, :force => true do |t| + t.integer :value end - def test_finds_migrations - migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations - - [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| - assert_equal migrations[i].version, pair.first - assert_equal migrations[i].name, pair.last - end + assert_nothing_raised do + connection.add_index :values, :value + connection.remove_index :values, :column => :value end - def test_finds_migrations_from_two_directories - directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] - migrations = ActiveRecord::Migrator.new(:up, directories).migrations - - [[20090101010101, "PeopleHaveHobbies"], - [20090101010202, "PeopleHaveDescriptions"], - [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], - [20100201010101, "ValidWithTimestampsWeNeedReminders"], - [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| - assert_equal pair.first, migrations[i].version - assert_equal pair.last, migrations[i].name - end - end + connection.drop_table :values rescue nil + end +end - def test_finds_pending_migrations - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1) - migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations - assert_equal 1, migrations.size - assert_equal migrations[0].version, 3 - assert_equal migrations[0].name, 'InterleavedInnocentJointable' +class ChangeTableMigrationsTest < ActiveRecord::TestCase + def setup + @connection = Person.connection + @connection.create_table :delete_me, :force => true do |t| end + end - def test_relative_migrations - list = Dir.chdir(MIGRATIONS_ROOT) do - ActiveRecord::Migrator.up("valid/", 1) - end + def teardown + Person.connection.drop_table :delete_me rescue nil + end - migration_proxy = list.find { |item| - item.name == 'ValidPeopleHaveLastNames' - } - assert migration_proxy, 'should find pending migration' + def test_references_column_type_adds_id + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) + t.references :customer end + end - def test_only_loads_pending_migrations - # migrate up to 1 - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - - proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) - - names = proxies.map(&:name) - assert !names.include?('ValidPeopleHaveLastNames') - assert names.include?('WeNeedReminders') - assert names.include?('InnocentJointable') + def test_remove_references_column_type_removes_id + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'customer_id') + t.remove_references :customer end + end - def test_target_version_zero_should_run_only_once - # migrate up to 1 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - - # migrate down to 0 - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - - # migrate down to 0 again - proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - assert_equal [], proxies + def test_add_belongs_to_works_like_add_references + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) + t.belongs_to :customer end + end - def test_migrator_db_has_no_schema_migrations_table - # Oracle adapter raises error if semicolon is present as last character - if current_adapter?(:OracleAdapter) - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") - else - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") - end - assert_nothing_raised do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - end + def test_remove_belongs_to_works_like_remove_references + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'customer_id') + t.remove_belongs_to :customer end + end - def test_migrator_verbosity - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert_not_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migration.message_count = 0 - - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert_not_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migration.message_count = 0 + def test_references_column_type_with_polymorphic_adds_type + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {}) + @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {}) + t.references :taggable, :polymorphic => true end + end - def test_migrator_verbosity_off - ActiveRecord::Migration.verbose = false - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert_equal 0, ActiveRecord::Migration.message_count - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert_equal 0, ActiveRecord::Migration.message_count + def test_remove_references_column_type_with_polymorphic_removes_type + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'taggable_type') + @connection.expects(:remove_column).with(:delete_me, 'taggable_id') + t.remove_references :taggable, :polymorphic => true end + end - def test_migrator_going_down_due_to_version_target - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - - assert !Person.column_methods_hash.include?(:last_name) - assert !Reminder.table_exists? - - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content + def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false}) + @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false}) + t.references :taggable, :polymorphic => true, :null => false end + end - def test_migrator_rollback - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - assert_equal(3, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(2, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(1, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(0, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal(0, ActiveRecord::Migrator.current_version) + def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, 'taggable_type') + @connection.expects(:remove_column).with(:delete_me, 'taggable_id') + t.remove_references :taggable, :polymorphic => true, :null => false end + end - def test_migrator_forward - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) - assert_equal(1, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2) - assert_equal(3, ActiveRecord::Migrator.current_version) - - ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid") - assert_equal(3, ActiveRecord::Migrator.current_version) + def test_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expects(:add_timestamps).with(:delete_me) + t.timestamps end + end - def test_get_all_versions - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([1,2], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([1], ActiveRecord::Migrator.get_all_versions) - - ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") - assert_equal([], ActiveRecord::Migrator.get_all_versions) + def test_remove_timestamps_creates_updated_at_and_created_at + with_change_table do |t| + @connection.expects(:remove_timestamps).with(:delete_me) + t.remove_timestamps end + end - def test_schema_migrations_table_name - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" - Reminder.reset_table_name - assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - Reminder.reset_table_name - assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - end - - def test_proper_table_name - assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) - assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) - Reminder.reset_table_name - assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) - - # Use the model's own prefix/suffix if a model is given - ActiveRecord::Base.table_name_prefix = "ARprefix_" - ActiveRecord::Base.table_name_suffix = "_ARsuffix" - Reminder.table_name_prefix = 'prefix_' - Reminder.table_name_suffix = '_suffix' - Reminder.reset_table_name - assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) - Reminder.table_name_prefix = '' - Reminder.table_name_suffix = '' - Reminder.reset_table_name - - # Use AR::Base's prefix/suffix if string or symbol is given - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" - Reminder.reset_table_name - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') - assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - Reminder.reset_table_name - end - - def test_add_drop_table_with_prefix_and_suffix - assert !Reminder.table_exists? - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' - Reminder.reset_table_name - Reminder.reset_sequence_name - WeNeedReminders.up - assert Reminder.create("content" => "hello world", "remind_at" => Time.now) - assert_equal "hello world", Reminder.find(:first).content - - WeNeedReminders.down - assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } - ensure - ActiveRecord::Base.table_name_prefix = '' - ActiveRecord::Base.table_name_suffix = '' - Reminder.reset_table_name - Reminder.reset_sequence_name + def string_column + if current_adapter?(:PostgreSQLAdapter) + "character varying(255)" + elsif current_adapter?(:OracleAdapter) + 'VARCHAR2(255)' + else + 'varchar(255)' end + end - def test_create_table_with_binary_column - Person.connection.drop_table :binary_testings rescue nil - - assert_nothing_raised { - Person.connection.create_table :binary_testings do |t| - t.column "data", :binary, :null => false - end - } - - columns = Person.connection.columns(:binary_testings) - data_column = columns.detect { |c| c.name == "data" } - - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - assert_equal '', data_column.default - else - assert_nil data_column.default - end - - Person.connection.drop_table :binary_testings rescue nil + def integer_column + if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + 'int(11)' + elsif current_adapter?(:OracleAdapter) + 'NUMBER(38)' + else + 'integer' end + end - def test_migrator_with_duplicates - assert_raise(ActiveRecord::DuplicateMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil) - end + def test_integer_creates_integer_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {}) + @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {}) + t.integer :foo, :bar end + end - def test_migrator_with_duplicate_names - assert_raise(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil) - end + def test_string_creates_string_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :foo, string_column, {}) + @connection.expects(:add_column).with(:delete_me, :bar, string_column, {}) + t.string :foo, :bar end + end - def test_migrator_with_missing_version_numbers - assert_raise(ActiveRecord::UnknownMigrationVersionError) do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) - end + def test_column_creates_column + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :bar, :integer, {}) + t.column :bar, :integer end + end - def test_create_table_with_custom_sequence_name - return unless current_adapter? :OracleAdapter - - # table name is 29 chars, the standard sequence name will - # be 33 chars and should be shortened - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok do |t| - t.column :foo, :string, :null => false - end - ensure - Person.connection.drop_table :table_with_name_thats_just_ok rescue nil - end - end - - # should be all good w/ a custom sequence name - assert_nothing_raised do - begin - Person.connection.create_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' do |t| - t.column :foo, :string, :null => false - end - - Person.connection.execute("select suitably_short_seq.nextval from dual") - - ensure - Person.connection.drop_table :table_with_name_thats_just_ok, - :sequence_name => 'suitably_short_seq' rescue nil - end - end - - # confirm the custom sequence got dropped - assert_raise(ActiveRecord::StatementInvalid) do - Person.connection.execute("select suitably_short_seq.nextval from dual") - end + def test_column_creates_column_with_options + with_change_table do |t| + @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false}) + t.column :bar, :integer, :null => false end - - protected - def with_env_tz(new_tz = 'US/Eastern') - old_tz, ENV['TZ'] = ENV['TZ'], new_tz - yield - ensure - old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') - end - end - class SexyMigrationsTest < ActiveRecord::TestCase - def test_references_column_type_adds_id - with_new_table do |t| - t.expects(:column).with('customer_id', :integer, {}) - t.references :customer - end + def test_index_creates_index + with_change_table do |t| + @connection.expects(:add_index).with(:delete_me, :bar, {}) + t.index :bar end + end - def test_references_column_type_with_polymorphic_adds_type - with_new_table do |t| - t.expects(:column).with('taggable_type', :string, {}) - t.expects(:column).with('taggable_id', :integer, {}) - t.references :taggable, :polymorphic => true - end + def test_index_creates_index_with_options + with_change_table do |t| + @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true}) + t.index :bar, :unique => true end + end - def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag - with_new_table do |t| - t.expects(:column).with('taggable_type', :string, {:null => false}) - t.expects(:column).with('taggable_id', :integer, {:null => false}) - t.references :taggable, :polymorphic => true, :null => false - end + def test_index_exists + with_change_table do |t| + @connection.expects(:index_exists?).with(:delete_me, :bar, {}) + t.index_exists?(:bar) end + end - def test_belongs_to_works_like_references - with_new_table do |t| - t.expects(:column).with('customer_id', :integer, {}) - t.belongs_to :customer - end + def test_index_exists_with_options + with_change_table do |t| + @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true}) + t.index_exists?(:bar, :unique => true) end + end - def test_timestamps_creates_updated_at_and_created_at - with_new_table do |t| - t.expects(:column).with(:created_at, :datetime, kind_of(Hash)) - t.expects(:column).with(:updated_at, :datetime, kind_of(Hash)) - t.timestamps - end + def test_change_changes_column + with_change_table do |t| + @connection.expects(:change_column).with(:delete_me, :bar, :string, {}) + t.change :bar, :string end + end - def test_integer_creates_integer_column - with_new_table do |t| - t.expects(:column).with(:foo, 'integer', {}) - t.expects(:column).with(:bar, 'integer', {}) - t.integer :foo, :bar - end + def test_change_changes_column_with_options + with_change_table do |t| + @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true}) + t.change :bar, :string, :null => true end + end - def test_string_creates_string_column - with_new_table do |t| - t.expects(:column).with(:foo, 'string', {}) - t.expects(:column).with(:bar, 'string', {}) - t.string :foo, :bar - end + def test_change_default_changes_column + with_change_table do |t| + @connection.expects(:change_column_default).with(:delete_me, :bar, :string) + t.change_default :bar, :string end + end - if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) || current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter) - def test_xml_creates_xml_column - type = current_adapter?(:PostgreSQLAdapter) ? 'xml' : :text - - with_new_table do |t| - t.expects(:column).with(:data, type, {}) - t.xml :data - end - end - else - def test_xml_creates_xml_column - with_new_table do |t| - assert_raises(NotImplementedError) do - t.xml :data - end - end - end + def test_remove_drops_single_column + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, [:bar]) + t.remove :bar end + end - protected - def with_new_table - Person.connection.create_table :delete_me, :force => true do |t| - yield t - end - ensure - Person.connection.drop_table :delete_me rescue nil + def test_remove_drops_multiple_columns + with_change_table do |t| + @connection.expects(:remove_column).with(:delete_me, [:bar, :baz]) + t.remove :bar, :baz end + end - end # SexyMigrationsTest - - class MigrationLoggerTest < ActiveRecord::TestCase - def test_migration_should_be_run_without_logger - previous_logger = ActiveRecord::Base.logger - ActiveRecord::Base.logger = nil - assert_nothing_raised do - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") - end - ensure - ActiveRecord::Base.logger = previous_logger + def test_remove_index_removes_index_with_options + with_change_table do |t| + @connection.expects(:remove_index).with(:delete_me, {:unique => true}) + t.remove_index :unique => true end end - class InterleavedMigrationsTest < ActiveRecord::TestCase - def test_migrator_interleaved_migrations - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") - - assert_nothing_raised do - ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2") - end - - Person.reset_column_information - assert Person.column_methods_hash.include?(:last_name) - - assert_nothing_raised do - proxies = ActiveRecord::Migrator.down( - MIGRATIONS_ROOT + "/interleaved/pass_3") - names = proxies.map(&:name) - assert names.include?('InterleavedPeopleHaveLastNames') - assert names.include?('InterleavedInnocentJointable') - end + def test_rename_renames_column + with_change_table do |t| + @connection.expects(:rename_column).with(:delete_me, :bar, :baz) + t.rename :bar, :baz end end - class ReservedWordsMigrationTest < ActiveRecord::TestCase - def test_drop_index_from_table_named_values - connection = Person.connection - connection.create_table :values, :force => true do |t| - t.integer :value - end - - assert_nothing_raised do - connection.add_index :values, :value - connection.remove_index :values, :column => :value - end - - connection.drop_table :values rescue nil + protected + def with_change_table + Person.connection.change_table :delete_me do |t| + yield t end end +end - - class ChangeTableMigrationsTest < ActiveRecord::TestCase +if ActiveRecord::Base.connection.supports_bulk_alter? + class BulkAlterTableMigrationsTest < ActiveRecord::TestCase def setup @connection = Person.connection - @connection.create_table :delete_me, :force => true do |t| - end + @connection.create_table(:delete_me, :force => true) {|t| } end def teardown - Person.connection.drop_table :delete_me rescue nil + Person.connection.drop_table(:delete_me) rescue nil end - def test_references_column_type_adds_id - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) - t.references :customer + def test_adding_multiple_columns + assert_queries(1) do + with_bulk_change_table do |t| + t.column :name, :string + t.string :qualification, :experience + t.integer :age, :default => 0 + t.date :birthdate + t.timestamps + end end - end - def test_remove_references_column_type_removes_id - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'customer_id') - t.remove_references :customer - end + assert_equal 8, columns.size + [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } + assert_equal 0, column(:age).default end - def test_add_belongs_to_works_like_add_references - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {}) - t.belongs_to :customer + def test_removing_columns + with_bulk_change_table do |t| + t.string :qualification, :experience end - end - def test_remove_belongs_to_works_like_remove_references - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'customer_id') - t.remove_belongs_to :customer - end - end - - def test_references_column_type_with_polymorphic_adds_type - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {}) - @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {}) - t.references :taggable, :polymorphic => true - end - end + [:qualification, :experience].each {|c| assert column(c) } - def test_remove_references_column_type_with_polymorphic_removes_type - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'taggable_type') - @connection.expects(:remove_column).with(:delete_me, 'taggable_id') - t.remove_references :taggable, :polymorphic => true + assert_queries(1) do + with_bulk_change_table do |t| + t.remove :qualification, :experience + t.string :qualification_experience + end end - end - def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false}) - @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false}) - t.references :taggable, :polymorphic => true, :null => false - end + [:qualification, :experience].each {|c| assert ! column(c) } + assert column(:qualification_experience) end - def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, 'taggable_type') - @connection.expects(:remove_column).with(:delete_me, 'taggable_id') - t.remove_references :taggable, :polymorphic => true, :null => false + def test_adding_indexes + with_bulk_change_table do |t| + t.string :username + t.string :name + t.integer :age end - end - def test_timestamps_creates_updated_at_and_created_at - with_change_table do |t| - @connection.expects(:add_timestamps).with(:delete_me) - t.timestamps + # Adding an index fires a query every time to check if an index already exists or not + assert_queries(3) do + with_bulk_change_table do |t| + t.index :username, :unique => true, :name => :awesome_username_index + t.index [:name, :age] + end end - end - def test_remove_timestamps_creates_updated_at_and_created_at - with_change_table do |t| - @connection.expects(:remove_timestamps).with(:delete_me) - t.remove_timestamps - end - end + assert_equal 2, indexes.size - def string_column - if current_adapter?(:PostgreSQLAdapter) - "character varying(255)" - elsif current_adapter?(:OracleAdapter) - 'VARCHAR2(255)' - else - 'varchar(255)' - end - end + name_age_index = index(:index_delete_me_on_name_and_age) + assert_equal ['name', 'age'].sort, name_age_index.columns.sort + assert ! name_age_index.unique - def integer_column - if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) - 'int(11)' - elsif current_adapter?(:OracleAdapter) - 'NUMBER(38)' - else - 'integer' - end + assert index(:awesome_username_index).unique end - def test_integer_creates_integer_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {}) - @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {}) - t.integer :foo, :bar + def test_removing_index + with_bulk_change_table do |t| + t.string :name + t.index :name end - end - def test_string_creates_string_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :foo, string_column, {}) - @connection.expects(:add_column).with(:delete_me, :bar, string_column, {}) - t.string :foo, :bar - end - end + assert index(:index_delete_me_on_name) - def test_column_creates_column - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :bar, :integer, {}) - t.column :bar, :integer + assert_queries(3) do + with_bulk_change_table do |t| + t.remove_index :name + t.index :name, :name => :new_name_index, :unique => true + end end - end - def test_column_creates_column_with_options - with_change_table do |t| - @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false}) - t.column :bar, :integer, :null => false - end - end + assert ! index(:index_delete_me_on_name) - def test_index_creates_index - with_change_table do |t| - @connection.expects(:add_index).with(:delete_me, :bar, {}) - t.index :bar - end + new_name_index = index(:new_name_index) + assert new_name_index.unique end - def test_index_creates_index_with_options - with_change_table do |t| - @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true}) - t.index :bar, :unique => true + def test_changing_columns + with_bulk_change_table do |t| + t.string :name + t.date :birthdate end - end - def test_index_exists - with_change_table do |t| - @connection.expects(:index_exists?).with(:delete_me, :bar, {}) - t.index_exists?(:bar) - end - end + assert ! column(:name).default + assert_equal :date, column(:birthdate).type - def test_index_exists_with_options - with_change_table do |t| - @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true}) - t.index_exists?(:bar, :unique => true) + # 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) do + with_bulk_change_table do |t| + t.change :name, :string, :default => 'NONAME' + t.change :birthdate, :datetime + end end - end - def test_change_changes_column - with_change_table do |t| - @connection.expects(:change_column).with(:delete_me, :bar, :string, {}) - t.change :bar, :string - end + assert_equal 'NONAME', column(:name).default + assert_equal :datetime, column(:birthdate).type end - def test_change_changes_column_with_options - with_change_table do |t| - @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true}) - t.change :bar, :string, :null => true - end - end + protected - def test_change_default_changes_column - with_change_table do |t| - @connection.expects(:change_column_default).with(:delete_me, :bar, :string) - t.change_default :bar, :string - end - end + def with_bulk_change_table + # Reset columns/indexes cache as we're changing the table + @columns = @indexes = nil - def test_remove_drops_single_column - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, [:bar]) - t.remove :bar + Person.connection.change_table(:delete_me, :bulk => true) do |t| + yield t end end - def test_remove_drops_multiple_columns - with_change_table do |t| - @connection.expects(:remove_column).with(:delete_me, [:bar, :baz]) - t.remove :bar, :baz - end + def column(name) + columns.detect {|c| c.name == name.to_s } end - def test_remove_index_removes_index_with_options - with_change_table do |t| - @connection.expects(:remove_index).with(:delete_me, {:unique => true}) - t.remove_index :unique => true - end + def columns + @columns ||= Person.connection.columns('delete_me') end - def test_rename_renames_column - with_change_table do |t| - @connection.expects(:rename_column).with(:delete_me, :bar, :baz) - t.rename :bar, :baz - end + def index(name) + indexes.detect {|i| i.name == name.to_s } end - protected - def with_change_table - Person.connection.change_table :delete_me do |t| - yield t - end + def indexes + @indexes ||= Person.connection.indexes('delete_me') end - end - - if ActiveRecord::Base.connection.supports_bulk_alter? - class BulkAlterTableMigrationsTest < ActiveRecord::TestCase - def setup - @connection = Person.connection - @connection.create_table(:delete_me, :force => true) {|t| } - end - - def teardown - Person.connection.drop_table(:delete_me) rescue nil - end - - def test_adding_multiple_columns - assert_queries(1) do - with_bulk_change_table do |t| - t.column :name, :string - t.string :qualification, :experience - t.integer :age, :default => 0 - t.date :birthdate - t.timestamps - end - end - - assert_equal 8, columns.size - [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type } - assert_equal 0, column(:age).default - end - - def test_removing_columns - with_bulk_change_table do |t| - t.string :qualification, :experience - end - - [:qualification, :experience].each {|c| assert column(c) } - - assert_queries(1) do - with_bulk_change_table do |t| - t.remove :qualification, :experience - t.string :qualification_experience - end - end - - [:qualification, :experience].each {|c| assert ! column(c) } - assert column(:qualification_experience) - end - - def test_adding_indexes - with_bulk_change_table do |t| - t.string :username - t.string :name - 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 - with_bulk_change_table do |t| - t.index :username, :unique => true, :name => :awesome_username_index - t.index [:name, :age] - end - end - - assert_equal 2, indexes.size - - name_age_index = index(:index_delete_me_on_name_and_age) - assert_equal ['name', 'age'].sort, name_age_index.columns.sort - assert ! name_age_index.unique - - assert index(:awesome_username_index).unique - end - - def test_removing_index - with_bulk_change_table do |t| - t.string :name - t.index :name - end - - assert index(:index_delete_me_on_name) - - assert_queries(3) do - with_bulk_change_table do |t| - t.remove_index :name - t.index :name, :name => :new_name_index, :unique => true - end - end - - assert ! index(:index_delete_me_on_name) - - new_name_index = index(:new_name_index) - assert new_name_index.unique - end - - def test_changing_columns - with_bulk_change_table do |t| - t.string :name - t.date :birthdate - end - - 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) do - with_bulk_change_table do |t| - t.change :name, :string, :default => 'NONAME' - t.change :birthdate, :datetime - end - end - - assert_equal 'NONAME', column(:name).default - assert_equal :datetime, column(:birthdate).type - end - - protected - - def with_bulk_change_table - # Reset columns/indexes cache as we're changing the table - @columns = @indexes = nil - - Person.connection.change_table(:delete_me, :bulk => true) do |t| - yield t - end - end + end # AlterTableMigrationsTest - def column(name) - columns.detect {|c| c.name == name.to_s } - end - - def columns - @columns ||= Person.connection.columns('delete_me') - end - - def index(name) - indexes.detect {|i| i.name == name.to_s } - end +end - def indexes - @indexes ||= Person.connection.indexes('delete_me') - end - end # AlterTableMigrationsTest +class CopyMigrationsTest < ActiveRecord::TestCase + def setup + end + def clear + ActiveRecord::Base.timestamped_migrations = true + to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations + File.delete(*to_delete) end - class CopyMigrationsTest < ActiveRecord::TestCase - def setup - end + def test_copying_migrations_without_timestamps + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") + assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename) + + expected = "# This migration comes from bukkits (originally 1)" + assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp + + 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? + ensure + clear + end - def clear - ActiveRecord::Base.timestamped_migrations = true - to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations - File.delete(*to_delete) - end + def test_copying_migrations_without_timestamps_from_2_sources + ActiveRecord::Base.timestamped_migrations = false + @migrations_path = MIGRATIONS_ROOT + "/valid" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy2" + ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb") + assert File.exists?(@migrations_path + "/6_create_articles.omg.rb") + assert File.exists?(@migrations_path + "/7_create_comments.omg.rb") + + files_count = Dir[@migrations_path + "/*.rb"].length + ActiveRecord::Migration.copy(@migrations_path, sources) + assert_equal files_count, Dir[@migrations_path + "/*.rb"].length + ensure + clear + end - def test_copying_migrations_without_timestamps - ActiveRecord::Base.timestamped_migrations = false - @migrations_path = MIGRATIONS_ROOT + "/valid" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) - assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb") - assert_equal [@migrations_path + "/4_people_have_hobbies.rb", @migrations_path + "/5_people_have_descriptions.rb"], copied.map(&:filename) + Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb", + @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"] + assert_equal expected, copied.map(&:filename) files_count = Dir[@migrations_path + "/*.rb"].length - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"}) + 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? - ensure - clear end + ensure + clear + end - def test_copying_migrations_without_timestamps_from_2_sources - ActiveRecord::Base.timestamped_migrations = false - @migrations_path = MIGRATIONS_ROOT + "/valid" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps_from_2_sources + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy" - sources[:omg] = MIGRATIONS_ROOT + "/to_copy2" - ActiveRecord::Migration.copy(@migrations_path, sources) - assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb") - assert File.exists?(@migrations_path + "/6_create_articles.rb") - assert File.exists?(@migrations_path + "/7_create_comments.rb") + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2" + + Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, sources) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101012_create_articles.omg.rb") + assert File.exists?(@migrations_path + "/20100726101013_create_comments.omg.rb") + assert_equal 4, copied.length files_count = Dir[@migrations_path + "/*.rb"].length ActiveRecord::Migration.copy(@migrations_path, sources) assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - ensure - clear end + ensure + clear + end - def test_copying_migrations_with_timestamps - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - expected = [@migrations_path + "/20100726101010_people_have_hobbies.rb", - @migrations_path + "/20100726101011_people_have_descriptions.rb"] - assert_equal expected, copied.map(&:filename) + Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do + ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb") - 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? - end - ensure - clear + 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? end + ensure + clear + end - def test_copying_migrations_with_timestamps_from_2_sources - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] - - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" - sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2" - - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, sources) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert File.exists?(@migrations_path + "/20100726101012_create_articles.rb") - assert File.exists?(@migrations_path + "/20100726101013_create_comments.rb") - assert_equal 4, copied.length - - files_count = Dir[@migrations_path + "/*.rb"].length - ActiveRecord::Migration.copy(@migrations_path, sources) - assert_equal files_count, Dir[@migrations_path + "/*.rb"].length - end - ensure - clear - end + def test_skipping_migrations + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] + + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision" + + skipped = [] + on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } + copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + assert_equal 2, copied.length + + assert_equal 1, skipped.length + assert_equal ["omg PeopleHaveHobbies"], skipped + ensure + clear + end - def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + def test_skip_is_not_called_if_migrations_are_from_the_same_plugin + @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" + @existing_migrations = Dir[@migrations_path + "/*.rb"] - Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do - ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.rb") + sources = {} + sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" - 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? - end - ensure - clear - end + skipped = [] + on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } + copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) - def test_skipping_migrations - @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" - @existing_migrations = Dir[@migrations_path + "/*.rb"] + assert_equal 2, copied.length + assert_equal 0, skipped.length + ensure + clear + end - sources = ActiveSupport::OrderedHash.new - sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" + def test_copying_migrations_to_non_existing_directory + @migrations_path = MIGRATIONS_ROOT + "/non_existing" + @existing_migrations = [] - skipped = [] - on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" } - copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip) + Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") assert_equal 2, copied.length - - assert_equal 2, skipped.length - assert_equal ["bukkits PeopleHaveHobbies", "bukkits PeopleHaveDescriptions"], skipped - ensure - clear - end - - def test_copying_migrations_to_non_existing_directory - @migrations_path = MIGRATIONS_ROOT + "/non_existing" - @existing_migrations = [] - - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert_equal 2, copied.length - end - ensure - clear - Dir.delete(@migrations_path) end + ensure + clear + Dir.delete(@migrations_path) + end - def test_copying_migrations_to_empty_directory - @migrations_path = MIGRATIONS_ROOT + "/empty" - @existing_migrations = [] + def test_copying_migrations_to_empty_directory + @migrations_path = MIGRATIONS_ROOT + "/empty" + @existing_migrations = [] - Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do - copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) - assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb") - assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb") - assert_equal 2, copied.length - end - ensure - clear + Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do + copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"}) + assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert_equal 2, copied.length end + ensure + clear end end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb new file mode 100644 index 0000000000..1e16addcf3 --- /dev/null +++ b/activerecord/test/cases/migrator_test.rb @@ -0,0 +1,377 @@ +require "cases/helper" +require "cases/migration/helper" + +module ActiveRecord + class MigratorTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + # Use this class to sense if migrations have gone + # up or down. + class Sensor < ActiveRecord::Migration + attr_reader :went_up, :went_down + + def initialize name = self.class.name, version = nil + super + @went_up = false + @went_down = false + end + + def up; @went_up = true; end + def down; @went_down = true; end + end + + def setup + super + ActiveRecord::SchemaMigration.create_table + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + def teardown + super + ActiveRecord::SchemaMigration.delete_all rescue nil + end + + def test_migrator_with_duplicate_names + assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do + list = [Migration.new('Chunky'), Migration.new('Chunky')] + ActiveRecord::Migrator.new(:up, list) + end + end + + def test_migrator_with_duplicate_versions + assert_raises(ActiveRecord::DuplicateMigrationVersionError) do + list = [Migration.new('Foo', 1), Migration.new('Bar', 1)] + ActiveRecord::Migrator.new(:up, list) + end + end + + def test_migrator_with_missing_version_numbers + assert_raises(ActiveRecord::UnknownMigrationVersionError) do + list = [Migration.new('Foo', 1), Migration.new('Bar', 2)] + ActiveRecord::Migrator.new(:up, list, 3).run + end + end + + def test_finds_migrations + migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid") + + [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + assert_equal migrations[i].version, pair.first + assert_equal migrations[i].name, pair.last + end + end + + def test_finds_migrations_in_subdirectories + migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories") + + [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + assert_equal migrations[i].version, pair.first + assert_equal migrations[i].name, pair.last + end + end + + def test_finds_migrations_from_two_directories + directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps'] + migrations = ActiveRecord::Migrator.migrations directories + + [[20090101010101, "PeopleHaveHobbies"], + [20090101010202, "PeopleHaveDescriptions"], + [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"], + [20100201010101, "ValidWithTimestampsWeNeedReminders"], + [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i| + assert_equal pair.first, migrations[i].version + assert_equal pair.last, migrations[i].name + end + end + + def test_deprecated_constructor + assert_deprecated do + ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid") + end + end + + def test_relative_migrations + list = Dir.chdir(MIGRATIONS_ROOT) do + ActiveRecord::Migrator.migrations("valid/") + end + + migration_proxy = list.find { |item| + item.name == 'ValidPeopleHaveLastNames' + } + assert migration_proxy, 'should find pending migration' + end + + def test_finds_pending_migrations + ActiveRecord::SchemaMigration.create!(:version => '1') + migration_list = [ Migration.new('foo', 1), Migration.new('bar', 3) ] + migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations + + assert_equal 1, migrations.size + assert_equal migration_list.last, migrations.first + end + + def test_migrator_interleaved_migrations + pass_one = [Sensor.new('One', 1)] + + ActiveRecord::Migrator.new(:up, pass_one).migrate + assert pass_one.first.went_up + refute pass_one.first.went_down + + pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)] + ActiveRecord::Migrator.new(:up, pass_two).migrate + refute pass_two[0].went_up + assert pass_two[1].went_up + assert pass_two.all? { |x| !x.went_down } + + pass_three = [Sensor.new('One', 1), + Sensor.new('Two', 2), + Sensor.new('Three', 3)] + + ActiveRecord::Migrator.new(:down, pass_three).migrate + assert pass_three[0].went_down + refute pass_three[1].went_down + assert pass_three[2].went_down + end + + def test_up_calls_up + migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)] + ActiveRecord::Migrator.new(:up, migrations).migrate + assert migrations.all? { |m| m.went_up } + assert migrations.all? { |m| !m.went_down } + assert_equal 2, ActiveRecord::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 + assert migrations.all? { |m| !m.went_up } + assert migrations.all? { |m| m.went_down } + assert_equal 0, ActiveRecord::Migrator.current_version + end + + def test_current_version + ActiveRecord::SchemaMigration.create!(:version => '1000') + assert_equal 1000, ActiveRecord::Migrator.current_version + end + + def test_migrator_one_up + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:up, migrations, 2).migrate + assert_equal [[:up, 2]], calls + end + + def test_migrator_one_down + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations).migrate + assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).migrate + + assert_equal [[:down, 3], [:down, 2]], calls + end + + def test_migrator_one_up_one_down + calls, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [[:down, 1]], calls + end + + def test_migrator_double_up + calls, migrations = sensors(3) + assert_equal(0, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [], calls + end + + def test_migrator_double_down + calls, migrations = sensors(3) + + assert_equal(0, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.new(:up, migrations, 1).run + assert_equal [[:up, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).run + assert_equal [[:down, 1]], calls + calls.clear + + ActiveRecord::Migrator.new(:down, migrations, 1).run + assert_equal [], calls + + assert_equal(0, ActiveRecord::Migrator.current_version) + end + + def test_migrator_verbosity + _, migrations = sensors(3) + + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_not_equal 0, ActiveRecord::Migration.message_count + + ActiveRecord::Migration.message_count = 0 + + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_not_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migration.message_count = 0 + end + + def test_migrator_verbosity_off + _, migrations = sensors(3) + + ActiveRecord::Migration.message_count = 0 + ActiveRecord::Migration.verbose = false + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal 0, ActiveRecord::Migration.message_count + end + + def test_target_version_zero_should_run_only_once + calls, migrations = sensors(3) + + # migrate up to 1 + ActiveRecord::Migrator.new(:up, migrations, 1).migrate + assert_equal [[:up, 1]], calls + calls.clear + + # migrate down to 0 + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [[:down, 1]], calls + calls.clear + + # migrate down to 0 again + ActiveRecord::Migrator.new(:down, migrations, 0).migrate + assert_equal [], calls + end + + def test_migrator_going_down_due_to_version_target + calls, migrator = migrator_class(3) + + migrator.up("valid", 1) + assert_equal [[:up, 1]], calls + calls.clear + + migrator.migrate("valid", 0) + assert_equal [[:down, 1]], calls + calls.clear + + migrator.migrate("valid") + assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls + end + + def test_migrator_rollback + _, migrator = migrator_class(3) + + migrator.migrate("valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(2, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(1, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(0, ActiveRecord::Migrator.current_version) + + migrator.rollback("valid") + assert_equal(0, ActiveRecord::Migrator.current_version) + end + + def test_migrator_db_has_no_schema_migrations_table + _, migrator = migrator_class(3) + + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + refute ActiveRecord::Base.connection.table_exists?('schema_migrations') + migrator.migrate("valid", 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.forward("/valid", 2) + assert_equal(3, ActiveRecord::Migrator.current_version) + + migrator.forward("/valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + end + + def test_only_loads_pending_migrations + # migrate up to 1 + ActiveRecord::SchemaMigration.create!(:version => '1') + + calls, migrator = migrator_class(3) + migrator.migrate("valid", nil) + + assert_equal [[:up, 2], [:up, 3]], calls + end + + def test_get_all_versions + _, migrator = migrator_class(3) + + migrator.migrate("valid") + assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([1,2], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([1], ActiveRecord::Migrator.get_all_versions) + + migrator.rollback("valid") + assert_equal([], ActiveRecord::Migrator.get_all_versions) + end + + private + def m(name, version, &block) + x = Sensor.new name, version + x.extend(Module.new { + define_method(:up) { block.call(:up, x); super() } + define_method(:down) { block.call(:down, x); super() } + }) if block_given? + end + + def sensors(count) + calls = [] + migrations = count.times.map { |i| + m(nil, i + 1) { |c,migration| + calls << [c, migration.version] + } + } + [calls, migrations] + end + + def migrator_class(count) + calls, migrations = sensors(count) + + migrator = Class.new(Migrator).extend(Module.new { + define_method(:migrations) { |paths| + migrations + } + }) + [calls, migrator] + end + end +end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index a2041af16a..f7a5d05582 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -72,7 +72,7 @@ class ModulesTest < ActiveRecord::TestCase clients = [] assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do - clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL', :references => :accounts) clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index bd51388e05..a802cfbf31 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -10,6 +10,7 @@ class MultipleDbTest < ActiveRecord::TestCase def setup @courses = create_fixtures("courses") { Course.retrieve_connection } + @colleges = create_fixtures("colleges") { College.retrieve_connection } @entrants = create_fixtures("entrants") end @@ -85,7 +86,17 @@ class MultipleDbTest < ActiveRecord::TestCase end def test_arel_table_engines - assert_not_equal Entrant.arel_engine, Course.arel_engine assert_equal Entrant.arel_engine, Bird.arel_engine end + + def test_associations_should_work_when_model_has_no_connection + begin + ActiveRecord::Model.remove_connection + assert_nothing_raised ActiveRecord::ConnectionNotEstablished do + College.first.courses.first + end + ensure + ActiveRecord::Model.establish_connection 'arunit' + end + end end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 4a09a87322..e17ba76437 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require 'active_support/core_ext/array/random_access' require 'models/post' require 'models/topic' require 'models/comment' @@ -337,6 +336,11 @@ class NamedScopeTest < ActiveRecord::TestCase end end + def test_should_not_duplicates_where_values + where_values = Topic.where("1=1").scope_with_lambda.where_values + assert_equal ["1=1"], where_values + end + def test_chaining_with_duplicate_joins join = "INNER JOIN comments ON comments.post_id = posts.id" post = Post.find(1) diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 434b8a677a..fba3006ebe 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -7,17 +7,17 @@ class PooledConnectionsTest < ActiveRecord::TestCase def setup @per_test_teardown = [] - @connection = ActiveRecord::Base.remove_connection + @connection = ActiveRecord::Model.remove_connection end def teardown - ActiveRecord::Base.clear_all_connections! - ActiveRecord::Base.establish_connection(@connection) + ActiveRecord::Model.clear_all_connections! + ActiveRecord::Model.establish_connection(@connection) @per_test_teardown.each {|td| td.call } end def checkout_connections - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3})) @connections = [] @timed_out = 0 @@ -33,24 +33,16 @@ class PooledConnectionsTest < ActiveRecord::TestCase end # Will deadlock due to lack of Monitor timeouts in 1.9 - if RUBY_VERSION < '1.9' - def test_pooled_connection_checkout - checkout_connections - assert_equal 2, @connections.length - assert_equal 2, @timed_out - end - end - def checkout_checkin_connections(pool_size, threads) - ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) + ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5})) @connection_count = 0 @timed_out = 0 threads.times do Thread.new do begin - conn = ActiveRecord::Base.connection_pool.checkout + conn = ActiveRecord::Model.connection_pool.checkout sleep 0.1 - ActiveRecord::Base.connection_pool.checkin conn + ActiveRecord::Model.connection_pool.checkin conn @connection_count += 1 rescue ActiveRecord::ConnectionTimeoutError @timed_out += 1 @@ -63,85 +55,13 @@ class PooledConnectionsTest < ActiveRecord::TestCase checkout_checkin_connections 1, 2 assert_equal 2, @connection_count assert_equal 0, @timed_out - assert_equal 1, ActiveRecord::Base.connection_pool.connections.size + assert_equal 1, ActiveRecord::Model.connection_pool.connections.size end - def test_pooled_connection_checkin_two - checkout_checkin_connections 2, 3 - assert_equal 3, @connection_count - assert_equal 0, @timed_out - assert_equal 1, ActiveRecord::Base.connection_pool.connections.size - end - - def test_pooled_connection_checkout_existing_first - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1})) - conn_pool = ActiveRecord::Base.connection_pool - conn = conn_pool.checkout - conn_pool.checkin(conn) - conn = conn_pool.checkout - assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn - conn_pool.checkin(conn) - end - - def test_not_connected_defined_connection_returns_false - ActiveRecord::Base.establish_connection(@connection) - assert ! ActiveRecord::Base.connected? - end - - def test_undefined_connection_returns_false - old_handler = ActiveRecord::Base.connection_handler - ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new - assert ! ActiveRecord::Base.connected? - ensure - ActiveRecord::Base.connection_handler = old_handler - end - - def test_connection_config - ActiveRecord::Base.establish_connection(@connection) - assert_equal @connection, ActiveRecord::Base.connection_config - end - - def test_with_connection_nesting_safety - ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1, :wait_timeout => 0.1})) - - before_count = Project.count - - add_record('one') - - ActiveRecord::Base.connection.transaction do - add_record('two') - # Have another thread try to screw up the transaction - Thread.new do - ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.connection_pool.release_connection - end - add_record('three') - end - - after_count = Project.count - assert_equal 3, after_count - before_count - end - - def test_connection_pool_callbacks - checked_out, checked_in = false, false - ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do - set_callback(:checkout, :after) { checked_out = true } - set_callback(:checkin, :before) { checked_in = true } - end - @per_test_teardown << proc do - ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do - reset_callbacks :checkout - reset_callbacks :checkin - end - end - checkout_checkin_connections 1, 1 - assert checked_out - assert checked_in - end private def add_record(name) - ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name } + ActiveRecord::Model.connection_pool.with_connection { Project.create! :name => name } end end unless current_adapter?(:FrontBase) || in_memory_db? diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 4bb5752096..e6e50a4cd4 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -23,6 +23,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal keyboard.to_key, [keyboard.id] end + def test_read_attribute_with_custom_primary_key + keyboard = Keyboard.create! + assert_equal keyboard.key_number, keyboard.read_attribute(:id) + end + def test_to_key_with_primary_key_after_destroy topic = Topic.find(1) topic.destroy @@ -142,8 +147,38 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key k.primary_key = "foo" assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key - k.set_primary_key "bar" - assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key + end + + def test_two_models_with_same_table_but_different_primary_key + k1 = Class.new(ActiveRecord::Base) + k1.table_name = 'posts' + k1.primary_key = 'id' + + k2 = Class.new(ActiveRecord::Base) + k2.table_name = 'posts' + k2.primary_key = 'title' + + assert k1.columns.find { |c| c.name == 'id' }.primary + assert !k1.columns.find { |c| c.name == 'title' }.primary + assert k1.columns_hash['id'].primary + assert !k1.columns_hash['title'].primary + + assert !k2.columns.find { |c| c.name == 'id' }.primary + assert k2.columns.find { |c| c.name == 'title' }.primary + assert !k2.columns_hash['id'].primary + assert k2.columns_hash['title'].primary + end + + def test_models_with_same_table_have_different_columns + k1 = Class.new(ActiveRecord::Base) + k1.table_name = 'posts' + + k2 = Class.new(ActiveRecord::Base) + k2.table_name = 'posts' + + k1.columns.zip(k2.columns).each do |col1, col2| + assert !col1.equal?(col2) + end end end @@ -153,16 +188,31 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase def test_set_primary_key_with_no_connection return skip("disconnect wipes in-memory db") if in_memory_db? - connection = ActiveRecord::Base.remove_connection + connection = ActiveRecord::Model.remove_connection - model = Class.new(ActiveRecord::Base) do - set_primary_key 'foo' - end + model = Class.new(ActiveRecord::Base) + model.primary_key = 'foo' assert_equal 'foo', model.primary_key - ActiveRecord::Base.establish_connection(connection) + ActiveRecord::Model.establish_connection(connection) assert_equal 'foo', model.primary_key end end + +if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) + class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + def test_primaery_key_method_with_ansi_quotes + con = ActiveRecord::Base.connection + con.execute("SET SESSION sql_mode='ANSI_QUOTES'") + assert_equal "id", con.primary_key("topics") + ensure + con.reconnect! + end + + end +end + diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 9554386dcf..f36f4237b1 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -3,7 +3,7 @@ require 'models/topic' require 'models/task' require 'models/category' require 'models/post' - +require 'rack' class QueryCacheTest < ActiveRecord::TestCase fixtures :tasks, :topics, :categories, :posts, :categories_posts @@ -43,6 +43,7 @@ class QueryCacheTest < ActiveRecord::TestCase called = false mw = ActiveRecord::QueryCache.new lambda { |env| called = true + [200, {}, nil] } mw.call({}) assert called, 'middleware should delegate' @@ -53,6 +54,7 @@ class QueryCacheTest < ActiveRecord::TestCase Task.find 1 Task.find 1 assert_equal 1, ActiveRecord::Base.connection.query_cache.length + [200, {}, nil] } mw.call({}) end @@ -62,6 +64,7 @@ class QueryCacheTest < ActiveRecord::TestCase mw = ActiveRecord::QueryCache.new lambda { |env| assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + [200, {}, nil] } mw.call({}) end @@ -83,7 +86,7 @@ class QueryCacheTest < ActiveRecord::TestCase end def test_cache_off_after_close - mw = ActiveRecord::QueryCache.new lambda { |env| } + mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] } body = mw.call({}).last assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' @@ -94,6 +97,7 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_clear_after_close mw = ActiveRecord::QueryCache.new lambda { |env| Post.find(:first) + [200, {}, nil] } body = mw.call({}).last @@ -244,14 +248,3 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end - -class QueryCacheBodyProxyTest < ActiveRecord::TestCase - - test "is polite to it's body and responds to it" do - body = Class.new(String) { def to_path; "/path"; end }.new - proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id) - assert proxy.respond_to?(:to_path) - assert_equal proxy.to_path, "/path" - end - -end diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb new file mode 100644 index 0000000000..576ab60090 --- /dev/null +++ b/activerecord/test/cases/reaper_test.rb @@ -0,0 +1,81 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class ReaperTest < ActiveRecord::TestCase + attr_reader :pool + + def setup + super + @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec + end + + def teardown + super + @pool.connections.each(&:close) + end + + class FakePool + attr_reader :reaped + + def initialize + @reaped = false + end + + def reap + @reaped = true + end + end + + # A reaper with nil time should never reap connections + def test_nil_time + fp = FakePool.new + assert !fp.reaped + reaper = ConnectionPool::Reaper.new(fp, nil) + reaper.run + assert !fp.reaped + end + + def test_some_time + fp = FakePool.new + assert !fp.reaped + + reaper = ConnectionPool::Reaper.new(fp, 0.0001) + reaper.run + until fp.reaped + Thread.pass + end + assert fp.reaped + end + + def test_pool_has_reaper + assert pool.reaper + end + + def test_reaping_frequency_configuration + spec = ActiveRecord::Base.connection_pool.spec.dup + spec.config[:reaping_frequency] = 100 + pool = ConnectionPool.new spec + assert_equal 100, pool.reaper.frequency + end + + def test_connection_pool_starts_reaper + spec = ActiveRecord::Base.connection_pool.spec.dup + spec.config[:reaping_frequency] = 0.0001 + + pool = ConnectionPool.new spec + pool.timeout = 0 + + conn = pool.checkout + count = pool.connections.length + + conn.extend(Module.new { def active?; false; end; }) + + while count == pool.connections.length + Thread.pass + end + assert_equal(count - 1, pool.connections.length) + end + end + end +end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 69e9fc8d61..16f05f2198 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -36,25 +36,25 @@ class ReflectionTest < ActiveRecord::TestCase def test_read_attribute_names assert_equal( - %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort, + %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names.sort ) end def test_columns - assert_equal 16, Topic.columns.length + assert_equal 17, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } - assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names + assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns content_column_names = content_columns.map {|column| column.name} - assert_equal 12, content_columns.length - assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort + assert_equal 13, content_columns.length + assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort end def test_column_string_type_and_limit @@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 36, Firm.reflect_on_all_associations.size - assert_equal 26, Firm.reflect_on_all_associations(:has_many).size + assert_equal 39, Firm.reflect_on_all_associations.size + assert_equal 29, Firm.reflect_on_all_associations(:has_many).size assert_equal 10, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end @@ -319,6 +319,16 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s end + def test_through_reflection_conditions_do_not_modify_other_reflections + orig_conds = Post.reflect_on_association(:first_blue_tags_2).conditions.inspect + Author.reflect_on_association(:misc_post_first_blue_tags_2).conditions + assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).conditions.inspect + end + + def test_symbol_for_class_name + assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass + end + private def assert_reflection(klass, association, options) assert reflection = klass.reflect_on_association(association) diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 1e2093273e..edf38cb7a3 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -106,7 +106,7 @@ class RelationScopingTest < ActiveRecord::TestCase def test_scoped_find_include # with the include, will retrieve only developers for the given project scoped_developers = Developer.includes(:projects).scoping do - Developer.where('projects.id = 2').all + Developer.where('projects.id' => 2).all end assert scoped_developers.include?(developers(:david)) assert !scoped_developers.include?(developers(:jamis)) @@ -421,6 +421,12 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_order_after_reorder_combines_orders + expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] } + received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] } + assert_equal expected, received + end + def test_nested_exclusive_scope expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index b23ead6feb..ac6dee3c6a 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -20,7 +20,7 @@ module ActiveRecord end def test_single_values - assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order].map(&:to_s).sort, + assert_equal [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq].map(&:to_s).sort, Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort end @@ -44,7 +44,7 @@ module ActiveRecord end def test_multi_value_methods - assert_equal [:select, :group, :order, :joins, :where, :having, :bind].map(&:to_s).sort, + assert_equal [:select, :group, :order, :joins, :where, :having, :bind, :references].map(&:to_s).sort, Relation::MULTI_VALUE_METHODS.map(&:to_s).sort end @@ -135,5 +135,24 @@ module ActiveRecord relation.eager_load_values << :b assert relation.eager_loading? end + + def test_references_values + relation = Relation.new :a, :b + 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 :a, :b + relation = relation.references(:foo).references(:foo) + assert_equal ['foo'], relation.references_values + end + + def test_apply_finder_options_takes_references + relation = Relation.new :a, :b + relation = relation.apply_finder_options(:references => :foo) + assert_equal ['foo'], relation.references_values + end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 95408a5f29..7b1d65c6db 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -4,11 +4,11 @@ require 'models/tagging' require 'models/post' require 'models/topic' require 'models/comment' -require 'models/reply' require 'models/author' require 'models/comment' require 'models/entrant' require 'models/developer' +require 'models/reply' require 'models/company' require 'models/bird' require 'models/car' @@ -184,12 +184,12 @@ class RelationTest < ActiveRecord::TestCase end def test_finding_with_complex_order_and_limit - tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a + tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a assert_equal 1, tags.length end def test_finding_with_complex_order - tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a + tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a assert_equal 3, tags.length end @@ -215,6 +215,19 @@ class RelationTest < ActiveRecord::TestCase assert_equal [2, 4, 6, 8, 10], even_ids.sort end + def test_none + assert_no_queries do + assert_equal [], Developer.none + assert_equal [], Developer.scoped.none + end + end + + def test_none_chainable + assert_no_queries do + assert_equal [], Developer.none.where(:name => 'David') + end + end + def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end @@ -1104,7 +1117,9 @@ class RelationTest < ActiveRecord::TestCase ) ) - assert scope.eager_loading? + assert_deprecated do + assert scope.eager_loading? + end end def test_ordering_with_extra_spaces @@ -1148,4 +1163,65 @@ class RelationTest < ActiveRecord::TestCase assert_equal posts(:thinking), comments(:more_greetings).post assert_equal posts(:welcome), comments(:greetings).post end + + def test_uniq + tag1 = Tag.create(:name => 'Foo') + tag2 = Tag.create(:name => 'Foo') + + query = Tag.select(:name).where(:id => [tag1.id, tag2.id]) + + assert_equal ['Foo', 'Foo'], query.map(&:name) + assert_sql(/DISTINCT/) do + assert_equal ['Foo'], query.uniq.map(&:name) + end + assert_sql(/DISTINCT/) do + assert_equal ['Foo'], query.uniq(true).map(&:name) + end + assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name) + end + + def test_references_triggers_eager_loading + scope = Post.includes(:comments) + assert !scope.eager_loading? + assert 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? + end + + def test_automatically_added_where_references + scope = Post.where(:comments => { :body => "Bla" }) + assert_equal ['comments'], scope.references_values + + scope = Post.where('comments.body' => 'Bla') + assert_equal ['comments'], scope.references_values + end + + def test_automatically_added_having_references + scope = Post.having(:comments => { :body => "Bla" }) + assert_equal ['comments'], scope.references_values + + scope = Post.having('comments.body' => 'Bla') + assert_equal ['comments'], scope.references_values + end + + def test_automatically_added_order_references + scope = Post.order('comments.body') + assert_equal ['comments'], scope.references_values + + scope = Post.order('comments.body', 'yaks.body') + assert_equal ['comments', 'yaks'], scope.references_values + + # Don't infer yaks, let's not go down that road again... + scope = Post.order('comments.body, yaks.body') + assert_equal ['comments'], scope.references_values + + scope = Post.order('comments.body asc') + assert_equal ['comments'], scope.references_values + + scope = Post.order('foo(comments.body)') + assert_equal [], scope.references_values + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 71ff727b7f..3314013cd4 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -2,7 +2,13 @@ require "cases/helper" class SchemaDumperTest < ActiveRecord::TestCase + def initialize(*) + super + ActiveRecord::SchemaMigration.create_table + end + def setup + super @stream = StringIO.new end @@ -13,10 +19,18 @@ class SchemaDumperTest < ActiveRecord::TestCase @stream.string end - if "string".encoding_aware? - def test_magic_comment - assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump + def test_dump_schema_information_outputs_lexically_ordered_versions + versions = %w{ 20100101010101 20100201010101 20100301010101 } + versions.reverse.each do |v| + ActiveRecord::SchemaMigration.create!(:version => v) end + + schema_info = ActiveRecord::Base.connection.dump_schema_information + assert_match(/20100201010101.*20100301010101/m, schema_info) + end + + def test_magic_comment + assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump end def test_schema_dump @@ -171,6 +185,15 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition end + def test_schema_dumps_partial_indices + index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip + if current_adapter?(:PostgreSQLAdapter) + assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index", :where => "(rating > 10)"', index_definition + else + assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index"', index_definition + end + end + def test_schema_dump_should_honor_nonstandard_primary_keys output = standard_dump match = output.match(%r{create_table "movies"(.*)do}) @@ -213,6 +236,13 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_hstores_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_hstores"} =~ output + assert_match %r{t.hstore "hash_store", default => ""}, output + end + end + def test_schema_dump_includes_tsvector_shorthand_definition output = standard_dump if %r{create_table "postgresql_tsvectors"} =~ output @@ -238,4 +268,9 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r(:id => false), match[1], "no table id not preserved" assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" end + + def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added + output = standard_dump + assert_match %r{create_table "subscribers", :id => false}, output + end end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index 61b04b3e37..a4c065e667 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => '<strong>ruby</strong>' }, - :alternative_id => nil + :alternative_id => nil, + :id => nil } end @@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_be_reversible - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") contact = Contact.new.send("from_#{format}", @serialized) @@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_only_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_equal @contact_attributes[:name], contact.name, "For #{format}" @@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase end def test_serialize_should_allow_attribute_except_filtering - for format in FORMATS + FORMATS.each do |format| @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ]) contact = Contact.new.send("from_#{format}", @serialized) assert_nil contact.name, "For #{format}" diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb index 258cee7aba..a3b8ab74d9 100644 --- a/activerecord/test/cases/session_store/session_test.rb +++ b/activerecord/test/cases/session_store/session_test.rb @@ -5,11 +5,15 @@ require 'active_record/session_store' module ActiveRecord class SessionStore class SessionTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false unless supports_savepoints? && ActiveRecord::Base.connection.supports_ddl_transactions? + self.use_transactional_fixtures = false + + attr_reader :session_klass def setup super + ActiveRecord::Base.connection.schema_cache.clear! Session.drop_table! if Session.table_exists? + @session_klass = Class.new(Session) end def test_data_column_name @@ -60,8 +64,8 @@ module ActiveRecord def test_find_by_session_id Session.create_table! session_id = "10" - s = Session.create!(:data => 'world', :session_id => session_id) - t = Session.find_by_session_id(session_id) + s = session_klass.create!(:data => 'world', :session_id => session_id) + t = session_klass.find_by_session_id(session_id) assert_equal s, t assert_equal s.data, t.data Session.drop_table! diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 074fd39e65..40520d6da2 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -4,14 +4,14 @@ require 'models/admin/user' class StoreTest < ActiveRecord::TestCase setup do - @john = Admin::User.create(:name => 'John Doe', :color => 'black') + @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true) end test "reading store attributes through accessors" do assert_equal 'black', @john.color assert_nil @john.homepage end - + test "writing store attributes through accessors" do @john.color = 'red' @john.homepage = '37signals.com' @@ -24,11 +24,20 @@ class StoreTest < ActiveRecord::TestCase @john.settings[:icecream] = 'graeters' @john.save - assert 'graeters', @john.reload.settings[:icecream] + assert_equal 'graeters', @john.reload.settings[:icecream] end - + test "updating the store will mark it as changed" do @john.color = 'red' assert @john.settings_changed? end + + test "object initialization with not nullable column" do + assert_equal true, @john.remember_login + end + + test "writing with not nullable column" do + @john.remember_login = false + assert_equal false, @john.remember_login + end end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb new file mode 100644 index 0000000000..94a13d386c --- /dev/null +++ b/activerecord/test/cases/test_case.rb @@ -0,0 +1,10 @@ +require 'active_support/deprecation' +ActiveSupport::Deprecation.silence do + require 'active_record/test_case' +end + +ActiveRecord::TestCase.class_eval do + def sqlite3? connection + connection.class.name.split('::').last == "SQLite3Adapter" + end +end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 85f222bca2..f8b3e01a49 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -6,7 +6,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase fixtures :topics class TopicWithCallbacks < ActiveRecord::Base - set_table_name :topics + self.table_name = :topics after_commit{|record| record.send(:do_after_commit, nil)} after_commit(:on => :create){|record| record.send(:do_after_commit, :create)} @@ -252,7 +252,7 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase fixtures :topics class TopicWithObserverAttached < ActiveRecord::Base - set_table_name :topics + self.table_name = :topics def history @history ||= [] end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 110a18772f..203dd054f1 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -563,6 +563,7 @@ if current_adapter?(:PostgreSQLAdapter) topic.approved = !topic.approved? topic.save! end + Topic.connection.close end end @@ -598,6 +599,7 @@ if current_adapter?(:PostgreSQLAdapter) dev = Developer.find(1) assert_equal original_salary, dev.salary end + Developer.connection.close end end @@ -610,6 +612,7 @@ if current_adapter?(:PostgreSQLAdapter) assert_equal original_salary, Developer.find(1).salary end end + Developer.connection.close end threads.each { |t| t.join } diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index e82ca3f93d..5a69054445 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -7,13 +7,13 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase self.use_transactional_fixtures = false def setup - @underlying = ActiveRecord::Base.connection - @specification = ActiveRecord::Base.remove_connection + @underlying = ActiveRecord::Model.connection + @specification = ActiveRecord::Model.remove_connection end def teardown @underlying = nil - ActiveRecord::Base.establish_connection(@specification) + ActiveRecord::Model.establish_connection(@specification) load_schema if in_memory_db? end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 56e345990f..c72b7b35cd 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -61,6 +61,16 @@ class AssociationValidationTest < ActiveRecord::TestCase assert r.valid? end + def test_validates_associated_marked_for_destruction + Topic.validates_associated(:replies) + Reply.validates_presence_of(:content) + t = Topic.new + t.replies << Reply.new + assert t.invalid? + t.replies.first.mark_for_destruction + assert t.valid? + end + def test_validates_associated_with_custom_message_using_quotes Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes" Topic.validates_presence_of :content @@ -81,14 +91,12 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_size_of_association_utf8 - with_kcode('UTF8') do - assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } - o = Owner.new('name' => 'あいうえおかきくけこ') - assert !o.save - assert o.errors[:pets].any? - o.pets.build('name' => 'あいうえおかきくけこ') - assert o.valid? - end + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'あいうえおかきくけこ') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'あいうえおかきくけこ') + assert o.valid? end def test_validates_presence_of_belongs_to_association__parent_is_new_record @@ -110,4 +118,21 @@ class AssociationValidationTest < ActiveRecord::TestCase end end + def test_validates_associated_models_in_the_same_context + Topic.validates_presence_of :title, :on => :custom_context + Topic.validates_associated :replies + Reply.validates_presence_of :title, :on => :custom_context + + t = Topic.new('title' => '') + r = t.replies.new('title' => '') + + assert t.valid? + assert !t.valid?(:custom_context) + + t.title = "Longer" + assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context." + + r.title = "Longer" + assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context." + end end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 0f1b3667cc..79442d68b0 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -45,6 +45,18 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.save, "Should now save t2 as unique" end + def test_validates_uniqueness_with_nil_value + Topic.validates_uniqueness_of(:title) + + t = Topic.new("title" => nil) + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => nil) + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_equal ["has already been taken"], t2.errors[:title] + end + def test_validates_uniqueness_with_validates Topic.validates :title, :uniqueness => true Topic.create!('title' => 'abc') @@ -80,6 +92,30 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert r3.valid?, "Saving r3" end + def test_validate_uniqueness_with_object_scope + Reply.validates_uniqueness_of(:content, :scope => :topic) + + t = Topic.create("title" => "I'm unique!") + + r1 = t.replies.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "title" => "r2", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + end + + def test_validate_uniqueness_with_object_arg + Reply.validates_uniqueness_of(:topic) + + t = Topic.create("title" => "I'm unique!") + + r1 = t.replies.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "title" => "r2", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + end + def test_validate_uniqueness_scoped_to_defining_class t = Topic.create("title" => "What, me worry?") @@ -149,16 +185,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert t2.valid?, "should validate with nil" assert t2.save, "should save with nil" - with_kcode('UTF8') do - t_utf8 = Topic.new("title" => "Я тоже уникальный!") - assert t_utf8.save, "Should save t_utf8 as unique" + t_utf8 = Topic.new("title" => "Я тоже уникальный!") + assert t_utf8.save, "Should save t_utf8 as unique" - # If database hasn't UTF-8 character set, this test fails - if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" - t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") - assert !t2_utf8.valid?, "Shouldn't be valid" - assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" - end + # If database hasn't UTF-8 character set, this test fails + if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" + t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") + assert !t2_utf8.valid?, "Shouldn't be valid" + assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" end end @@ -256,13 +290,11 @@ class UniquenessValidationTest < ActiveRecord::TestCase end def test_validate_uniqueness_with_limit_and_utf8 - with_kcode('UTF8') do - # Event.title is limited to 5 characters - e1 = Event.create(:title => "一二三四五") - assert e1.valid?, "Could not create an event with a unique, 5 character title" - e2 = Event.create(:title => "一二三四五六七八") - assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" - end + # Event.title is limited to 5 characters + e1 = Event.create(:title => "一二三四五") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "一二三四五六七八") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end def test_validate_straight_inheritance_uniqueness diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index c3e494866b..e575a98170 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -8,7 +8,7 @@ require 'models/parrot' require 'models/company' class ProtectedPerson < ActiveRecord::Base - set_table_name 'people' + self.table_name = 'people' attr_accessor :addon attr_protected :first_name end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 0b54c309d1..302913e095 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -1,14 +1,20 @@ -require "cases/helper" +require 'cases/helper' require 'models/topic' class YamlSerializationTest < ActiveRecord::TestCase fixtures :topics def test_to_yaml_with_time_with_zone_should_not_raise_exception + tz = Time.zone Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"] ActiveRecord::Base.time_zone_aware_attributes = true + topic = Topic.new(:written_on => DateTime.now) assert_nothing_raised { topic.to_yaml } + + ensure + Time.zone = tz + ActiveRecord::Base.time_zone_aware_attributes = false end def test_roundtrip @@ -18,6 +24,11 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal topic, t end + def test_roundtrip_serialized_column + topic = Topic.new(:content => {:omg=>:lol}) + assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content) + end + def test_encode_with_coder topic = Topic.first coder = {} @@ -25,22 +36,17 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal({'attributes' => topic.attributes}, coder) end - begin - require 'psych' - - def test_psych_roundtrip - topic = Topic.first - assert topic - t = Psych.load Psych.dump topic - assert_equal topic, t - end - - def test_psych_roundtrip_new_object - topic = Topic.new - assert topic - t = Psych.load Psych.dump topic - assert_equal topic.attributes, t.attributes - end - rescue LoadError + def test_psych_roundtrip + topic = Topic.first + assert topic + t = Psych.load Psych.dump topic + assert_equal topic, t + end + + def test_psych_roundtrip_new_object + topic = Topic.new + assert topic + t = Psych.load Psych.dump topic + assert_equal topic.attributes, t.attributes end end diff --git a/activerecord/test/fixtures/admin/randomly_named_a9.yml b/activerecord/test/fixtures/admin/randomly_named_a9.yml new file mode 100644 index 0000000000..bc51c83112 --- /dev/null +++ b/activerecord/test/fixtures/admin/randomly_named_a9.yml @@ -0,0 +1,7 @@ +first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/admin/randomly_named_b0.yml b/activerecord/test/fixtures/admin/randomly_named_b0.yml new file mode 100644 index 0000000000..bc51c83112 --- /dev/null +++ b/activerecord/test/fixtures/admin/randomly_named_b0.yml @@ -0,0 +1,7 @@ +first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/colleges.yml b/activerecord/test/fixtures/colleges.yml new file mode 100644 index 0000000000..27591e0c2c --- /dev/null +++ b/activerecord/test/fixtures/colleges.yml @@ -0,0 +1,3 @@ +FIU: + id: 1 + name: Florida International University diff --git a/activerecord/test/fixtures/courses.yml b/activerecord/test/fixtures/courses.yml index 5ee1916003..de3a4a97e5 100644 --- a/activerecord/test/fixtures/courses.yml +++ b/activerecord/test/fixtures/courses.yml @@ -1,6 +1,7 @@ ruby: id: 1 name: Ruby Development + college: FIU java: id: 2 diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml index 308bf75de2..3656564f63 100644 --- a/activerecord/test/fixtures/developers.yml +++ b/activerecord/test/fixtures/developers.yml @@ -8,7 +8,7 @@ jamis: name: Jamis salary: 150000 -<% for digit in 3..10 %> +<% (3..10).each do |digit| %> dev_<%= digit %>: id: <%= digit %> name: fixture_<%= digit %> diff --git a/activerecord/test/fixtures/dog_lovers.yml b/activerecord/test/fixtures/dog_lovers.yml new file mode 100644 index 0000000000..d3e5e4a1aa --- /dev/null +++ b/activerecord/test/fixtures/dog_lovers.yml @@ -0,0 +1,4 @@ +david: + id: 1 + bred_dogs_count: 0 + trained_dogs_count: 1 diff --git a/activerecord/test/fixtures/dogs.yml b/activerecord/test/fixtures/dogs.yml new file mode 100644 index 0000000000..16d19be2c5 --- /dev/null +++ b/activerecord/test/fixtures/dogs.yml @@ -0,0 +1,3 @@ +sophie: + id: 1 + trainer_id: 1 diff --git a/activerecord/test/fixtures/other_topics.yml b/activerecord/test/fixtures/other_topics.yml new file mode 100644 index 0000000000..93f48aedc4 --- /dev/null +++ b/activerecord/test/fixtures/other_topics.yml @@ -0,0 +1,42 @@ +first: + id: 1 + title: The First Topic + author_name: David + author_email_address: david@loudthinking.com + written_on: 2003-07-16t15:28:11.2233+01:00 + last_read: 2004-04-15 + bonus_time: 2005-01-30t15:28:00.00+01:00 + content: Have a nice day + approved: false + replies_count: 1 + +second: + id: 2 + title: The Second Topic of the day + author_name: Mary + written_on: 2004-07-15t15:28:00.0099+01:00 + content: Have a nice day + approved: true + replies_count: 0 + parent_id: 1 + type: Reply + +third: + id: 3 + title: The Third Topic of the day + author_name: Carl + written_on: 2005-07-15t15:28:00.0099+01:00 + content: I'm a troll + approved: true + replies_count: 1 + +fourth: + id: 4 + title: The Fourth Topic of the day + author_name: Carl + written_on: 2006-07-15t15:28:00.0099+01:00 + content: Why not? + approved: true + type: Reply + parent_id: 3 + diff --git a/activerecord/test/fixtures/randomly_named_a9.yml b/activerecord/test/fixtures/randomly_named_a9.yml new file mode 100644 index 0000000000..bc51c83112 --- /dev/null +++ b/activerecord/test/fixtures/randomly_named_a9.yml @@ -0,0 +1,7 @@ +first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/teapots.yml b/activerecord/test/fixtures/teapots.yml new file mode 100644 index 0000000000..ff515beb45 --- /dev/null +++ b/activerecord/test/fixtures/teapots.yml @@ -0,0 +1,3 @@ +bob: + id: 1 + name: Bob diff --git a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb b/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb deleted file mode 100644 index ffb224dad9..0000000000 --- a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb +++ /dev/null @@ -1,10 +0,0 @@ -class MigrationThatRaisesException < ActiveRecord::Migration - def self.up - add_column "people", "last_name", :string - raise 'Something broke' - end - - def self.down - remove_column "people", "last_name" - end -end diff --git a/activerecord/test/migrations/duplicate/3_foo.rb b/activerecord/test/migrations/duplicate/3_foo.rb deleted file mode 100644 index 916fe580fa..0000000000 --- a/activerecord/test/migrations/duplicate/3_foo.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Foo < ActiveRecord::Migration - def self.up - end - - def self.down - end -end
\ No newline at end of file diff --git a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb deleted file mode 100644 index 5fe5089e18..0000000000 --- a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Chunky < ActiveRecord::Migration - def self.up - end - - def self.down - end -end diff --git a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb deleted file mode 100644 index 5fe5089e18..0000000000 --- a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Chunky < ActiveRecord::Migration - def self.up - end - - def self.down - end -end diff --git a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb deleted file mode 100644 index bf912fbfc8..0000000000 --- a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb +++ /dev/null @@ -1,12 +0,0 @@ -class InterleavedInnocentJointable < ActiveRecord::Migration - def self.up - create_table("people_reminders", :id => false) do |t| - t.column :reminder_id, :integer - t.column :person_id, :integer - end - end - - def self.down - drop_table "people_reminders" - end -end diff --git a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb deleted file mode 100644 index c6c94213a0..0000000000 --- a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb +++ /dev/null @@ -1,9 +0,0 @@ -class InterleavedPeopleHaveLastNames < ActiveRecord::Migration - def self.up - add_column "people", "last_name", :string - end - - def self.down - remove_column "people", "last_name" - end -end diff --git a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb deleted file mode 100644 index bf912fbfc8..0000000000 --- a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb +++ /dev/null @@ -1,12 +0,0 @@ -class InterleavedInnocentJointable < ActiveRecord::Migration - def self.up - create_table("people_reminders", :id => false) do |t| - t.column :reminder_id, :integer - t.column :person_id, :integer - end - end - - def self.down - drop_table "people_reminders" - end -end diff --git a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb deleted file mode 100644 index c6c94213a0..0000000000 --- a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb +++ /dev/null @@ -1,9 +0,0 @@ -class InterleavedPeopleHaveLastNames < ActiveRecord::Migration - def self.up - add_column "people", "last_name", :string - end - - def self.down - remove_column "people", "last_name" - end -end diff --git a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb b/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb deleted file mode 100644 index 6849995f5e..0000000000 --- a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb +++ /dev/null @@ -1,8 +0,0 @@ -class InterleavedIRaiseOnDown < ActiveRecord::Migration - def self.up - end - - def self.down - raise - end -end diff --git a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb deleted file mode 100644 index bf912fbfc8..0000000000 --- a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb +++ /dev/null @@ -1,12 +0,0 @@ -class InterleavedInnocentJointable < ActiveRecord::Migration - def self.up - create_table("people_reminders", :id => false) do |t| - t.column :reminder_id, :integer - t.column :person_id, :integer - end - end - - def self.down - drop_table "people_reminders" - end -end diff --git a/activerecord/test/migrations/rename/1_we_need_things.rb b/activerecord/test/migrations/rename/1_we_need_things.rb new file mode 100644 index 0000000000..cdbe0b1679 --- /dev/null +++ b/activerecord/test/migrations/rename/1_we_need_things.rb @@ -0,0 +1,11 @@ +class WeNeedThings < ActiveRecord::Migration + def self.up + create_table("things") do |t| + t.column :content, :text + end + end + + def self.down + drop_table "things" + end +end
\ No newline at end of file diff --git a/activerecord/test/migrations/rename/2_rename_things.rb b/activerecord/test/migrations/rename/2_rename_things.rb new file mode 100644 index 0000000000..d441b71fc9 --- /dev/null +++ b/activerecord/test/migrations/rename/2_rename_things.rb @@ -0,0 +1,9 @@ +class RenameThings < ActiveRecord::Migration + def self.up + rename_table "things", "awesome_things" + end + + def self.down + rename_table "awesome_things", "things" + end +end
\ No newline at end of file diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb new file mode 100644 index 0000000000..e438cf5999 --- /dev/null +++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "hobbies", :string + end + + def self.down + remove_column "people", "hobbies" + end +end diff --git a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb index 81af5fef5e..06cb911117 100644 --- a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb +++ b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration +class ValidPeopleHaveLastNames < ActiveRecord::Migration def self.up add_column "people", "last_name", :string end @@ -6,4 +6,4 @@ class PeopleHaveLastNames < ActiveRecord::Migration def self.down remove_column "people", "last_name" end -end
\ No newline at end of file +end diff --git a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb index d5e71ce8ef..d5e71ce8ef 100644 --- a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb +++ b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb diff --git a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb index 21c9ca5328..21c9ca5328 100644 --- a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb +++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb new file mode 100644 index 0000000000..2f81d5b831 --- /dev/null +++ b/activerecord/test/models/admin/randomly_named_c1.rb @@ -0,0 +1,3 @@ +class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+ self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index c12c88e195..d0e628bd50 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,4 +1,5 @@ class Admin::User < ActiveRecord::Base belongs_to :account store :settings, :accessors => [ :color, :homepage ] + store :preferences, :accessors => [ :remember_login ] end diff --git a/activerecord/test/models/arunit2_model.rb b/activerecord/test/models/arunit2_model.rb new file mode 100644 index 0000000000..04b8b15d3d --- /dev/null +++ b/activerecord/test/models/arunit2_model.rb @@ -0,0 +1,3 @@ +class ARUnit2Model < ActiveRecord::Base + self.abstract_class = true +end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 23db5650d4..d50e11d6c9 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -49,12 +49,12 @@ class Author < ActiveRecord::Base has_many :sti_post_comments, :through => :sti_posts, :source => :comments has_many :special_nonexistant_posts, :class_name => "SpecialPost", :conditions => "posts.body = 'nonexistant'" - has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => "comments.post_id = 0" + has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => { 'comments.post_id' => 0 } has_many :nonexistant_comments, :through => :posts has_many :hello_posts, :class_name => "Post", :conditions => "posts.body = 'hello'" has_many :hello_post_comments, :through => :hello_posts, :source => :comments - has_many :posts_with_no_comments, :class_name => 'Post', :conditions => 'comments.id is null', :include => :comments + has_many :posts_with_no_comments, :class_name => 'Post', :conditions => { 'comments.id' => nil }, :include => :comments has_many :hello_posts_with_hash_conditions, :class_name => "Post", :conditions => {:body => 'hello'} @@ -128,7 +128,6 @@ class Author < ActiveRecord::Base belongs_to :author_address, :dependent => :destroy belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress" - has_many :post_categories, :through => :posts, :source => :categories has_many :category_post_comments, :through => :categories, :source => :post_comments has_many :misc_posts, :class_name => 'Post', diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb index e61d48e6a5..dff099c1fb 100644 --- a/activerecord/test/models/bird.rb +++ b/activerecord/test/models/bird.rb @@ -1,9 +1,12 @@ class Bird < ActiveRecord::Base + belongs_to :pirate validates_presence_of :name + accepts_nested_attributes_for :pirate + attr_accessor :cancel_save_from_callback before_save :cancel_save_callback_method, :if => :cancel_save_from_callback def cancel_save_callback_method false end -end
\ No newline at end of file +end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index b9c2e8ec9a..6ff1329d8e 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -9,7 +9,7 @@ class Car < ActiveRecord::Base has_many :tyres has_many :engines, :dependent => :destroy - has_many :wheels, :as => :wheelable + has_many :wheels, :as => :wheelable, :dependent => :destroy scope :incl_tyres, includes(:tyres) scope :incl_engines, includes(:engines) diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb new file mode 100644 index 0000000000..c7495d7deb --- /dev/null +++ b/activerecord/test/models/college.rb @@ -0,0 +1,5 @@ +require_dependency 'models/arunit2_model' + +class College < ARUnit2Model + has_many :courses +end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index c1f7a4171a..fbdfaa2c29 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -4,7 +4,7 @@ end class Company < AbstractCompany attr_protected :rating - set_sequence_name :companies_nonstd_seq + self.sequence_name = :companies_nonstd_seq validates_presence_of :name @@ -42,8 +42,10 @@ class Firm < Company :before_remove => :log_before_remove, :after_remove => :log_after_remove has_many :unsorted_clients, :class_name => "Client" + has_many :unsorted_clients_with_symbol, :class_name => :Client has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC" has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id" + has_many :clients_ordered_by_name, :order => "name", :class_name => "Client" has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all @@ -87,6 +89,8 @@ class Firm < Company has_many :accounts has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false + has_many :association_with_references, :class_name => 'Client', :references => :foo + def log @log ||= [] end diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb index 15e3a1de0b..7db9a4e731 100644 --- a/activerecord/test/models/country.rb +++ b/activerecord/test/models/country.rb @@ -1,6 +1,6 @@ class Country < ActiveRecord::Base - set_primary_key :country_id + self.primary_key = :country_id has_and_belongs_to_many :treaties diff --git a/activerecord/test/models/course.rb b/activerecord/test/models/course.rb index 8a40fa740d..f3d0e05ff7 100644 --- a/activerecord/test/models/course.rb +++ b/activerecord/test/models/course.rb @@ -1,3 +1,6 @@ -class Course < ActiveRecord::Base +require_dependency 'models/arunit2_model' + +class Course < ARUnit2Model + belongs_to :college has_many :entrants end diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb index a8a25834b1..1b3b54545f 100644 --- a/activerecord/test/models/dashboard.rb +++ b/activerecord/test/models/dashboard.rb @@ -1,3 +1,3 @@ class Dashboard < ActiveRecord::Base - set_primary_key :dashboard_id -end
\ No newline at end of file + self.primary_key = :dashboard_id +end diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb new file mode 100644 index 0000000000..72b7d33a86 --- /dev/null +++ b/activerecord/test/models/dog.rb @@ -0,0 +1,4 @@ +class Dog < ActiveRecord::Base + belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count + belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count +end diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb new file mode 100644 index 0000000000..a33dc575c5 --- /dev/null +++ b/activerecord/test/models/dog_lover.rb @@ -0,0 +1,4 @@ +class DogLover < ActiveRecord::Base + has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id + has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id +end diff --git a/activerecord/test/models/joke.rb b/activerecord/test/models/joke.rb index d7f01e59e6..edda4655dc 100644 --- a/activerecord/test/models/joke.rb +++ b/activerecord/test/models/joke.rb @@ -1,7 +1,7 @@ class Joke < ActiveRecord::Base - set_table_name 'funny_jokes' + self.table_name = 'funny_jokes' end class GoodJoke < ActiveRecord::Base - set_table_name 'funny_jokes' + self.table_name = 'funny_jokes' end diff --git a/activerecord/test/models/keyboard.rb b/activerecord/test/models/keyboard.rb index 32a4a7fad0..39347e274e 100644 --- a/activerecord/test/models/keyboard.rb +++ b/activerecord/test/models/keyboard.rb @@ -1,3 +1,3 @@ class Keyboard < ActiveRecord::Base - set_primary_key 'key_number' + self.primary_key = 'key_number' end diff --git a/activerecord/test/models/legacy_thing.rb b/activerecord/test/models/legacy_thing.rb index eaeb642d12..eead181a0e 100644 --- a/activerecord/test/models/legacy_thing.rb +++ b/activerecord/test/models/legacy_thing.rb @@ -1,3 +1,3 @@ class LegacyThing < ActiveRecord::Base - set_locking_column :version + self.locking_column = :version end diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb index b96c054f6c..3fcd5e4b69 100644 --- a/activerecord/test/models/liquid.rb +++ b/activerecord/test/models/liquid.rb @@ -1,5 +1,5 @@ class Liquid < ActiveRecord::Base - set_table_name :liquid + self.table_name = :liquid has_many :molecules, :uniq => true end diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb index 830cdb5796..4fe79720ad 100644 --- a/activerecord/test/models/minivan.rb +++ b/activerecord/test/models/minivan.rb @@ -1,5 +1,5 @@ class Minivan < ActiveRecord::Base - set_primary_key :minivan_id + self.primary_key = :minivan_id belongs_to :speedometer has_one :dashboard, :through => :speedometer diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 853f2682b3..763baefd91 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,3 +1,3 @@ class MixedCaseMonkey < ActiveRecord::Base - set_primary_key 'monkeyID' + self.primary_key = 'monkeyID' end diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb index 5760b991ec..fea55f4535 100644 --- a/activerecord/test/models/owner.rb +++ b/activerecord/test/models/owner.rb @@ -1,5 +1,5 @@ class Owner < ActiveRecord::Base - set_primary_key :owner_id + self.primary_key = :owner_id has_many :pets has_many :toys, :through => :pets end diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index 737ef9131b..c4ee2bd19d 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -1,5 +1,6 @@ class Parrot < ActiveRecord::Base - set_inheritance_column :parrot_sti_class + self.inheritance_column = :parrot_sti_class + has_and_belongs_to_many :pirates has_and_belongs_to_many :treasures has_many :loots, :as => :looter diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 967a3625aa..d2a0c6b40c 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -3,7 +3,8 @@ class Person < ActiveRecord::Base has_one :reader has_many :posts, :through => :readers - has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null' + has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, + :conditions => 'comments.id is null', :references => :comments has_many :references has_many :bad_references @@ -54,7 +55,7 @@ class LoosePerson < ActiveRecord::Base self.table_name = 'people' self.abstract_class = true - attr_protected :comments + attr_protected :comments, :best_friend_id, :best_friend_of_id attr_protected :as => :admin has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id @@ -81,4 +82,4 @@ class TightPerson < ActiveRecord::Base accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end -class TightDescendant < TightPerson; end
\ No newline at end of file +class TightDescendant < TightPerson; end diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb index 113826756a..3cd5bceed5 100644 --- a/activerecord/test/models/pet.rb +++ b/activerecord/test/models/pet.rb @@ -2,7 +2,7 @@ class Pet < ActiveRecord::Base attr_accessor :current_user - set_primary_key :pet_id + self.primary_key = :pet_id belongs_to :owner, :touch => true has_many :toys diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 198a963cbc..1cab78d8c7 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -24,6 +24,10 @@ class Post < ActiveRecord::Base belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts belongs_to :author_with_address, :class_name => "Author", :foreign_key => :author_id, :include => :author_address + def first_comment + super.body + end + has_one :first_comment, :class_name => 'Comment', :order => 'id ASC' has_one :last_comment, :class_name => 'Comment', :order => 'id desc' scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } @@ -40,6 +44,10 @@ class Post < ActiveRecord::Base def newest created.last end + + def the_association + proxy_association + end end has_many :author_favorites, :through => :author @@ -181,4 +189,4 @@ end class SpecialPostWithDefaultScope < ActiveRecord::Base self.table_name = 'posts' default_scope where(:id => [1, 5,6]) -end
\ No newline at end of file +end diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb new file mode 100644 index 0000000000..18a86c4989 --- /dev/null +++ b/activerecord/test/models/randomly_named_c1.rb @@ -0,0 +1,3 @@ +class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+ self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb index 94743eff8e..0a7d38d8ec 100644 --- a/activerecord/test/models/speedometer.rb +++ b/activerecord/test/models/speedometer.rb @@ -1,4 +1,4 @@ class Speedometer < ActiveRecord::Base - set_primary_key :speedometer_id + self.primary_key = :speedometer_id belongs_to :dashboard -end
\ No newline at end of file +end diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb index f8d4c6e0e4..f084ec1bdc 100644 --- a/activerecord/test/models/string_key_object.rb +++ b/activerecord/test/models/string_key_object.rb @@ -1,3 +1,3 @@ class StringKeyObject < ActiveRecord::Base - set_primary_key :id + self.primary_key = :id end diff --git a/activerecord/test/models/subscriber.rb b/activerecord/test/models/subscriber.rb index 5b78014e6f..76e85a0cd3 100644 --- a/activerecord/test/models/subscriber.rb +++ b/activerecord/test/models/subscriber.rb @@ -1,5 +1,5 @@ class Subscriber < ActiveRecord::Base - set_primary_key 'nick' + self.primary_key = 'nick' has_many :subscriptions has_many :books, :through => :subscriptions end diff --git a/activerecord/test/models/teapot.rb b/activerecord/test/models/teapot.rb new file mode 100644 index 0000000000..b035b18c1b --- /dev/null +++ b/activerecord/test/models/teapot.rb @@ -0,0 +1,35 @@ +class Teapot + # I'm a little teapot, + # Short and stout, + # Here is my handle + # Here is my spout + # When I get all steamed up, + # Hear me shout, + # Tip me over and pour me out! + # + # HELL YEAH TEAPOT SONG + + include ActiveRecord::Model +end + +class OtherTeapot < Teapot +end + +class OMFGIMATEAPOT + def aaahhh + "mmm" + end +end + +class CoolTeapot < OMFGIMATEAPOT + include ActiveRecord::Model + self.table_name = "teapots" +end + +class Ceiling + include ActiveRecord::Model + + class Teapot + include ActiveRecord::Model + end +end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index fe424e61b2..8bcb9df8a8 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -8,6 +8,8 @@ class Topic < ActiveRecord::Base scope :approved, :conditions => {:approved => true} scope :rejected, :conditions => {:approved => false} + scope :scope_with_lambda, lambda { scoped } + scope :by_lifo, :conditions => {:author_name => 'lifo'} scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}} @@ -78,6 +80,11 @@ class Topic < ActiveRecord::Base after_initialize :set_email_address + class_attribute :after_initialize_called + after_initialize do + self.class.after_initialize_called = true + end + def approved=(val) @custom_approved = val write_attribute(:approved, val) @@ -106,6 +113,10 @@ class Topic < ActiveRecord::Base def after_create_for_transaction; end end +class ImportantTopic < Topic + serialize :important, Hash +end + module Web class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply' diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb index 6c45e99671..0377e50011 100644 --- a/activerecord/test/models/toy.rb +++ b/activerecord/test/models/toy.rb @@ -1,5 +1,5 @@ class Toy < ActiveRecord::Base - set_primary_key :toy_id + self.primary_key = :toy_id belongs_to :pet scope :with_pet, joins(:pet) diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb index b46537f0d2..41fd1350f3 100644 --- a/activerecord/test/models/treaty.rb +++ b/activerecord/test/models/treaty.rb @@ -1,6 +1,6 @@ class Treaty < ActiveRecord::Base - set_primary_key :treaty_id + self.primary_key = :treaty_id has_and_belongs_to_many :countries diff --git a/activerecord/test/models/warehouse_thing.rb b/activerecord/test/models/warehouse_thing.rb index 6ace1183cc..f20bd1a245 100644 --- a/activerecord/test/models/warehouse_thing.rb +++ b/activerecord/test/models/warehouse_thing.rb @@ -1,5 +1,5 @@ class WarehouseThing < ActiveRecord::Base - set_table_name "warehouse-things" + self.table_name = "warehouse-things" validates_uniqueness_of :value -end
\ No newline at end of file +end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 5cf9a207f3..25b416a906 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,6 +1,6 @@ ActiveRecord::Schema.define do - %w(postgresql_tsvectors postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings + %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -63,6 +63,15 @@ _SQL ); _SQL + if 't' == select_value("select 'hstore'=ANY(select typname from pg_type)") + execute <<_SQL + CREATE TABLE postgresql_hstores ( + id SERIAL PRIMARY KEY, + hash_store hstore default ''::hstore + ); +_SQL + end + execute <<_SQL CREATE TABLE postgresql_moneys ( id SERIAL PRIMARY KEY, diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index bb08f5c181..428a85ab4e 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -37,7 +37,8 @@ ActiveRecord::Schema.define do create_table :admin_users, :force => true do |t| t.string :name - t.text :settings + t.text :settings, :null => true + t.text :preferences, :null => false, :default => "" t.references :account end @@ -107,6 +108,7 @@ ActiveRecord::Schema.define do t.string :name t.integer :engines_count t.integer :wheels_count + t.column :lock_version, :integer, :null => false, :default => 0 end create_table :categories, :force => true do |t| @@ -173,6 +175,7 @@ ActiveRecord::Schema.define do end add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index" + add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10" create_table :computers, :force => true do |t| t.integer :developer, :null => false @@ -212,6 +215,16 @@ ActiveRecord::Schema.define do t.integer :access_level, :default => 1 end + create_table :dog_lovers, :force => true do |t| + t.integer :trained_dogs_count, :default => 0 + t.integer :bred_dogs_count, :default => 0 + end + + create_table :dogs, :force => true do |t| + t.integer :trainer_id + t.integer :breeder_id + end + create_table :edges, :force => true, :id => false do |t| t.column :source_id, :integer, :null => false t.column :sink_id, :integer, :null => false @@ -505,6 +518,11 @@ ActiveRecord::Schema.define do t.string :type end + create_table :randomly_named_table, :force => true do |t| + t.string :some_attribute + t.integer :another_attribute + end + create_table :ratings, :force => true do |t| t.integer :comment_id t.integer :value @@ -596,6 +614,12 @@ ActiveRecord::Schema.define do t.datetime :ending end + create_table :teapots, :force => true do |t| + t.string :name + t.string :type + t.timestamps + end + create_table :topics, :force => true do |t| t.string :title t.string :author_name @@ -607,8 +631,10 @@ ActiveRecord::Schema.define do # Oracle SELECT WHERE clause which causes many unit test failures if current_adapter?(:OracleAdapter) t.string :content, :limit => 4000 + t.string :important, :limit => 4000 else t.text :content + t.text :important end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 @@ -700,8 +726,6 @@ ActiveRecord::Schema.define do create_table :countries_treaties, :force => true, :id => false do |t| t.string :country_id, :null => false t.string :treaty_id, :null => false - t.datetime :created_at - t.datetime :updated_at end create_table :liquid, :force => true do |t| @@ -736,4 +760,9 @@ end Course.connection.create_table :courses, :force => true do |t| t.column :name, :string, :null => false + t.column :college_id, :integer +end + +College.connection.create_table :colleges, :force => true do |t| + t.column :name, :string, :null => false end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index a39794fa39..11154c3797 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -1,4 +1,5 @@ -require 'logger' +require 'active_support/logger' +require_dependency 'models/college' require_dependency 'models/course' module ARTest @@ -12,9 +13,9 @@ module ARTest def self.connect puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}" - ActiveRecord::Base.logger = Logger.new("debug.log") - ActiveRecord::Base.configurations = connection_config - ActiveRecord::Base.establish_connection 'arunit' - Course.establish_connection 'arunit2' + ActiveRecord::Model.logger = ActiveSupport::Logger.new("debug.log") + ActiveRecord::Model.configurations = connection_config + ActiveRecord::Model.establish_connection 'arunit' + ARUnit2Model.establish_connection 'arunit2' end end |