migration_test.rb 42.4 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
require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down"
11

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

15 16
  class Reminder < ActiveRecord::Base; end

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

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

30
    fixtures :people
J
Jamis Buck 已提交
31

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

    def teardown
38 39
      ActiveRecord::Base.connection.initialize_schema_migrations_table
      ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
40

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

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

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

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

65
      # Orcl nds shrt indx nms.  Sybs 2.
66 67
      # OpenBase does not have named indexes.  You must specify a single column name
      unless current_adapter?(:OracleAdapter, :SybaseAdapter, :OpenBaseAdapter)
68 69 70 71 72 73 74 75 76
        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
77

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

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

    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
105 106

    def test_create_table_with_not_null_column
107 108 109 110
      assert_nothing_raised do
        Person.connection.create_table :testings do |t|
          t.column :foo, :string, :null => false
        end
111 112 113 114 115 116 117 118
      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
119 120

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

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

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

    ensure
      Person.connection.drop_table :testings rescue nil
    end
148 149

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

154
          t.column :default_int, :integer
155

156 157 158 159
          t.column :one_int,   :integer, :limit => 1
          t.column :four_int,  :integer, :limit => 4
          t.column :eight_int, :integer, :limit => 8
        end
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      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
176 177 178 179 180
      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
181 182 183 184
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
185

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
      ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore

      Person.connection.create_table :testings do |t|
          t.column :foo, :string
      end

      assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort
    ensure
      Person.connection.drop_table :testings rescue nil
      ActiveRecord::Base.primary_key_prefix_type = nil
    end

    def test_create_table_with_primary_key_prefix_as_table_name
      ActiveRecord::Base.primary_key_prefix_type = :table_name

      Person.connection.create_table :testings do |t|
          t.column :foo, :string
      end

      assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort
    ensure
      Person.connection.drop_table :testings rescue nil
      ActiveRecord::Base.primary_key_prefix_type = nil
    end


213 214 215
    # 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)
216 217 218 219 220
      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
221

222 223 224 225 226 227 228
        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
229

230
    def test_add_column_not_null_with_default
231
      Person.connection.create_table :testings do |t|
232 233
        t.column :foo, :string
      end
J
Jeremy Kemper 已提交
234 235

      con = Person.connection
236
      Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
237
      Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
238
      Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
239
      assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
240 241

      assert_raises(ActiveRecord::StatementInvalid) do
242 243 244 245 246 247
        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
248 249 250 251
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
252

253 254 255 256
    # 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
257
      correct_value = '0012345678901234567890.0123456789'.to_d
258 259 260 261 262 263

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

      # Do a manual insertion
264 265
      if current_adapter?(:OracleAdapter)
        Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
266 267
      elsif current_adapter?(:OpenBaseAdapter)
        Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
268 269 270
      else
        Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
      end
271 272 273 274 275 276

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

      # If this assert fails, that means the SELECT is broken!
277 278 279
      unless current_adapter?(:SQLite3Adapter)
        assert_equal correct_value, row.wealth
      end
280 281 282 283 284 285 286 287 288 289 290 291

      # 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!
292 293 294
      unless current_adapter?(:SQLite3Adapter)
        assert_equal correct_value, row.wealth
      end
295 296 297 298 299 300

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

301 302 303 304 305 306 307 308 309
    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
    
310 311 312 313 314 315
    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
316
      Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
317 318
      Person.connection.add_column "people", "birthday", :datetime
      Person.connection.add_column "people", "favorite_day", :date
319
      Person.connection.add_column "people", "moment_of_truth", :datetime
320
      Person.connection.add_column "people", "male", :boolean
321
      Person.reset_column_information
322

323 324 325 326 327
      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,
328
          :moment_of_truth => "1782-10-10 21:40:18", :male => true
329 330 331
      end

      bob = Person.find(:first)
332 333 334 335 336 337 338
      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 已提交
339

340 341 342
      unless current_adapter?(:SQLite3Adapter)
        assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
      end
J
Jeremy Kemper 已提交
343

344
      assert_equal true, bob.male?
345

346 347 348 349 350
      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
351

352
      if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
353
        # Sybase, and Oracle don't differentiate between date/time
