migration_test.rb 38.5 KB
Newer Older
1
require "cases/helper"
2 3
require 'bigdecimal/util'

J
Jeremy Kemper 已提交
4 5
require 'models/person'
require 'models/topic'
6 7 8 9

require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
10

11
if ActiveRecord::Base.connection.supports_migrations?
12 13
  class BigNumber < ActiveRecord::Base; end

14 15
  class Reminder < ActiveRecord::Base; end

J
Jamis Buck 已提交
16 17 18 19 20 21 22 23 24 25
  class ActiveRecord::Migration
    class <<self
      attr_accessor :message_count
      def puts(text="")
        self.message_count ||= 0
        self.message_count += 1
      end
    end
  end

26
  class MigrationTest < ActiveRecord::TestCase
J
Jamis Buck 已提交
27
    self.use_transactional_fixtures = false
J
Jeremy Kemper 已提交
28

29
    fixtures :people
J
Jamis Buck 已提交
30

31
    def setup
J
Jamis Buck 已提交
32 33
      ActiveRecord::Migration.verbose = true
      PeopleHaveLastNames.message_count = 0
34 35 36 37
    end

    def teardown
      ActiveRecord::Base.connection.initialize_schema_information
38
      ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"
39

40 41 42
      %w(reminders people_reminders prefix_reminders_suffix).each do |table|
        Reminder.connection.drop_table(table) rescue nil
      end
43 44
      Reminder.reset_column_information

45
      %w(last_name key bio age height wealth birthday favorite_day
46
         moment_of_truth male administrator funny).each do |column|
47 48
        Person.connection.remove_column('people', column) rescue nil
      end
49
      Person.connection.remove_column("people", "first_name") rescue nil
50
      Person.connection.remove_column("people", "middle_name") rescue nil
51
      Person.connection.add_column("people", "first_name", :string, :limit => 40)
52 53
      Person.reset_column_information
    end
54

55
    def test_add_index
56 57 58
      # 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
59
      Person.connection.add_column "people", "administrator", :boolean
60

61 62 63
      assert_nothing_raised { Person.connection.add_index("people", "last_name") }
      assert_nothing_raised { Person.connection.remove_index("people", "last_name") }

64
      # Orcl nds shrt indx nms.  Sybs 2.
65 66
      # OpenBase does not have named indexes.  You must specify a single column name
      unless current_adapter?(:OracleAdapter, :SybaseAdapter, :OpenBaseAdapter)
67 68 69 70 71 72 73 74 75
        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"]) }
        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") }
        assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
        assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
      end
76

77
      # quoting
78
      # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
79 80 81 82 83
      # OpenBase does not have named indexes.  You must specify a single column name
      unless current_adapter?(:OpenBaseAdapter)
        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
J
Jeremy Kemper 已提交
84

85
      # Sybase adapter does not support indexes on :boolean columns
86 87
      # OpenBase does not have named indexes.  You must specify a single column
      unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
88 89 90
        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
91
    end
92 93 94 95 96 97 98 99 100 101 102

    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
    end
103 104

    def test_create_table_with_not_null_column
105 106 107 108
      assert_nothing_raised do
        Person.connection.create_table :testings do |t|
          t.column :foo, :string, :null => false
        end
109 110 111 112 113 114 115 116
      end

      assert_raises(ActiveRecord::StatementInvalid) do
        Person.connection.execute "insert into testings (foo) values (NULL)"
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
117 118

    def test_create_table_with_defaults
119 120 121
      # MySQL doesn't allow defaults on TEXT or BLOB columns.
      mysql = current_adapter?(:MysqlAdapter)

122 123 124 125 126
      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
127
        t.column :five, :text, :default => "hello" unless mysql
128 129 130 131 132 133 134
      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" }
135
      five = columns.detect { |c| c.name == "five" } unless mysql
136 137

      assert_equal "hello", one.default
138 139
      assert_equal true, two.default
      assert_equal false, three.default
140
      assert_equal 1, four.default
141
      assert_equal "hello", five.default unless mysql
142 143 144 145

    ensure
      Person.connection.drop_table :testings rescue nil
    end
146 147

    def test_create_table_with_limits
148 149 150
      assert_nothing_raised do
        Person.connection.create_table :testings do |t|
          t.column :foo, :string, :limit => 255
