Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activerecord/test/cases/migration_test.rb

Revision 9244, 41.9 kB (checked in by rick, 1 year ago)

Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi]

Line 
1 require "cases/helper"
2 require 'bigdecimal/util'
3
4 require 'models/person'
5 require 'models/topic'
6
7 require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
8 require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
9 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   class BigNumber < ActiveRecord::Base; end
14
15   class Reminder < ActiveRecord::Base; end
16
17   class ActiveRecord::Migration
18     class <<self
19       attr_accessor :message_count
20       def puts(text="")
21         self.message_count ||= 0
22         self.message_count += 1
23       end
24     end
25   end
26
27   class MigrationTest < ActiveRecord::TestCase
28     self.use_transactional_fixtures = false
29
30     fixtures :people
31
32     def setup
33       ActiveRecord::Migration.verbose = true
34       PeopleHaveLastNames.message_count = 0
35     end
36
37     def teardown
38       ActiveRecord::Base.connection.initialize_schema_migrations_table
39       ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
40
41       %w(reminders people_reminders prefix_reminders_suffix).each do |table|
42         Reminder.connection.drop_table(table) rescue nil
43       end
44       Reminder.reset_column_information
45
46       %w(last_name key bio age height wealth birthday favorite_day
47          moment_of_truth male administrator funny).each do |column|
48         Person.connection.remove_column('people', column) rescue nil
49       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       Person.reset_column_information
54     end
55
56     def test_add_index
57       # Limit size of last_name and key columns to support Firebird index limitations
58       Person.connection.add_column "people", "last_name", :string, :limit => 100
59       Person.connection.add_column "people", "key", :string, :limit => 100
60       Person.connection.add_column "people", "administrator", :boolean
61
62       assert_nothing_raised { Person.connection.add_index("people", "last_name") }
63       assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
64
65       # Orcl nds shrt indx nms.  Sybs 2.
66       # OpenBase does not have named indexes.  You must specify a single column name
67       unless current_adapter?(:OracleAdapter, :SybaseAdapter, :OpenBaseAdapter)
68         assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
69         assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
70         assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
71         assert_nothing_raised { Person.connection.remove_index("people", :name => "index_people_on_last_name_and_first_name") }
72         assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
73         assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
74         assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
75         assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
76       end
77
78       # quoting
79       # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
80       # OpenBase does not have named indexes.  You must specify a single column name
81       unless current_adapter?(:OpenBaseAdapter)
82         assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) }
83         assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) }
84       end
85
86       # Sybase adapter does not support indexes on :boolean columns
87       # OpenBase does not have named indexes.  You must specify a single column
88       unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
89         assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
90         assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
91       end
92     end
93
94     def test_create_table_adds_id
95       Person.connection.create_table :testings do |t|
96         t.column :foo, :string
97       end
98
99       assert_equal %w(foo id),
100         Person.connection.columns(:testings).map { |c| c.name }.sort
101     ensure
102       Person.connection.drop_table :testings rescue nil
103     end
104
105     def test_create_table_with_not_null_column
106       assert_nothing_raised do
107         Person.connection.create_table :testings do |t|
108           t.column :foo, :string, :null => false
109         end
110       end
111
112       assert_raises(ActiveRecord::StatementInvalid) do
113         Person.connection.execute "insert into testings (foo) values (NULL)"
114       end
115     ensure
116       Person.connection.drop_table :testings rescue nil
117     end
118
119     def test_create_table_with_defaults
120       # MySQL doesn't allow defaults on TEXT or BLOB columns.
121       mysql = current_adapter?(:MysqlAdapter)
122
123       Person.connection.create_table :testings do |t|
124         t.column :one, :string, :default => "hello"
125         t.column :two, :boolean, :default => true
126         t.column :three, :boolean, :default => false
127         t.column :four, :integer, :default => 1
128         t.column :five, :text, :default => "hello" unless mysql
129       end
130
131       columns = Person.connection.columns(:testings)
132       one = columns.detect { |c| c.name == "one" }
133       two = columns.detect { |c| c.name == "two" }
134       three = columns.detect { |c| c.name == "three" }
135       four = columns.detect { |c| c.name == "four" }
136       five = columns.detect { |c| c.name == "five" } unless mysql
137
138       assert_equal "hello", one.default
139       assert_equal true, two.default
140       assert_equal false, three.default
141       assert_equal 1, four.default
142       assert_equal "hello", five.default unless mysql
143
144     ensure
145       Person.connection.drop_table :testings rescue nil
146     end
147
148     def test_create_table_with_limits
149       assert_nothing_raised do
150         Person.connection.create_table :testings do |t|
151           t.column :foo, :string, :limit => 255
152
153           t.column :default_int, :integer
154
155           t.column :one_int,   :integer, :limit => 1
156           t.column :four_int,  :integer, :limit => 4
157           t.column :eight_int, :integer, :limit => 8
158         end
159       end
160
161       columns = Person.connection.columns(:testings)
162       foo = columns.detect { |c| c.name == "foo" }
163       assert_equal 255, foo.limit
164
165       default = columns.detect { |c| c.name == "default_int" }
166       one     = columns.detect { |c| c.name == "one_int"     }
167       four    = columns.detect { |c| c.name == "four_int"    }
168       eight   = columns.detect { |c| c.name == "eight_int"   }
169
170       if current_adapter?(:PostgreSQLAdapter)
171         assert_equal 'integer', default.sql_type
172         assert_equal 'smallint', one.sql_type
173         assert_equal 'integer', four.sql_type
174         assert_equal 'bigint', eight.sql_type
175       elsif current_adapter?(:OracleAdapter)
176         assert_equal 'NUMBER(38)', default.sql_type
177         assert_equal 'NUMBER(1)', one.sql_type
178         assert_equal 'NUMBER(4)', four.sql_type
179         assert_equal 'NUMBER(8)', eight.sql_type
180       end
181     ensure
182       Person.connection.drop_table :testings rescue nil
183     end
184
185     def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
186       ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
187
188       Person.connection.create_table :testings do |t|
189           t.column :foo, :string
190       end
191
192       assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort
193     ensure
194       Person.connection.drop_table :testings rescue nil
195       ActiveRecord::Base.primary_key_prefix_type = nil
196     end
197
198     def test_create_table_with_primary_key_prefix_as_table_name
199       ActiveRecord::Base.primary_key_prefix_type = :table_name
200
201       Person.connection.create_table :testings do |t|
202           t.column :foo, :string
203       end
204
205       assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort
206     ensure
207       Person.connection.drop_table :testings rescue nil
208       ActiveRecord::Base.primary_key_prefix_type = nil
209     end
210
211
212     # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
213     # column to a table without a default value.
214     unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter)
215       def test_add_column_not_null_without_default
216         Person.connection.create_table :testings do |t|
217           t.column :foo, :string
218         end
219         Person.connection.add_column :testings, :bar, :string, :null => false
220
221         assert_raises(ActiveRecord::StatementInvalid) do
222           Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
223         end
224       ensure
225         Person.connection.drop_table :testings rescue nil
226       end
227     end
228
229     def test_add_column_not_null_with_default
230       Person.connection.create_table :testings do |t|
231         t.column :foo, :string
232       end
233
234       con = Person.connection
235       Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
236       Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
237       Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
238       assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
239
240       assert_raises(ActiveRecord::StatementInvalid) do
241         unless current_adapter?(:OpenBaseAdapter)
242           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)"
243         else
244           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)",
245             "Testing Insert","id",2)
246         end
247       end
248     ensure
249       Person.connection.drop_table :testings rescue nil
250     end
251
252     # We specifically do a manual INSERT here, and then test only the SELECT
253     # functionality. This allows us to more easily catch INSERT being broken,
254     # but SELECT actually working fine.
255     def test_native_decimal_insert_manual_vs_automatic
256       correct_value = '0012345678901234567890.0123456789'.to_d
257
258       Person.delete_all
259       Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
260       Person.reset_column_information
261
262       # Do a manual insertion
263       if current_adapter?(:OracleAdapter)
264         Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
265       elsif current_adapter?(:OpenBaseAdapter)
266         Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
267       else
268         Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
269       end
270
271       # SELECT
272       row = Person.find(:first)
273       assert_kind_of BigDecimal, row.wealth
274
275       # If this assert fails, that means the SELECT is broken!
276       unless current_adapter?(:SQLite3Adapter)
277         assert_equal correct_value, row.wealth
278       end
279
280       # Reset to old state
281       Person.delete_all
282
283       # Now use the Rails insertion
284       assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") }
285
286       # SELECT
287       row = Person.find(:first)
288       assert_kind_of BigDecimal, row.wealth
289
290       # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
291       unless current_adapter?(:SQLite3Adapter)
292         assert_equal correct_value, row.wealth
293       end
294
295       # Reset to old state
296       Person.connection.del_column "people", "wealth" rescue nil
297       Person.reset_column_information
298     end
299
300     def test_add_column_with_precision_and_scale
301       Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
302       Person.reset_column_information
303
304       wealth_column = Person.columns_hash['wealth']
305       assert_equal 9, wealth_column.precision
306       assert_equal 7, wealth_column.scale
307     end
308    
309     def test_native_types
310       Person.delete_all
311       Person.connection.add_column "people", "last_name", :string
312       Person.connection.add_column "people", "bio", :text
313       Person.connection.add_column "people", "age", :integer
314       Person.connection.add_column "people", "height", :float
315       Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
316       Person.connection.add_column "people", "birthday", :datetime
317       Person.connection.add_column "people", "favorite_day", :date
318       Person.connection.add_column "people", "moment_of_truth", :datetime
319       Person.connection.add_column "people", "male", :boolean
320       Person.reset_column_information
321
322       assert_nothing_raised do
323         Person.create :first_name => 'bob', :last_name => 'bobsen',
324           :bio => "I was born ....", :age => 18, :height => 1.78,
325           :wealth => BigDecimal.new("12345678901234567890.0123456789"),
326           :birthday => 18.years.ago, :favorite_day => 10.days.ago,
327           :moment_of_truth => "1782-10-10 21:40:18", :male => true
328       end
329
330       bob = Person.find(:first)
331       assert_equal 'bob', bob.first_name
332       assert_equal 'bobsen', bob.last_name
333       assert_equal "I was born ....", bob.bio
334       assert_equal 18, bob.age
335
336       # Test for 30 significent digits (beyond the 16 of float), 10 of them
337       # after the decimal place.
338
339       unless current_adapter?(:SQLite3Adapter)
340         assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
341       end
342
343       assert_equal true, bob.male?
344
345       assert_equal String, bob.first_name.class
346       assert_equal String, bob.last_name.class
347       assert_equal String, bob.bio.class
348       assert_equal Fixnum, bob.age.class
349       assert_equal Time, bob.birthday.class
350
351       if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
352         # Sybase, and Oracle don't differentiate between date/time
353         assert_equal Time, bob.favorite_day.class
354       else
355         assert_equal Date, bob.favorite_day.class
356       end
357
358       # Test DateTime column and defaults, including timezone.
359       # FIXME: moment of truth may be Time on 64-bit platforms.
360       if bob.moment_of_truth.is_a?(DateTime)
361
362         with_env_tz 'US/Eastern' do
363           assert_equal DateTime.local_offset, bob.moment_of_truth.offset
364           assert_not_equal 0, bob.moment_of_truth.offset
365           assert_not_equal "Z", bob.moment_of_truth.zone
366           # US/Eastern is -5 hours from GMT
367           assert_equal Rational(-5, 24), bob.moment_of_truth.offset
368           assert_equal "-05:00", bob.moment_of_truth.zone
369           assert_equal DateTime::ITALY, bob.moment_of_truth.start
370         end
371       end
372
373       assert_equal TrueClass, bob.male?.class
374       assert_kind_of BigDecimal, bob.wealth
375     end
376
377     if current_adapter?(:MysqlAdapter)
378       def test_unabstracted_database_dependent_types
379         Person.delete_all
380
381         ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
382         Person.reset_column_information
383         Person.create :intelligence_quotient => 300
384         jonnyg = Person.find(:first)
385         assert_equal 127, jonnyg.intelligence_quotient
386         jonnyg.destroy
387       ensure
388         ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
389       end
390     end
391
392     def test_add_remove_single_field_using_string_arguments
393       assert !Person.column_methods_hash.include?(:last_name)
394
395       ActiveRecord::Migration.add_column 'people', 'last_name', :string
396
397       Person.reset_column_information
398       assert Person.column_methods_hash.include?(:last_name)
399
400       ActiveRecord::Migration.remove_column 'people', 'last_name'
401
402       Person.reset_column_information
403       assert !Person.column_methods_hash.include?(:last_name)
404     end
405
406     def test_add_remove_single_field_using_symbol_arguments
407       assert !Person.column_methods_hash.include?(:last_name)
408
409       ActiveRecord::Migration.add_column :people, :last_name, :string
410
411       Person.reset_column_information
412       assert Person.column_methods_hash.include?(:last_name)
413
414       ActiveRecord::Migration.remove_column :people, :last_name
415
416       Person.reset_column_information
417       assert !Person.column_methods_hash.include?(:last_name)
418     end
419
420     def test_add_rename
421       Person.delete_all
422
423       begin
424         Person.connection.add_column "people", "girlfriend", :string
425         Person.reset_column_information
426         Person.create :girlfriend => 'bobette'
427
428         Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
429
430         Person.reset_column_information
431         bob = Person.find(:first)
432
433         assert_equal "bobette", bob.exgirlfriend
434       ensure
435         Person.connection.remove_column("people", "girlfriend") rescue nil
436         Person.connection.remove_column("people", "exgirlfriend") rescue nil
437       end
438
439     end
440
441     def test_rename_column_using_symbol_arguments
442       begin
443         names_before = Person.find(:all).map(&:first_name)
444         Person.connection.rename_column :people, :first_name, :nick_name
445         Person.reset_column_information
446         assert Person.column_names.include?("nick_name")
447         assert_equal names_before, Person.find(:all).map(&:nick_name)
448       ensure
449         Person.connection.remove_column("people","nick_name")
450         Person.connection.add_column("people","first_name", :string)
451       end
452     end
453
454     def test_rename_column
455       begin
456         names_before = Person.find(:all).map(&:first_name)
457         Person.connection.rename_column "people", "first_name", "nick_name"
458         Person.reset_column_information
459         assert Person.column_names.include?("nick_name")
460         assert_equal names_before, Person.find(:all).map(&:nick_name)
461       ensure
462         Person.connection.remove_column("people","nick_name")
463         Person.connection.add_column("people","first_name", :string)
464       end
465     end
466
467     def test_rename_column_with_sql_reserved_word
468       begin
469         assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
470         Person.reset_column_information
471         assert Person.column_names.include?("group")
472       ensure
473         Person.connection.remove_column("people", "group") rescue nil
474         Person.connection.add_column("people", "first_name", :string) rescue nil
475       end
476     end
477
478     def test_rename_column_with_an_index
479       ActiveRecord::Base.connection.create_table(:hats) do |table|
480         table.column :hat_name, :string, :limit => 100
481         table.column :hat_size, :integer
482       end
483       Person.connection.add_index :hats, :hat_name
484       assert_nothing_raised do
485         Person.connection.rename_column "hats", "hat_name", "name"
486       end
487     ensure
488       ActiveRecord::Base.connection.drop_table(:hats)
489     end
490
491     def test_remove_column_with_index
492       ActiveRecord::Base.connection.create_table(:hats) do |table|
493         table.column :hat_name, :string, :limit => 100
494         table.column :hat_size, :integer
495       end
496       ActiveRecord::Base.connection.add_index "hats", "hat_size"
497
498       assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
499     ensure
500       ActiveRecord::Base.connection.drop_table(:hats)
501     end
502
503     def test_remove_column_with_multi_column_index
504       ActiveRecord::Base.connection.create_table(:hats) do |table|
505         table.column :hat_name, :string, :limit => 100
506         table.column :hat_size, :integer
507         table.column :hat_style, :string, :limit => 100
508       end
509       ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
510
511       assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
512     ensure
513       ActiveRecord::Base.connection.drop_table(:hats)
514     end
515
516     def test_change_type_of_not_null_column
517       assert_nothing_raised do
518         Topic.connection.change_column "topics", "written_on", :datetime, :null => false
519         Topic.reset_column_information
520
521         Topic.connection.change_column "topics", "written_on", :datetime, :null => false
522         Topic.reset_column_information
523       end
524     end
525
526     def test_rename_table
527       begin
528         ActiveRecord::Base.connection.create_table :octopuses do |t|
529           t.column :url, :string
530         end
531         ActiveRecord::Base.connection.rename_table :octopuses, :octopi
532
533         # Using explicit id in insert for compatibility across all databases
534         con = ActiveRecord::Base.connection
535         con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
536         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')" }
537         con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
538
539         assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
540
541       ensure
542         ActiveRecord::Base.connection.drop_table :octopuses rescue nil
543         ActiveRecord::Base.connection.drop_table :octopi rescue nil
544       end
545     end
546
547     def test_change_column_nullability
548       Person.delete_all
549       Person.connection.add_column "people", "funny", :boolean
550       Person.reset_column_information
551       assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
552       Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true
553       Person.reset_column_information
554       assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
555       Person.connection.change_column "people", "funny", :boolean, :null => true
556       Person.reset_column_information
557       assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
558     end
559
560     def test_rename_table_with_an_index
561       begin
562         ActiveRecord::Base.connection.create_table :octopuses do |t|
563           t.column :url, :string
564         end
565         ActiveRecord::Base.connection.add_index :octopuses, :url
566
567         ActiveRecord::Base.connection.rename_table :octopuses, :octopi
568
569         # Using explicit id in insert for compatibility across all databases
570         con = ActiveRecord::Base.connection
571         con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
572         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')" }
573         con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
574
575         assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
576         assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url")
577       ensure
578         ActiveRecord::Base.connection.drop_table :octopuses rescue nil
579         ActiveRecord::Base.connection.drop_table :octopi rescue nil
580       end
581     end
582
583     def test_change_column
584       Person.connection.add_column 'people', 'age', :integer
585       old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
586       assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
587
588       assert_nothing_raised { Person.connection.change_column "people", "age", :string }
589
590       new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
591       assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
592       assert new_columns.find { |c| c.name == 'age' and c.type == :string }
593
594       old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
595       assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
596       assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false }
597       new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
598       assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
599       assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
600       assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
601     end
602
603     def test_change_column_with_nil_default
604       Person.connection.add_column "people", "contributor", :boolean, :default => true
605       Person.reset_column_information
606       assert Person.new.contributor?
607
608       assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil }
609       Person.reset_column_information
610       assert !Person.new.contributor?
611       assert_nil Person.new.contributor
612     ensure
613       Person.connection.remove_column("people", "contributor") rescue nil
614     end
615
616     def test_change_column_with_new_default
617       Person.connection.add_column "people", "administrator", :boolean, :default => true
618       Person.reset_column_information
619       assert Person.new.administrator?
620
621       assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
622       Person.reset_column_information
623       assert !Person.new.administrator?
624     ensure
625       Person.connection.remove_column("people", "administrator") rescue nil
626     end
627
628     def test_change_column_default
629       Person.connection.change_column_default "people", "first_name", "Tester"
630       Person.reset_column_information
631       assert_equal "Tester", Person.new.first_name
632     end
633
634     def test_change_column_quotes_column_names
635       Person.connection.create_table :testings do |t|
636         t.column :select, :string
637       end
638
639       assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 }
640
641       assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
642     ensure
643       Person.connection.drop_table :testings rescue nil
644     end
645
646     def test_change_column_default_to_null
647       Person.connection.change_column_default "people", "first_name", nil
648       Person.reset_column_information
649       assert_nil Person.new.first_name
650     end
651
652     def test_add_table
653       assert !Reminder.table_exists?
654
655       WeNeedReminders.up
656
657       assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
658       assert_equal "hello world", Reminder.find(:first).content
659
660       WeNeedReminders.down
661       assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
662     end
663
664     def test_add_table_with_decimals
665       Person.connection.drop_table :big_numbers rescue nil
666
667       assert !BigNumber.table_exists?
668       GiveMeBigNumbers.up
669
670       assert BigNumber.create(
671         :bank_balance => 1586.43,
672         :big_bank_balance => BigDecimal("1000234000567.95"),
673         :world_population => 6000000000,
674         :my_house_population => 3,
675         :value_of_e => BigDecimal("2.7182818284590452353602875")
676       )
677
678       b = BigNumber.find(:first)
679       assert_not_nil b
680
681       assert_not_nil b.bank_balance
682       assert_not_nil b.big_bank_balance
683       assert_not_nil b.world_population
684       assert_not_nil b.my_house_population
685       assert_not_nil b.value_of_e
686
687       # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
688       # is_a?(Bignum)
689       assert_kind_of Integer, b.world_population
690       assert_equal 6000000000, b.world_population
691       assert_kind_of Fixnum, b.my_house_population
692       assert_equal 3, b.my_house_population
693       assert_kind_of BigDecimal, b.bank_balance
694       assert_equal BigDecimal("1586.43"), b.bank_balance
695       assert_kind_of BigDecimal, b.big_bank_balance
696       assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
697
698       # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
699       # precision/scale explicitly left out.  By the SQL standard, numbers
700       # assigned to this field should be truncated but that's seldom respected.
701       if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter)
702         # - PostgreSQL changes the SQL spec on columns declared simply as
703         # "decimal" to something more useful: instead of being given a scale
704         # of 0, they take on the compile-time limit for precision and scale,
705         # so the following should succeed unless you have used really wacky
706         # compilation options
707         # - SQLite2 has the default behavior of preserving all data sent in,
708         # so this happens there too
709         assert_kind_of BigDecimal, b.value_of_e
710         assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
711       elsif current_adapter?(:SQLiteAdapter)
712         # - SQLite3 stores a float, in violation of SQL
713         assert_kind_of BigDecimal, b.value_of_e
714         assert_equal BigDecimal("2.71828182845905"), b.value_of_e
715       elsif current_adapter?(:SQLServer)
716         # - SQL Server rounds instead of truncating
717         assert_kind_of Fixnum, b.value_of_e
718         assert_equal 3, b.value_of_e
719       else
720         # - SQL standard is an integer
721         assert_kind_of Fixnum, b.value_of_e
722         assert_equal 2, b.value_of_e
723       end
724
725       GiveMeBigNumbers.down
726       assert_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
727     end
728
729     def test_migrator
730       assert !Person.column_methods_hash.include?(:last_name)
731       assert !Reminder.table_exists?
732
733       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
734
735       assert_equal 3, ActiveRecord::Migrator.current_version
736       Person.reset_column_information
737       assert Person.column_methods_hash.include?(:last_name)
738       assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
739       assert_equal "hello world", Reminder.find(:first).content
740
741       ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
742
743       assert_equal 0, ActiveRecord::Migrator.current_version
744       Person.reset_column_information
745       assert !Person.column_methods_hash.include?(:last_name)
746       assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
747     end
748
749     def test_migrator_one_up
750       assert !Person.column_methods_hash.include?(:last_name)
751       assert !Reminder.table_exists?
752
753       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
754
755       Person.reset_column_information
756       assert Person.column_methods_hash.include?(:last_name)
757       assert !Reminder.table_exists?
758
759       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
760
761       assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
762       assert_equal "hello world", Reminder.find(:first).content
763     end
764
765     def test_migrator_one_down
766       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
767    
768       ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
769    
770       Person.reset_column_information
771       assert Person.column_methods_hash.include?(:last_name)
772       assert !Reminder.table_exists?
773     end
774
775     def test_migrator_one_up_one_down
776       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
777       ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
778
779       assert !Person.column_methods_hash.include?(:last_name)
780       assert !Reminder.table_exists?
781     end
782
783     def test_finds_migrations
784       migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
785       [['1', 'people_have_last_names'],
786        ['2', 'we_need_reminders'],
787        ['3', 'innocent_jointable']].each_with_index do |pair, i|
788         migrations[i].version == pair.first
789         migrations[1].name    == pair.last
790       end
791     end
792
793     def test_finds_pending_migrations
794       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1)
795       migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations
796       assert_equal 1, migrations.size
797       migrations[0].version == '3'
798       migrations[0].name    == 'innocent_jointable'
799     end
800
801     def test_migrator_interleaved_migrations
802       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
803
804       assert_nothing_raised do
805         ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
806       end
807
808       Person.reset_column_information
809       assert Person.column_methods_hash.include?(:last_name)
810
811       assert_nothing_raised do
812         ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3")
813       end
814     end
815
816     def test_migrator_verbosity
817       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
818       assert PeopleHaveLastNames.message_count > 0
819       PeopleHaveLastNames.message_count = 0
820
821       ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
822       assert PeopleHaveLastNames.message_count > 0
823       PeopleHaveLastNames.message_count = 0
824     end
825
826     def test_migrator_verbosity_off
827       PeopleHaveLastNames.verbose = false
828       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
829       assert PeopleHaveLastNames.message_count.zero?
830       ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
831       assert PeopleHaveLastNames.message_count.zero?
832     end
833
834     def test_migrator_going_down_due_to_version_target
835       ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
836       ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
837
838       assert !Person.column_methods_hash.include?(:last_name)
839       assert !Reminder.table_exists?
840
841       ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
842
843       Person.reset_column_information
844       assert Person.column_methods_hash.include?(:last_name)
845       assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
846       assert_equal "hello world", Reminder.find(:first).content
847     end
848    
849     def test_migrator_rollback
850       ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
851       assert_equal(3, ActiveRecord::Migrator.current_version)
852      
853       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
854       assert_equal(2, ActiveRecord::Migrator.current_version)
855      
856       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
857       assert_equal(1, ActiveRecord::Migrator.current_version)
858      
859       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
860       assert_equal(0, ActiveRecord::Migrator.current_version)
861      
862       ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
863       assert_equal(0, ActiveRecord::Migrator.current_version)
864     end
865    
866     def test_migrator_run
867       assert_equal(0, ActiveRecord::Migrator.current_version)
868       ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
869       assert_equal(0, ActiveRecord::Migrator.current_version)
870
871       assert_equal(0, ActiveRecord::Migrator.current_version)
872       ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
873       assert_equal(0, ActiveRecord::Migrator.current_version)
874     end
875
876     def test_schema_migrations_table_name
877       ActiveRecord::Base.table_name_prefix = "prefix_"
878       ActiveRecord::Base.table_name_suffix = "_suffix"
879       Reminder.reset_table_name
880       assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
881       ActiveRecord::Base.table_name_prefix = ""
882       ActiveRecord::Base.table_name_suffix = ""
883       Reminder.reset_table_name
884       assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
885     ensure
886       ActiveRecord::Base.table_name_prefix = ""
887       ActiveRecord::Base.table_name_suffix = ""
888     end
889
890     def test_proper_table_name
891       assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
892       assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
893       assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
894       Reminder.reset_table_name
895       assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
896
897       # Use the model's own prefix/suffix if a model is given
898       ActiveRecord::Base.table_name_prefix = "ARprefix_"
899       ActiveRecord::Base.table_name_suffix = "_ARsuffix"
900       Reminder.table_name_prefix = 'prefix_'
901       Reminder.table_name_suffix = '_suffix'
902       Reminder.reset_table_name
903       assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
904       Reminder.table_name_prefix = ''
905       Reminder.table_name_suffix = ''
906       Reminder.reset_table_name
907
908       # Use AR::Base's prefix/suffix if string or symbol is given
909       ActiveRecord::Base.table_name_prefix = "prefix_"
910       ActiveRecord::Base.table_name_suffix = "_suffix"
911       Reminder.reset_table_name
912       assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
913       assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
914       ActiveRecord::Base.table_name_prefix = ""
915       ActiveRecord::Base.table_name_suffix = ""
916       Reminder.reset_table_name
917     end
918
919     def test_add_drop_table_with_prefix_and_suffix
920       assert !Reminder.table_exists?
921       ActiveRecord::Base.table_name_prefix = 'prefix_'
922       ActiveRecord::Base.table_name_suffix = '_suffix'
923       Reminder.reset_table_name
924       Reminder.reset_sequence_name
925       WeNeedReminders.up
926       assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
927       assert_equal "hello world", Reminder.find(:first).content
928
929       WeNeedReminders.down
930       assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
931     ensure
932       ActiveRecord::Base.table_name_prefix = ''
933       ActiveRecord::Base.table_name_suffix = ''
934       Reminder.reset_table_name
935       Reminder.reset_sequence_name
936     end
937
938     def test_create_table_with_binary_column
939       Person.connection.drop_table :binary_testings rescue nil
940
941       assert_nothing_raised {
942         Person.connection.create_table :binary_testings do |t|
943           t.column "data", :binary, :null => false
944         end
945       }
946
947       columns = Person.connection.columns(:binary_testings)
948       data_column = columns.detect { |c| c.name == "data" }
949
950       assert_nil data_column.default
951
952       Person.connection.drop_table :binary_testings rescue nil
953     end
954
955     def test_migrator_with_duplicates
956       assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
957         ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
958       end
959     end
960
961     def test_migrator_with_missing_version_numbers
962       assert_raise(ActiveRecord::UnknownMigrationVersionError) do
963         ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
964       end
965     end
966
967     def test_create_table_with_custom_sequence_name
968       return unless current_adapter? :OracleAdapter
969
970       # table name is 29 chars, the standard sequence name will
971       # be 33 chars and fail
972       assert_raises(ActiveRecord::StatementInvalid) do
973         begin
974           Person.connection.create_table :table_with_name_thats_just_ok do |t|
975             t.column :foo, :string, :null => false
976           end
977         ensure
978           Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
979         end
980       end
981
982       # should be all good w/ a custom sequence name
983       assert_nothing_raised do
984         begin
985           Person.connection.create_table :table_with_name_thats_just_ok,
986                                          :sequence_name => 'suitably_short_seq' do |t|
987             t.column :foo, :string, :null => false
988           end
989
990           Person.connection.execute("select suitably_short_seq.nextval from dual")
991
992         ensure
993           Person.connection.drop_table :table_with_name_thats_just_ok,
994                                        :sequence_name => 'suitably_short_seq' rescue nil
995         end
996       end
997
998       # confirm the custom sequence got dropped
999       assert_raises(ActiveRecord::StatementInvalid) do
1000         Person.connection.execute("select suitably_short_seq.nextval from dual")
1001       end
1002     end
1003
1004     protected
1005       def with_env_tz(new_tz = 'US/Eastern')
1006         old_tz, ENV['TZ'] = ENV['TZ'], new_tz
1007         yield
1008       ensure
1009         old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
1010       end
1011
1012   end
1013
1014   uses_mocha 'Sexy migration tests' do
1015     class SexyMigrationsTest < ActiveRecord::TestCase
1016       def test_references_column_type_adds_id
1017         with_new_table do |t|
1018           t.expects(:column).with('customer_id', :integer, {})
1019           t.references :customer
1020         end
1021       end
1022
1023       def test_references_column_type_with_polymorphic_adds_type
1024         with_new_table do |t|
1025           t.expects(:column).with('taggable_type', :string, {})
1026           t.expects(:column).with('taggable_id', :integer, {})
1027           t.references :taggable, :polymorphic => true
1028         end
1029       end
1030
1031       def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
1032         with_new_table do |t|
1033           t.expects(:column).with('taggable_type', :string, {:null => false})
1034           t.expects(:column).with('taggable_id', :integer, {:null => false})
1035           t.references :taggable, :polymorphic => true, :null => false
1036         end
1037       end
1038
1039       def test_belongs_to_works_like_references
1040         with_new_table do |t|
1041           t.expects(:column).with('customer_id', :integer, {})
1042           t.belongs_to :customer
1043         end
1044       end
1045
1046       def test_timestamps_creates_updated_at_and_created_at
1047         with_new_table do |t|
1048           t.expects(:column).with(:created_at, :datetime)
1049           t.expects(:column).with(:updated_at, :datetime)
1050           t.timestamps
1051         end
1052       end
1053
1054       def test_integer_creates_integer_column
1055         with_new_table do |t|
1056           t.expects(:column).with(:foo, 'integer', {})
1057           t.expects(:column).with(:bar, 'integer', {})
1058           t.integer :foo, :bar
1059         end
1060       end
1061
1062       def test_string_creates_string_column
1063         with_new_table do |t|
1064           t.expects(:column).with(:foo, 'string', {})
1065           t.expects(:column).with(:bar, 'string', {})
1066           t.string :foo, :bar
1067         end
1068       end
1069
1070       protected
1071       def with_new_table
1072         Person.connection.create_table :delete_me do |t|
1073           yield t
1074         end
1075       ensure
1076         Person.connection.drop_table :delete_me rescue nil
1077       end
1078
1079     end # SexyMigrationsTest
1080   end # uses_mocha
1081 end
Note: See TracBrowser for help on using the browser.