354 355 356 357 358
        assert_equal Time, bob.favorite_day.class
      else
        assert_equal Date, bob.favorite_day.class
      end

359
      # Test DateTime column and defaults, including timezone.
J
Jeremy Kemper 已提交
360
      # FIXME: moment of truth may be Time on 64-bit platforms.
361
      if bob.moment_of_truth.is_a?(DateTime)
362 363 364 365 366 367 368 369 370 371

        with_env_tz 'US/Eastern' do
          assert_equal DateTime.local_offset, bob.moment_of_truth.offset
          assert_not_equal 0, bob.moment_of_truth.offset
          assert_not_equal "Z", bob.moment_of_truth.zone
          # US/Eastern is -5 hours from GMT
          assert_equal Rational(-5, 24), bob.moment_of_truth.offset
          assert_equal "-05:00", bob.moment_of_truth.zone
          assert_equal DateTime::ITALY, bob.moment_of_truth.start
        end
372
      end
373

374
      assert_equal TrueClass, bob.male?.class
375
      assert_kind_of BigDecimal, bob.wealth
376 377
    end

378 379 380 381 382
    if current_adapter?(:MysqlAdapter)
      def test_unabstracted_database_dependent_types
        Person.delete_all

        ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
383
        Person.reset_column_information
384
        Person.create :intelligence_quotient => 300
J
Jeremy Kemper 已提交
385
        jonnyg = Person.find(:first)
386 387 388
        assert_equal 127, jonnyg.intelligence_quotient
        jonnyg.destroy
      ensure
389
        ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
390 391 392
      end
    end

393
    def test_add_remove_single_field_using_string_arguments
394 395
      assert !Person.column_methods_hash.include?(:last_name)

396
      ActiveRecord::Migration.add_column 'people', 'last_name', :string
397 398 399

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

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
      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
416 417 418 419

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

421 422
    def test_add_rename
      Person.delete_all
423

424
      begin
425
        Person.connection.add_column "people", "girlfriend", :string
426
        Person.reset_column_information
427 428
        Person.create :girlfriend => 'bobette'

429
        Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
430 431

        Person.reset_column_information
432
        bob = Person.find(:first)
433

434 435 436 437 438
        assert_equal "bobette", bob.exgirlfriend
      ensure
        Person.connection.remove_column("people", "girlfriend") rescue nil
        Person.connection.remove_column("people", "exgirlfriend") rescue nil
      end
439

440
    end
441

442 443
    def test_rename_column_using_symbol_arguments
      begin
444
        names_before = Person.find(:all).map(&:first_name)
445 446 447
        Person.connection.rename_column :people, :first_name, :nick_name
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
448
        assert_equal names_before, Person.find(:all).map(&:nick_name)
449 450 451 452 453
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
454

455 456
    def test_rename_column
      begin
457
        names_before = Person.find(:all).map(&:first_name)
458 459 460
        Person.connection.rename_column "people", "first_name", "nick_name"
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
461
        assert_equal names_before, Person.find(:all).map(&:nick_name)
462 463 464 465 466
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
467

468 469 470 471 472 473 474 475 476 477
    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 已提交
478

479 480 481 482 483
    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
484
      Person.connection.add_index :hats, :hat_name
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
      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

517 518 519 520
    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 已提交
521

522 523 524 525
        Topic.connection.change_column "topics", "written_on", :datetime, :null => false
        Topic.reset_column_information
      end
    end
526

527 528 529 530
    def test_rename_table
      begin
        ActiveRecord::Base.connection.create_table :octopuses do |t|
          t.column :url, :string
531
        end
532
        ActiveRecord::Base.connection.rename_table :octopuses, :octopi
533

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

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

542 543 544 545 546
      ensure
        ActiveRecord::Base.connection.drop_table :octopuses rescue nil
        ActiveRecord::Base.connection.drop_table :octopi rescue nil
      end
    end
J
Jeremy Kemper 已提交
547

548
    def test_change_column_nullability
J
Jeremy Kemper 已提交
549
      Person.delete_all
550 551 552 553 554 555 556 557 558 559
      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
560

561 562 563 564 565 566
    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 已提交
567