151

152
          t.column :default_int, :integer
153

154 155 156 157
          t.column :one_int,   :integer, :limit => 1
          t.column :four_int,  :integer, :limit => 4
          t.column :eight_int, :integer, :limit => 8
        end
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
      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"   }

      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
174 175 176 177 178
      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
179 180 181 182
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
183

184 185 186
    # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
    # column to a table without a default value.
    unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter)
187 188 189 190 191
      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
192

193 194 195 196 197 198 199
        assert_raises(ActiveRecord::StatementInvalid) do
          Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
        end
      ensure
        Person.connection.drop_table :testings rescue nil
      end
    end
200

201
    def test_add_column_not_null_with_default
202
      Person.connection.create_table :testings do |t|
203 204
        t.column :foo, :string
      end
J
Jeremy Kemper 已提交
205 206

      con = Person.connection
207
      Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
208
      Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
209
      Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
210
      assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
211 212

      assert_raises(ActiveRecord::StatementInvalid) do
213 214 215 216 217 218
        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
219 220 221 222
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
223

224 225 226 227
    # 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
228
      correct_value = '0012345678901234567890.0123456789'.to_d
229 230 231 232 233 234

      Person.delete_all
      Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
      Person.reset_column_information

      # Do a manual insertion
235 236
      if current_adapter?(:OracleAdapter)
        Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
237 238
      elsif current_adapter?(:OpenBaseAdapter)
        Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
239 240 241
      else
        Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
      end
242 243 244 245 246 247

      # SELECT
      row = Person.find(:first)
      assert_kind_of BigDecimal, row.wealth

      # If this assert fails, that means the SELECT is broken!
248 249 250
      unless current_adapter?(:SQLite3Adapter)
        assert_equal correct_value, row.wealth
      end
251 252 253 254 255 256 257 258 259 260 261 262

      # 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

      # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
263 264 265
      unless current_adapter?(:SQLite3Adapter)
        assert_equal correct_value, row.wealth
      end
266 267 268 269 270 271

      # Reset to old state
      Person.connection.del_column "people", "wealth" rescue nil
      Person.reset_column_information
    end

272 273 274 275 276 277 278 279 280
    def test_add_column_with_precision_and_scale
      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
    end
    
281 282 283 284 285 286
    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
287
      Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
288 289
      Person.connection.add_column "people", "birthday", :datetime
      Person.connection.add_column "people", "favorite_day", :date
290
      Person.connection.add_column "people", "moment_of_truth", :datetime
291
      Person.connection.add_column "people", "male", :boolean
292
      Person.reset_column_information
293

294 295 296 297 298
      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,
299
          :moment_of_truth => "1782-10-10 21:40:18", :male => true
300 301 302
      end

      bob = Person.find(:first)
303 304 305 306 307 308 309
      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
      # after the decimal place.
J
Jeremy Kemper 已提交
310

311 312 313
      unless current_adapter?(:SQLite3Adapter)
        assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
      end
J
Jeremy Kemper 已提交
314

315
      assert_equal true, bob.male?
316

317 318 319 320 321
      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
322

323
      if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
324
        # Sybase, and Oracle don't differentiate between date/time
325 326 327 328 329
        assert_equal Time, bob.favorite_day.class
      else
        assert_equal Date, bob.favorite_day.class
      end

330
      # Test DateTime column and defaults, including timezone.
J
Jeremy Kemper 已提交
331
      # FIXME: moment of truth may be Time on 64-bit platforms.
332
      if bob.moment_of_truth.is_a?(DateTime)
333
        assert_equal DateTime.local_offset, bob.moment_of_truth.offset
334 335
        assert_not_equal 0, bob.moment_of_truth.offset
        assert_not_equal "Z", bob.moment_of_truth.zone
336
        assert_equal DateTime::ITALY, bob.moment_of_truth.start
337
      end
338

339
      assert_equal TrueClass, bob.male?.class
340
      assert_kind_of BigDecimal, bob.wealth
341 342
    end

343 344 345 346 347
    if current_adapter?(:MysqlAdapter)
      def test_unabstracted_database_dependent_types
        Person.delete_all

        ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
348
        Person.reset_column_information
