test/cases/migration_test.rb in db2-2.6.2 vs test/cases/migration_test.rb in db2-2.7.0

- old
+ new

@@ -3,28 +3,31 @@ require 'models/person' require 'models/topic' require 'models/developer' -require MIGRATIONS_ROOT + "/valid/1_people_have_last_names" 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" -require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down" if ActiveRecord::Base.connection.supports_migrations? class BigNumber < ActiveRecord::Base; end class Reminder < ActiveRecord::Base; end + class Thing < ActiveRecord::Base; end + class ActiveRecord::Migration - class <<self + class << self attr_accessor :message_count - def puts(text="") - self.message_count ||= 0 - self.message_count += 1 - end 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 @@ -44,10 +47,11 @@ end end class MigrationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false + if (current_adapter?(:IBM_DBAdapter)) #Rename is supported only for server zOS 9 , DB2 COBRA and Informix server_type = ActiveRecord::Base.connection.servertype.class.name @ibm_db_rename_supported = server_type.include?('::IBM_DB2_LUW_COBRA') || server_type.class.name.include?('::IBM_IDS') || @@ -56,17 +60,22 @@ fixtures :people def setup ActiveRecord::Migration.verbose = true - PeopleHaveLastNames.message_count = 0 + 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(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 + %w(reminders people_reminders prefix_reminders_suffix).each do |table| Reminder.connection.drop_table(table) rescue nil end Reminder.reset_column_information @@ -116,11 +125,11 @@ # 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) - unless current_adapter?(:IBM_DBAdapter) #cannot assign a integer value to string column + unless current_adapter?(:IBM_DBAdapter) 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) else Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'first_name'}" end assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) } @@ -131,10 +140,22 @@ # 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 + + # Selected adapters support index sort order + if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :order => {:last_name => :desc}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :order => :desc) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + 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) @@ -150,10 +171,17 @@ 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_add_index_attribute_length_limit + Person.connection.add_index :people, [:first_name, :primary_contact_id], :length => {:first_name => 10, :primary_contact_id => nil}, :name => "attribute_length" + assert Person.connection.index_exists?(:people, [:first_name, :primary_contact_id], :name => "attribute_length") + ensure + Person.connection.remove_index(:people, :name => "attribute_length") + 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 @@ -240,13 +268,13 @@ assert_equal connection.columns(:testings).size, 1 end end unless current_adapter?(:IBM_DBAdapter) - # Cannot add a primary key to a table with some rows already in it as it violates the unique constraint + # For DB2: Cannot add a primary key to a table with some rows already in it as it violates the unique constraint # Secondly GENERATED BY DEFAULT AS IDENTITY cannot be applied in a alter table command. - # as this will be wrong sql syntax for DB2 + # as this will be wrong sql syntax for DB 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 @@ -278,86 +306,37 @@ 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) - ibm_ids_zOS = ActiveRecord::Base.connection.servertype.class.name.include?('::IBM_IDS')|| - ActiveRecord::Base.connection.servertype.class.name.include?('::IBM_DB2_ZOS') - + 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 || ibm_ids_zOS + 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 || ibm_ids_zOS + 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 || ibm_ids_zOS + assert_equal "hello", five.default unless mysql ensure Person.connection.drop_table :testings rescue nil end - if current_adapter?(:IBM_DBAdapter) - def test_no_limits_datatypes_IBM_DB - ibm_ids = ActiveRecord::Base.connection.servertype.class.name.include?('::IBM_IDS') - clasz = Class.new(ActiveRecord::Base) - clasz.table_name = 'test_no_limits_datatypes_IBM_DB' - assert_nothing_raised do - clasz.connection.create_table clasz.table_name do |t| - t.column "test_varchar", :string, :limit => 10 - t.column "test_integer", :integer, :limit => 5 - t.column "test_boolean", :boolean, :limit => 5 - t.column "test_double", :double, :limit => 10 - t.column "test_date", :date, :limit => 10 - t.column "test_time", :time, :limit => 10 - t.column "test_tstamp", :timestamp, :limit => 10 - t.column "test_xml", :xml, :limit => 10 unless ibm_ids - t.column "test_clob", :text, :limit => 10000 - t.column "test_decfloat", :decfloat, :limit => 100 - end - end - ensure - clasz.connection.drop_table(clasz.table_name) rescue nil - end - - #Sexy migration test for column of type xml and char - def test_short_hand_migrations_for_ibm_db_datatypes - ibm_ids = ActiveRecord::Base.connection.servertype.class.name.include?('::IBM_IDS') - clasz = Class.new(ActiveRecord::Base) - clasz.table_name = 'test_short_hand_migrations' - assert_nothing_raised do - clasz.connection.create_table clasz.table_name do |t| - t.xml :xml_col unless ibm_ids - t.char :char_col, :limit=>10 - t.decfloat :dec_col, :precision=>16 - end - end - assert_nothing_raised do - clasz.connection.change_table clasz.table_name do |t| - t.xml :xml_col1 unless ibm_ids - t.char :char_col1, :limit=>50 - t.decfloat :dec_col1, :precision=>34 - end - end - ensure - clasz.connection.drop_table(clasz.table_name) rescue nil - end - end - def test_create_table_with_limits assert_nothing_raised do Person.connection.create_table :testings do |t| t.column :foo, :string, :limit => 255 @@ -453,12 +432,12 @@ created_columns = Person.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 + 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 @@ -485,11 +464,11 @@ Person.connection.drop_table table_name rescue nil 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, :SQLiteAdapter, :IBM_DBAdapter) + unless current_adapter?(:SybaseAdapter, :SQLite3Adapter, :IBM_DBAdapter) 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 @@ -535,13 +514,17 @@ 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) values (people_seq.nextval, 12345678901234567890.0123456789)" + Person.connection.execute "insert into people (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 - Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')" + 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())" + elsif current_adapter?(:IBM_DBAdapter) + Person.connection.execute "insert into people (wealth, created_at, updated_at, lock_version, first_name) values (12345678901234567890.0123456789, CURRENT TIMESTAMP, CURRENT TIMESTAMP, 0, 'Jim')" else Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)" end # SELECT @@ -555,11 +538,16 @@ # Reset to old state Person.delete_all # Now use the Rails insertion - assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") } + unless current_adapter?(:IBM_DBAdapter) + assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") } + else + # First_name is a not null column, hence ensure a value is specified + assert_nothing_raised { Person.create ({:wealth => BigDecimal.new("12345678901234567890.0123456789"), :first_name => "James"}) } + end # SELECT row = Person.find(:first) assert_kind_of BigDecimal, row.wealth @@ -580,10 +568,46 @@ wealth_column = Person.columns_hash['wealth'] assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale 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 + + Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8 + Person.reset_column_information + + 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 + 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 @@ -607,11 +631,11 @@ 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 significent digits (beyond the 16 of float), 10 of them + # 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 @@ -740,32 +764,32 @@ assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name } end end end - if (!current_adapter?(:IBM_DBAdapter) || @ibm_db_rename_supported) + if(!current_adapter?(:IBM_DBAdapter) || @ibm_db_rename_supported) 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 @@ -807,11 +831,11 @@ 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) + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) ActiveRecord::StatementInvalid else ActiveRecord::ActiveRecordError end assert_raise(exception) do @@ -831,11 +855,11 @@ Person.connection.add_column("people", "first_name", :string) rescue nil end end end - unless (current_adapter?(:IBM_DBAdapter)) #Cannot alter a object when there is another object depending on it + unless current_adapter?(:IBM_DBAdapter) 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 @@ -846,63 +870,66 @@ ensure ActiveRecord::Base.connection.drop_table(:hats) end end - def test_rename_column_with_an_index + 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 - Person.connection.add_index :hats, :hat_name - assert_nothing_raised do - Person.connection.rename_column "hats", "hat_name", "name" - 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_index + 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_size" + 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 - unless (current_adapter?(:IBM_DBAdapter)) #Cannot alter a object when there is another object depending on it - 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") } end - unless current_adapter?(:IBM_DBAdapter) #incompatible types changes - def test_change_type_of_not_null_column - assert_nothing_raised do + def test_change_type_of_not_null_column + assert_nothing_raised do + unless current_adapter?(:IBM_DBAdapter) Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information + else + Topic.connection.change_column_null "topics", "written_on", false + end + Topic.reset_column_information + unless current_adapter?(:IBM_DBAdapter) Topic.connection.change_column "topics", "written_on", :datetime, :null => false - Topic.reset_column_information + else + Topic.connection.change_column_null "topics", "written_on", false end + Topic.reset_column_information + + unless current_adapter?(:IBM_DBAdapter) + Topic.connection.change_column "topics", "written_on", :datetime, :null => true + else + Topic.connection.change_column_null "topics", "written_on", true + end + + Topic.reset_column_information end - end + end - if current_adapter?(:SQLiteAdapter) + 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| @@ -950,18 +977,24 @@ 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" - unless current_adapter?(:IBM_DBAdapter) # incompatible types changes + unless current_adapter?(:IBM_DBAdapter) Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true + else + Person.connection.change_column_null "people", "funny",false + end Person.reset_column_information assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point" + unless current_adapter?(:IBM_DBAdapter) 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 + else + Person.connection.change_column_null "people", "funny",true + end + 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 begin ActiveRecord::Base.connection.create_table :octopuses do |t| @@ -983,56 +1016,64 @@ ActiveRecord::Base.connection.drop_table :octopuses rescue nil ActiveRecord::Base.connection.drop_table :octopi rescue nil end end - unless current_adapter?(:IBM_DBAdapter) # incompatible types changes - 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 } + unless current_adapter?(:IBM_DBAdapter) + # Cannot convert from type integer to varchar. + # Safe to not run this test as add_column will be covered in other test cases + 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 } + 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 } + 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 } + 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 end - 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? - unless current_adapter?(:IBM_DBAdapter) # incompatible types changes + + unless current_adapter?(:IBM_DBAdapter) 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 - end + else + assert_nothing_raised { Person.connection.change_column_default "people", "contributor", nil } + end + 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? - unless current_adapter?(:IBM_DBAdapter) # incompatible types changes + + unless current_adapter?(:IBM_DBAdapter) assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false } - Person.reset_column_information - assert !Person.new.administrator? - end + else + assert_nothing_raised { Person.connection.change_column_default "people", "administrator", false } + end + Person.reset_column_information + assert !Person.new.administrator? ensure Person.connection.remove_column("people", "administrator") rescue nil end def test_change_column_default @@ -1041,14 +1082,15 @@ assert_equal "Tester", Person.new.first_name end def test_change_column_quotes_column_names Person.connection.create_table :testings do |t| - if current_adapter?(:IBM_DBAdapter) - t.column :select, :string, :limit => 5 - else + unless current_adapter?(:IBM_DBAdapter) t.column :select, :string + else + # If no limit specified by default column of length 255 is created, which later cannot be scaled down to 10 + t.column :select, :string, :limit => 5 end end assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } @@ -1065,11 +1107,11 @@ 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.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 @@ -1085,60 +1127,63 @@ 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 - if (!current_adapter?(:IBM_DBAdapter) || @ibm_db_rename_supported) + if (!current_adapter?(:IBM_DBAdapter) || @ibm_db_rename_supported) 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 - end + end # change column - unless (current_adapter?(:IBM_DBAdapter) && !@ibm_db_rename_supported) + unless current_adapter?(:IBM_DBAdapter) 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 else person_klass.connection.change_column "testings", "wealth", :decimal, :precision => 15, :scale => 1,:null => false, :default => 1000 person_klass.reset_column_information assert_equal 1000, person_klass.columns_hash["wealth"].default assert_equal false, person_klass.columns_hash["wealth"].null - end + end # change column, make it nullable and clear default - unless (current_adapter?(:IBM_DBAdapter) && !@ibm_db_rename_supported) + unless current_adapter?(:IBM_DBAdapter) 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 else person_klass.connection.change_column "testings", "wealth", :decimal, :precision => 20, :scale => 2, :null => true, :default => nil person_klass.reset_column_information assert_nil person_klass.columns_hash["wealth"].default assert_equal true, person_klass.columns_hash["wealth"].null - end + end # change_column_null, make it not nullable and set null values to a default value - unless (current_adapter?(:IBM_DBAdapter) && !@ibm_db_rename_supported) + unless current_adapter?(:IBM_DBAdapter) 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 else # Trying to set the value of the column wealth to NULL and - # in the next statement a not null constraint is being applied which is wrong. - #person_klass.connection.execute('UPDATE testings SET wealth = NULL') + # in the next statement a not null constraint is being applied which is wrong + #person_klass.connection.execute('UPDATE testings SET money = NULL') person_klass.connection.change_column_null "testings", "wealth", false, 2000 person_klass.reset_column_information + #assert_nil person_klass.columns_hash["wealth"].default #Setting default to 2000 and expecting nil is nor correct + assert_not_nil person_klass.columns_hash["wealth"].default assert_equal false, person_klass.columns_hash["wealth"].null - assert_equal 2000, person_klass.columns_hash["wealth"].default + #Changing default does not change the already inserted value. Hence expecting 2000 is wrong. + assert_equal [99], Person.connection.select_values("SELECT wealth FROM testings").map { |s| s.to_i }.sort end ensure Person.connection.drop_table :testings rescue nil end @@ -1185,10 +1230,22 @@ assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) ensure Person.connection.drop_table :testings rescue nil 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 + ensure + Person.connection.drop_table :testings rescue nil + end + def test_add_table assert !Reminder.table_exists? WeNeedReminders.up @@ -1222,21 +1279,21 @@ 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) - unless current_adapter?(:IBM_DBAdapter) # incompatible types retrieved + unless current_adapter?(:IBM_DBAdapter) assert_kind_of Integer, b.world_population else assert_kind_of BigDecimal, b.world_population - end + end assert_equal 6000000000, b.world_population - unless current_adapter?(:IBM_DBAdapter) # incompatible types retrieved + unless current_adapter?(:IBM_DBAdapter) assert_kind_of Fixnum, b.my_house_population else assert_kind_of BigDecimal, b.my_house_population - end + end 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 @@ -1252,21 +1309,21 @@ # 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?(:SQLiteAdapter) + 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 - unless current_adapter?(:IBM_DBAdapter) # incompatible types retrieved - assert_kind_of Fixnum, b.value_of_e - else - assert_kind_of BigDecimal, b.value_of_e - end + unless current_adapter?(:IBM_DBAdapter) + assert_kind_of Fixnum, b.value_of_e + else + assert_kind_of BigDecimal, b.value_of_e + end assert_equal 2, b.value_of_e end GiveMeBigNumbers.down assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) } @@ -1290,10 +1347,66 @@ Person.reset_column_information assert !Person.column_methods_hash.include?(:last_name) assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } end + def test_filtering_migrations + assert !Person.column_methods_hash.include?(:last_name) + assert !Reminder.table_exists? + + name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" } + ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter) + + Person.reset_column_information + assert Person.column_methods_hash.include?(:last_name) + assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) } + + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter) + + 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) @@ -1357,74 +1470,90 @@ end def test_finds_migrations migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations - [[1, 'PeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + [[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.new(:up, MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations + + [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i| + assert_equal migrations[i].version, pair.first + 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.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 + + def test_dump_schema_information_outputs_lexically_ordered_versions + migration_path = MIGRATIONS_ROOT + '/valid_with_timestamps' + ActiveRecord::Migrator.run(:up, migration_path, 20100301010101) + ActiveRecord::Migrator.run(:up, migration_path, 20100201010101) + + schema_info = ActiveRecord::Base.connection.dump_schema_information + assert_match(/20100201010101.*20100301010101/m, schema_info) + 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, 'InnocentJointable' + assert_equal migrations[0].name, 'InterleavedInnocentJointable' end def test_relative_migrations - $".delete_if do |fname| - fname == (MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb") - end - Object.send(:remove_const, :PeopleHaveLastNames) - - Dir.chdir(MIGRATIONS_ROOT) do + list = Dir.chdir(MIGRATIONS_ROOT) do ActiveRecord::Migrator.up("valid/", 1) end - assert defined?(PeopleHaveLastNames) + migration_proxy = list.find { |item| + item.name == 'ValidPeopleHaveLastNames' + } + assert migration_proxy, 'should find pending migration' end def test_only_loads_pending_migrations # migrate up to 1 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - # now unload the migrations that have been defined - Object.send(:remove_const, :PeopleHaveLastNames) + proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) - - assert !defined? PeopleHaveLastNames - - %w(WeNeedReminders, InnocentJointable).each do |migration| - assert defined? migration - end - - ensure - load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb") + names = proxies.map(&:name) + assert !names.include?('ValidPeopleHaveLastNames') + assert names.include?('WeNeedReminders') + assert names.include?('InnocentJointable') 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) - # now unload the migrations that have been defined - PeopleHaveLastNames.unloadable - ActiveSupport::Dependencies.remove_unloadable_constants! - # migrate down to 0 again - ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) - - assert !defined? PeopleHaveLastNames - ensure - load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb") + proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) + assert_equal [], proxies 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) @@ -1437,24 +1566,24 @@ end end def test_migrator_verbosity ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert PeopleHaveLastNames.message_count > 0 - PeopleHaveLastNames.message_count = 0 + assert_not_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migration.message_count = 0 ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert PeopleHaveLastNames.message_count > 0 - PeopleHaveLastNames.message_count = 0 + assert_not_equal 0, ActiveRecord::Migration.message_count + ActiveRecord::Migration.message_count = 0 end def test_migrator_verbosity_off - PeopleHaveLastNames.verbose = false + ActiveRecord::Migration.verbose = false ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) - assert PeopleHaveLastNames.message_count.zero? + assert_equal 0, ActiveRecord::Migration.message_count ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0) - assert PeopleHaveLastNames.message_count.zero? + assert_equal 0, ActiveRecord::Migration.message_count end def test_migrator_going_down_due_to_version_target ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0) @@ -1553,10 +1682,32 @@ ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" Reminder.reset_table_name 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 + 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 @@ -1658,90 +1809,11 @@ 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 - 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 - 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 - 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 - 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 - 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 - 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 - end - - if current_adapter?(:PostgreSQLAdapter) - def test_xml_creates_xml_column - with_new_table do |t| - t.expects(:column).with(:data, 'xml', {}) - t.xml :data - end - 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 - end - - end # SexyMigrationsTest - class MigrationLoggerTest < ActiveRecord::TestCase - def setup - Object.send(:remove_const, :InnocentJointable) - end - 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") @@ -1750,28 +1822,26 @@ ActiveRecord::Base.logger = previous_logger end end class InterleavedMigrationsTest < ActiveRecord::TestCase - def setup - Object.send(:remove_const, :PeopleHaveLastNames) - end - 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) - Object.send(:remove_const, :PeopleHaveLastNames) - Object.send(:remove_const, :InnocentJointable) assert_nothing_raised do - ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3") + proxies = ActiveRecord::Migrator.down( + MIGRATIONS_ROOT + "/interleaved/pass_3") + names = proxies.map(&:name) + assert names.include?('InterleavedPeopleHaveLastNames') + assert names.include?('InterleavedInnocentJointable') end end end class ReservedWordsMigrationTest < ActiveRecord::TestCase @@ -2008,7 +2078,331 @@ Person.connection.change_table :delete_me do |t| yield t end end 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 + + 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 + + def indexes + @indexes ||= Person.connection.indexes('delete_me') + end + end # AlterTableMigrationsTest + + end + + 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 + + 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 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 = 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.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_with_timestamps + @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.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_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.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 + end + 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"] + + 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 + end + + def test_skipping_migrations + @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_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_skip_is_not_called_if_migrations_are_from_the_same_plugin + @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" + + 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) + + assert_equal 2, copied.length + assert_equal 0, skipped.length + 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.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert_equal 2, copied.length + end + ensure + clear + Dir.delete(@migrations_path) + end + + 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.bukkits.rb") + assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb") + assert_equal 2, copied.length + end + ensure + clear + end + end +end