568 569 570
        ActiveRecord::Base.connection.rename_table :octopuses, :octopi

        # Using explicit id in insert for compatibility across all databases
J
Jeremy Kemper 已提交
571
        con = ActiveRecord::Base.connection
572
        con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
573
        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')" }
574
        con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
575 576 577 578 579 580 581 582 583

        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

584
    def test_change_column
585 586 587 588 589
      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 }
590

591 592 593
      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 }
594 595 596 597

      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 }
598
      new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
599 600
      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 }
601
      assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
602
    end
J
Jeremy Kemper 已提交
603

604 605 606 607
    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 已提交
608

609 610 611 612
      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
613 614
    ensure
      Person.connection.remove_column("people", "contributor") rescue nil
615
    end
616 617

    def test_change_column_with_new_default
618
      Person.connection.add_column "people", "administrator", :boolean, :default => true
619
      Person.reset_column_information
620
      assert Person.new.administrator?
621

622
      assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
623
      Person.reset_column_information
624
      assert !Person.new.administrator?
625 626
    ensure
      Person.connection.remove_column("people", "administrator") rescue nil
627
    end
J
Jeremy Kemper 已提交
628

629 630 631 632 633
    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
634 635 636 637 638 639 640 641 642 643 644 645 646

    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

647 648 649 650 651
    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
652 653

    def test_add_table
654
      assert !Reminder.table_exists?
655

656
      WeNeedReminders.up
J
Jeremy Kemper 已提交
657 658

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

661 662 663 664
      WeNeedReminders.down
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    end

665 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 695 696 697 698 699
    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
700
      # precision/scale explicitly left out.  By the SQL standard, numbers
701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
      # 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

730 731
    def test_migrator
      assert !Person.column_methods_hash.include?(:last_name)
732
      assert !Reminder.table_exists?
733

734
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
735

736
      assert_equal 3, ActiveRecord::Migrator.current_version
737 738 739 740 741
      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

742
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
743 744 745 746 747 748 749 750 751

      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)
752
      assert !Reminder.table_exists?
753

754
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
755 756 757

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
758
      assert !Reminder.table_exists?
759

760
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
761 762 763 764

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

766
    def test_migrator_one_down
767
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
768
    
769
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
770
    
771 772
      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
773
      assert !Reminder.table_exists?
774
    end
775

776
    def test_migrator_one_up_one_down
777 778
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
779 780

      assert !Person.column_methods_hash.include?(:last_name)
781
      assert !Reminder.table_exists?
782
    end
783

784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
    def test_finds_migrations
      migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
      [['1', 'people_have_last_names'],
       ['2', 'we_need_reminders'],
       ['3', 'innocent_jointable']].each_with_index do |pair, i|
        migrations[i].version == pair.first
        migrations[1].name    == pair.last
      end
    end

    def test_finds_pending_migrations
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1)
      migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations
      assert_equal 1, migrations.size
      migrations[0].version == '3'
      migrations[0].name    == 'innocent_jointable'
    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)

      assert_nothing_raised do
        ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3")
      end
    end

817 818 819 820 821 822 823
    def test_migrator_db_has_no_schema_migrations_table
      ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
      assert_nothing_raised do
        ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
      end
    end

J
Jamis Buck 已提交
824
    def test_migrator_verbosity
825
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
J
Jamis Buck 已提交
826 827 828
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0

829
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
J
Jamis Buck 已提交
830 831 832
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0
    end
833

J
Jamis Buck 已提交
834 835
    def test_migrator_verbosity_off
      PeopleHaveLastNames.verbose = false
836
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
J
Jamis Buck 已提交
837
      assert PeopleHaveLastNames.message_count.zero?
838
      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
J
Jamis Buck 已提交
839 840
      assert PeopleHaveLastNames.message_count.zero?
    end
841

842
    def test_migrator_going_down_due_to_version_target
843 844
      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
845 846

      assert !Person.column_methods_hash.include?(:last_name)
847
      assert !Reminder.table_exists?
848

849
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
850 851 852 853 854 855

      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
856 857 858 859 860 861
    
    def test_migrator_rollback
      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
      assert_equal(3, ActiveRecord::Migrator.current_version)
      
      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