349
        Person.create :intelligence_quotient => 300
J
Jeremy Kemper 已提交
350
        jonnyg = Person.find(:first)
351 352 353
        assert_equal 127, jonnyg.intelligence_quotient
        jonnyg.destroy
      ensure
354
        ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
355 356 357
      end
    end

358
    def test_add_remove_single_field_using_string_arguments
359 360
      assert !Person.column_methods_hash.include?(:last_name)

361
      ActiveRecord::Migration.add_column 'people', 'last_name', :string
362 363 364

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
365

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
      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
381 382 383 384

      Person.reset_column_information
      assert !Person.column_methods_hash.include?(:last_name)
    end
385

386 387
    def test_add_rename
      Person.delete_all
388

389
      begin
390
        Person.connection.add_column "people", "girlfriend", :string
391
        Person.reset_column_information
392 393
        Person.create :girlfriend => 'bobette'

394
        Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
395 396

        Person.reset_column_information
397
        bob = Person.find(:first)
398

399 400 401 402 403
        assert_equal "bobette", bob.exgirlfriend
      ensure
        Person.connection.remove_column("people", "girlfriend") rescue nil
        Person.connection.remove_column("people", "exgirlfriend") rescue nil
      end
404

405
    end
406

407 408
    def test_rename_column_using_symbol_arguments
      begin
409
        names_before = Person.find(:all).map(&:first_name)
410 411 412
        Person.connection.rename_column :people, :first_name, :nick_name
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
413
        assert_equal names_before, Person.find(:all).map(&:nick_name)
414 415 416 417 418
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
419

420 421
    def test_rename_column
      begin
422
        names_before = Person.find(:all).map(&:first_name)
423 424 425
        Person.connection.rename_column "people", "first_name", "nick_name"
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
426
        assert_equal names_before, Person.find(:all).map(&:nick_name)
427 428 429 430 431
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
432

433 434 435 436 437 438 439 440 441 442
    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
J
Jeremy Kemper 已提交
443

444 445 446 447 448
    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
449
      Person.connection.add_index :hats, :hat_name
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
      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

482 483 484 485
    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
J
Jeremy Kemper 已提交
486

487 488 489 490
        Topic.connection.change_column "topics", "written_on", :datetime, :null => false
        Topic.reset_column_information
      end
    end
491

492 493 494 495
    def test_rename_table
      begin
        ActiveRecord::Base.connection.create_table :octopuses do |t|
          t.column :url, :string
496
        end
497
        ActiveRecord::Base.connection.rename_table :octopuses, :octopi
498

499
        # Using explicit id in insert for compatibility across all databases
J
Jeremy Kemper 已提交
500
        con = ActiveRecord::Base.connection
501
        con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
502
        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')" }
503
        con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
504 505

        assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
506

507 508 509 510 511
      ensure
        ActiveRecord::Base.connection.drop_table :octopuses rescue nil
        ActiveRecord::Base.connection.drop_table :octopi rescue nil
      end
    end
J
Jeremy Kemper 已提交
512

513
    def test_change_column_nullability
J
Jeremy Kemper 已提交
514
      Person.delete_all
515 516 517 518 519 520 521 522 523 524
      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
525

526 527 528 529 530 531
    def test_rename_table_with_an_index
      begin
        ActiveRecord::Base.connection.create_table :octopuses do |t|
          t.column :url, :string
        end
        ActiveRecord::Base.connection.add_index :octopuses, :url
J
Jeremy Kemper 已提交
532

533 534 535
        ActiveRecord::Base.connection.rename_table :octopuses, :octopi

        # Using explicit id in insert for compatibility across all databases
J
Jeremy Kemper 已提交
536
        con = ActiveRecord::Base.connection
537
        con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
538
        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')" }
539
        con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
540 541 542 543 544 545 546 547 548

        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

549
    def test_change_column
550 551 552 553 554
      Person.connection.add_column 'people', 'age', :integer
      old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
      assert old_columns.find { |c| c.name == 'age' and c.type == :integer }

      assert_nothing_raised { Person.connection.change_column "people", "age", :string }
555

556 557 558
      new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
      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 }
559 560 561 562

      old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
      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 }
563
      new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
564 565
      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 }
566
      assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
567
    end
J
Jeremy Kemper 已提交
568