862
      assert_equal(2, ActiveRecord::Migrator.current_version)
863 864
      
      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
865
      assert_equal(1, ActiveRecord::Migrator.current_version)
866 867
      
      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
868
      assert_equal(0, ActiveRecord::Migrator.current_version)
869 870
      
      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
871
      assert_equal(0, ActiveRecord::Migrator.current_version)
872 873 874 875 876 877 878 879 880 881 882
    end
    
    def test_migrator_run
      assert_equal(0, ActiveRecord::Migrator.current_version)
      ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
      assert_equal(0, ActiveRecord::Migrator.current_version)

      assert_equal(0, ActiveRecord::Migrator.current_version)
      ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
      assert_equal(0, ActiveRecord::Migrator.current_version)
    end
883

884
    def test_schema_migrations_table_name
885 886
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
887
      Reminder.reset_table_name
888
      assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
889 890
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
891
      Reminder.reset_table_name
892
      assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
893 894 895
    ensure
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
896
    end
897

898 899 900 901
    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)
902
      Reminder.reset_table_name
903
      assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
904

905 906 907 908 909
      # 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'
910
      Reminder.reset_table_name
911 912 913
      assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
      Reminder.table_name_prefix = ''
      Reminder.table_name_suffix = ''
914
      Reminder.reset_table_name
915 916

      # Use AR::Base's prefix/suffix if string or symbol is given
917 918
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
919
      Reminder.reset_table_name
920 921 922 923
      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 = ""
924
      Reminder.reset_table_name
925
    end
926 927

    def test_add_drop_table_with_prefix_and_suffix
928
      assert !Reminder.table_exists?
929 930
      ActiveRecord::Base.table_name_prefix = 'prefix_'
      ActiveRecord::Base.table_name_suffix = '_suffix'
931
      Reminder.reset_table_name
932
      Reminder.reset_sequence_name
933 934 935 936 937 938
      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) }
939
    ensure
940 941
      ActiveRecord::Base.table_name_prefix = ''
      ActiveRecord::Base.table_name_suffix = ''
942
      Reminder.reset_table_name
943
      Reminder.reset_sequence_name
944
    end
945

946 947
    def test_create_table_with_binary_column
      Person.connection.drop_table :binary_testings rescue nil
948

949 950 951 952 953
      assert_nothing_raised {
        Person.connection.create_table :binary_testings do |t|
          t.column "data", :binary, :null => false
        end
      }
954

955 956
      columns = Person.connection.columns(:binary_testings)
      data_column = columns.detect { |c| c.name == "data" }
957

958
      assert_nil data_column.default
959

960
      Person.connection.drop_table :binary_testings rescue nil
961
    end
962

963 964
    def test_migrator_with_duplicates
      assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
965
        ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
966 967
      end
    end
968 969

    def test_migrator_with_missing_version_numbers
970 971 972
      assert_raise(ActiveRecord::UnknownMigrationVersionError) do
        ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
      end
973
    end
974

975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
    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
1011 1012 1013 1014 1015 1016 1017 1018 1019

    protected
      def with_env_tz(new_tz = 'US/Eastern')
        old_tz, ENV['TZ'] = ENV['TZ'], new_tz
        yield
      ensure
        old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
      end

1020
  end
1021
  
1022
  uses_mocha 'Sexy migration tests' do
1023
    class SexyMigrationsTest < ActiveRecord::TestCase
1024 1025 1026 1027 1028 1029
      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 已提交
1030

M
Michael Koziarski 已提交
1031
      def test_references_column_type_with_polymorphic_adds_type
1032 1033 1034 1035 1036 1037
        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 已提交
1038

1039 1040 1041 1042 1043 1044 1045
      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 已提交
1046

1047 1048 1049 1050 1051 1052
      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 已提交
1053

1054 1055 1056 1057 1058 1059 1060
      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 已提交
1061

1062 1063 1064 1065 1066 1067 1068
      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 已提交
1069

1070 1071 1072 1073 1074 1075 1076
      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 已提交
1077

1078 1079 1080 1081 1082 1083 1084 1085
      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 已提交
1086

1087 1088
    end # SexyMigrationsTest
  end # uses_mocha
1089
end