569 570 571 572
    def test_change_column_with_nil_default
      Person.connection.add_column "people", "contributor", :boolean, :default => true
      Person.reset_column_information
      assert Person.new.contributor?
J
Jeremy Kemper 已提交
573

574 575 576 577
      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
578 579
    ensure
      Person.connection.remove_column("people", "contributor") rescue nil
580
    end
581 582

    def test_change_column_with_new_default
583
      Person.connection.add_column "people", "administrator", :boolean, :default => true
584
      Person.reset_column_information
585
      assert Person.new.administrator?
586

587
      assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
588
      Person.reset_column_information
589
      assert !Person.new.administrator?
590 591
    ensure
      Person.connection.remove_column("people", "administrator") rescue nil
592
    end
J
Jeremy Kemper 已提交
593

594 595 596 597 598
    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
599 600 601 602 603 604 605 606 607 608 609 610 611

    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 }

      assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
    ensure
      Person.connection.drop_table :testings rescue nil
    end

612 613 614 615 616
    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
617 618

    def test_add_table
619
      assert !Reminder.table_exists?
620

621
      WeNeedReminders.up
J
Jeremy Kemper 已提交
622 623

      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
624
      assert_equal "hello world", Reminder.find(:first).content
625

626 627 628 629
      WeNeedReminders.down
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    end

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
    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
665
      # precision/scale explicitly left out.  By the SQL standard, numbers
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
      # assigned to this field should be truncated but that's seldom respected.
      if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter)
        # - 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?(:SQLiteAdapter)
        # - SQLite3 stores a float, in violation of SQL
        assert_kind_of BigDecimal, b.value_of_e
        assert_equal BigDecimal("2.71828182845905"), b.value_of_e
      elsif current_adapter?(:SQLServer)
        # - SQL Server rounds instead of truncating
        assert_kind_of Fixnum, b.value_of_e
        assert_equal 3, b.value_of_e
      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_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
    end

695 696
    def test_migrator
      assert !Person.column_methods_hash.include?(:last_name)
697
      assert !Reminder.table_exists?
698

699
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
700

701
      assert_equal 3, ActiveRecord::Migrator.current_version
702 703 704 705 706
      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

707
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
708 709 710 711 712 713 714 715 716

      assert_equal 0, ActiveRecord::Migrator.current_version
      Person.reset_column_information
      assert !Person.column_methods_hash.include?(:last_name)
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    end

    def test_migrator_one_up
      assert !Person.column_methods_hash.include?(:last_name)
717
      assert !Reminder.table_exists?
718

719
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
720 721 722

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
723
      assert !Reminder.table_exists?
724

725
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
726 727 728 729

      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content
    end
730

731
    def test_migrator_one_down
732
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
733

734
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
735 736 737

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
738
      assert !Reminder.table_exists?
739
    end
740

741
    def test_migrator_one_up_one_down
742 743
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
744 745

      assert !Person.column_methods_hash.include?(:last_name)
746
      assert !Reminder.table_exists?
747
    end
748

J
Jamis Buck 已提交
749
    def test_migrator_verbosity
750
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
J
Jamis Buck 已提交
751 752 753
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0

754
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
J
Jamis Buck 已提交
755 756 757
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0
    end
758

J
Jamis Buck 已提交
759 760
    def test_migrator_verbosity_off
      PeopleHaveLastNames.verbose = false
761
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
J
Jamis Buck 已提交
762
      assert PeopleHaveLastNames.message_count.zero?
763
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
J
Jamis Buck 已提交
764 765
      assert PeopleHaveLastNames.message_count.zero?
    end
766

767
    def test_migrator_going_down_due_to_version_target
768 769
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
770 771

      assert !Person.column_methods_hash.include?(:last_name)
772
      assert !Reminder.table_exists?
773

774
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
775 776 777 778 779 780

      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
    end
781 782 783 784

    def test_schema_info_table_name
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
785
      Reminder.reset_table_name
786 787 788
      assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
789
      Reminder.reset_table_name
790
      assert_equal "schema_info", ActiveRecord::Migrator.schema_info_table_name
791 792 793
    ensure
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
794
    end
795

796 797 798 799
    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)
800
      Reminder.reset_table_name
801
      assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
802

803 804 805 806 807
      # 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'
808
      Reminder.reset_table_name
809 810 811
      assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
      Reminder.table_name_prefix = ''
      Reminder.table_name_suffix = ''
812
      Reminder.reset_table_name
813 814

      # Use AR::Base's prefix/suffix if string or symbol is given
815 816
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
817
      Reminder.reset_table_name
818 819 820 821
      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 = ""
822
      Reminder.reset_table_name
823
    end
824 825

    def test_add_drop_table_with_prefix_and_suffix
826
      assert !Reminder.table_exists?
827 828
      ActiveRecord::Base.table_name_prefix = 'prefix_'
      ActiveRecord::Base.table_name_suffix = '_suffix'
829
      Reminder.reset_table_name
830
      Reminder.reset_sequence_name
831 832 833 834 835 836
      WeNeedReminders.up
      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content

      WeNeedReminders.down
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
837
    ensure
838 839
      ActiveRecord::Base.table_name_prefix = ''
      ActiveRecord::Base.table_name_suffix = ''
840
      Reminder.reset_table_name
841
      Reminder.reset_sequence_name
842
    end
843

844 845
    def test_create_table_with_binary_column
      Person.connection.drop_table :binary_testings rescue nil
846

847 848 849 850 851
      assert_nothing_raised {
        Person.connection.create_table :binary_testings do |t|
          t.column "data", :binary, :null => false
        end
      }
852

853 854
      columns = Person.connection.columns(:binary_testings)
      data_column = columns.detect { |c| c.name == "data" }
855

856
      assert_nil data_column.default
857

858
      Person.connection.drop_table :binary_testings rescue nil
859
    end
860

861 862
    def test_migrator_with_duplicates
      assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
863
        ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
864 865
      end
    end
866 867

    def test_migrator_with_missing_version_numbers
868
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
869
      assert !Person.column_methods_hash.include?(:middle_name)
J
Jeremy Kemper 已提交
870
      assert_equal 4, ActiveRecord::Migrator.current_version
J
Jeremy Kemper 已提交
871

872
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 2)
J
Jeremy Kemper 已提交
873 874
      Person.reset_column_information
      assert !Reminder.table_exists?
J
Jeremy Kemper 已提交
875
      assert Person.column_methods_hash.include?(:last_name)
J
Jeremy Kemper 已提交
876
      assert_equal 2, ActiveRecord::Migrator.current_version
877
    end
878

879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
    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 fail
      assert_raises(ActiveRecord::StatementInvalid) 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_raises(ActiveRecord::StatementInvalid) do
        Person.connection.execute("select suitably_short_seq.nextval from dual")
      end
    end
915
  end
916 917

  uses_mocha 'Sexy migration tests' do
918
    class SexyMigrationsTest < ActiveRecord::TestCase
919 920 921 922 923 924
      def test_references_column_type_adds_id
        with_new_table do |t|
          t.expects(:column).with('customer_id', :integer, {})
          t.references :customer
        end
      end
J
Jeremy Kemper 已提交
925

M
Michael Koziarski 已提交
926
      def test_references_column_type_with_polymorphic_adds_type
927 928 929 930 931 932
        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
J
Jeremy Kemper 已提交
933

934 935 936 937 938 939 940
      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
J
Jeremy Kemper 已提交
941

942 943 944 945 946 947
      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
J
Jeremy Kemper 已提交
948

949 950 951 952 953 954 955
      def test_timestamps_creates_updated_at_and_created_at
        with_new_table do |t|
          t.expects(:column).with(:created_at, :datetime)
          t.expects(:column).with(:updated_at, :datetime)
          t.timestamps
        end
      end
J
Jeremy Kemper 已提交
956

957 958 959 960 961 962 963
      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
J
Jeremy Kemper 已提交
964

965 966 967 968 969 970 971
      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
J
Jeremy Kemper 已提交
972

973 974 975 976 977 978 979 980
      protected
      def with_new_table
        Person.connection.create_table :delete_me do |t|
          yield t
        end
      ensure
        Person.connection.drop_table :delete_me rescue nil
      end
J
Jeremy Kemper 已提交
981

982 983
    end # SexyMigrationsTest
  end # uses_mocha
984
end