Ticket #11493: interleaved_migrations_with_tests_and_docs.diff
| File interleaved_migrations_with_tests_and_docs.diff, 21.5 kB (added by jordi, 1 month ago) |
|---|
-
a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
old new 232 232 233 233 # Should not be called normally, but this operation is non-destructive. 234 234 # The migrations module handles this automatically. 235 def initialize_schema_information(current_version=0) 236 begin 237 execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})" 238 execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})" 239 rescue ActiveRecord::StatementInvalid 240 # Schema has been initialized, make sure version is a string 241 version_column = columns(:schema_info).detect { |c| c.name == "version" } 242 243 # can't just alter the table, since SQLite can't deal 244 unless version_column.type == :string 245 version = ActiveRecord::Migrator.current_version 246 execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}" 247 initialize_schema_information(version) 235 def initialize_schema_migrations_table 236 sm_table = ActiveRecord::Migrator.schema_migrations_table_name 237 238 unless tables.detect { |t| t == sm_table } 239 create_table(sm_table, :id => false) do |schema_migrations_table| 240 schema_migrations_table.column :version, :string, :null => false 248 241 end 249 end 250 end 242 add_index sm_table, :version, :unique => true, 243 :name => 'unique_schema_migrations' 244 245 # Backwards-compatibility: if we find schema_info, assume we've 246 # migrated up to that point: 247 si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix 248 249 if tables.detect { |t| t == si_table } 251 250 252 def dump_schema_information #:nodoc: 253 begin 254 if (current_schema = ActiveRecord::Migrator.current_version) > 0 255 return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})" 251 old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i 252 assume_migrated_upto_version(old_version) 253 drop_table(si_table) 256 254 end 257 rescue ActiveRecord::StatementInvalid258 # No Schema Info259 255 end 260 256 end 261 257 258 def assume_migrated_upto_version(version) 259 sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) 260 migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) 261 versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename| 262 filename.split('/').last.split('_').first.to_i 263 end 264 265 execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i) 266 (versions - migrated).select { |v| v < version.to_i }.each do |v| 267 execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" 268 end 269 end 262 270 263 271 def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: 264 272 if native = native_database_types[type] -
a/activerecord/lib/active_record/migration.rb
old new 123 123 # 124 124 # To run migrations against the currently configured database, use 125 125 # <tt>rake db:migrate</tt>. This will update the database by running all of the 126 # pending migrations, creating the <tt>schema_info</tt> table if missing. 126 # pending migrations, creating the <tt>schema_migrations</tt> table 127 # (see "About the schema_migrations table" section below) if missing. 127 128 # 128 129 # To roll the database back to a previous migration version, use 129 130 # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which … … 216 217 # 217 218 # The phrase "Updating salaries..." would then be printed, along with the 218 219 # benchmark for the block when the block completes. 220 # 221 # == About the schema_migrations table 222 # 223 # Rails versions 2.0 and prior used to create a table called 224 # <tt>schema_info</tt> when using migrations. This table contained the 225 # version of the schema as of the last applied migration. 226 # 227 # Starting with Rails 2.1, the <tt>schema_info</tt> table is 228 # (automatically) replaced by the <tt>schema_migrations</tt> table, which 229 # contains the version numbers of all the migrations applied. 230 # 231 # As a result, it is now possible to add migration files that are numbered 232 # lower than the current schema version: when migrating up, those 233 # never-applied "interleaved" migrations will be automatically applied, and 234 # when migrating down, never-applied "interleaved" migrations will be skipped. 219 235 class Migration 220 236 @@verbose = true 221 237 cattr_accessor :verbose … … 315 331 class << self 316 332 def migrate(migrations_path, target_version = nil) 317 333 case 318 when target_version.nil?, current_version < target_version 319 up(migrations_path, target_version) 320 when current_version > target_version 321 down(migrations_path, target_version) 322 when current_version == target_version 323 return # You're on the right version 334 when target_version.nil? then up(migrations_path, target_version) 335 when current_version > target_version then down(migrations_path, target_version) 336 else up(migrations_path, target_version) 324 337 end 325 338 end 326 339 327 340 def rollback(migrations_path, steps=1) 328 341 migrator = self.new(:down, migrations_path) 329 342 start_index = migrator.migrations.index(migrator.current_migration) … … 346 359 self.new(direction, migrations_path, target_version).run 347 360 end 348 361 349 def schema_ info_table_name350 Base.table_name_prefix + "schema_info"+ Base.table_name_suffix362 def schema_migrations_table_name 363 Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix 351 364 end 352 365 353 366 def current_version 354 Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i 367 Base.connection.select_values( 368 "SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).max || 0 355 369 end 356 370 357 371 def proper_table_name(name) … … 362 376 363 377 def initialize(direction, migrations_path, target_version = nil) 364 378 raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? 365 Base.connection.initialize_schema_ information379 Base.connection.initialize_schema_migrations_table 366 380 @direction, @migrations_path, @target_version = direction, migrations_path, target_version 367 381 end 368 382 … … 383 397 def migrate 384 398 current = migrations.detect { |m| m.version == current_version } 385 399 target = migrations.detect { |m| m.version == @target_version } 386 400 387 401 if target.nil? && !@target_version.nil? && @target_version > 0 388 402 raise UnknownMigrationVersionError.new(@target_version) 389 403 end 390 404 391 start = migrations.index(current) || 0392 finish = migrations.index(target) || migrations.size - 1 405 start = up? ? 0 : (migrations.index(current) || 0) 406 finish = migrations.index(target) || migrations.size - 1 393 407 runnable = migrations[start..finish] 394 408 395 # skip the current migration if we're heading upwards396 runnable.shift if up? && runnable.first == current397 398 409 # skip the last migration if we're headed down, but not ALL the way down 399 410 runnable.pop if down? && !target.nil? 400 411 401 412 runnable.each do |migration| 402 413 Base.logger.info "Migrating to #{migration} (#{migration.version})" 403 migration.migrate(@direction) 404 set_schema_version_after_migrating(migration) 414 415 # On our way up, we skip migrating the ones we've already migrated 416 # On our way down, we skip reverting the ones we've never migrated 417 next if up? && migrated.include?(migration.version.to_i) 418 419 if down? && !migrated.include?(migration.version.to_i) 420 migration.announce 'never migrated, skipping'; migration.write 421 else 422 migration.migrate(@direction) 423 record_version_state_after_migrating(migration.version) 424 end 405 425 end 406 426 end 407 427 … … 412 432 migrations = files.inject([]) do |klasses, file| 413 433 version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 414 434 415 raise IllegalMigrationNameError.new(f ) unless version435 raise IllegalMigrationNameError.new(file) unless version 416 436 version = version.to_i 417 437 418 438 if klasses.detect { |m| m.version == version } … … 432 452 end 433 453 end 434 454 435 def pending_migrations 436 migrations.select { |m| m.version > current_version } 455 def migrated 456 sm_table = self.class.schema_migrations_table_name 457 Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort 437 458 end 438 459 439 460 private 440 def set_schema_version_after_migrating(migration)441 version = migration.version442 461 def record_version_state_after_migrating(version) 462 sm_table = self.class.schema_migrations_table_name 463 443 464 if down? 444 after = migrations[migrations.index(migration) + 1] 445 version = after ? after.version : 0 465 Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") 466 else 467 Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") 446 468 end 447 448 Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}")449 469 end 450 470 451 471 def up? -
a/activerecord/lib/active_record/schema.rb
old new 34 34 # #add_index, etc.). 35 35 # 36 36 # The +info+ hash is optional, and if given is used to define metadata 37 # about the current schema ( likethe schema's version):37 # about the current schema (currently, only the schema's version): 38 38 # 39 # ActiveRecord::Schema.define(:version => 15) do39 # ActiveRecord::Schema.define(:version => 20380119000001) do 40 40 # ... 41 41 # end 42 42 def self.define(info={}, &block) 43 43 instance_eval(&block) 44 44 45 unless info.empty? 46 initialize_schema_information 47 cols = columns('schema_info') 48 49 info = info.map do |k,v| 50 v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s }) 51 "#{k} = #{v}" 52 end 53 54 Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}" 45 unless info[:version].blank? 46 initialize_schema_migrations_table 47 assume_migrated_upto_version info[:version] 55 48 end 56 49 end 57 50 end -
a/activerecord/lib/active_record/schema_dumper.rb
old new 30 30 def initialize(connection) 31 31 @connection = connection 32 32 @types = @connection.native_database_types 33 @ info = @connection.select_one("SELECT * FROM schema_info")rescue nil33 @version = Migrator::current_version rescue nil 34 34 end 35 35 36 36 def header(stream) 37 define_params = @ info ? ":version => #{@info['version']}" : ""37 define_params = @version ? ":version => #{@version}" : "" 38 38 39 39 stream.puts <<HEADER 40 40 # This file is auto-generated from the current state of the database. Instead of editing this file, … … 59 59 60 60 def tables(stream) 61 61 @connection.tables.sort.each do |tbl| 62 next if [ "schema_info", ignore_tables].flatten.any? do |ignored|62 next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| 63 63 case ignored 64 64 when String; tbl == ignored 65 65 when Regexp; tbl =~ ignored -
a/activerecord/test/cases/ar_schema_test.rb
old new 25 25 end 26 26 27 27 assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } 28 assert_nothing_raised { @connection.select_all "SELECT * FROM schema_ info" }29 assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i28 assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } 29 assert_equal 7, Migrator::current_version 30 30 end 31 31 end 32 32 -
a/activerecord/test/cases/migration_test.rb
old new 7 7 require MIGRATIONS_ROOT + "/valid/1_people_have_last_names" 8 8 require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" 9 9 require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" 10 require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down" 10 11 11 12 if ActiveRecord::Base.connection.supports_migrations? 12 13 class BigNumber < ActiveRecord::Base; end … … 34 35 end 35 36 36 37 def teardown 37 ActiveRecord::Base.connection.initialize_schema_ information38 ActiveRecord::Base.connection. update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"38 ActiveRecord::Base.connection.initialize_schema_migrations_table 39 ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" 39 40 40 41 %w(reminders people_reminders prefix_reminders_suffix).each do |table| 41 42 Reminder.connection.drop_table(table) rescue nil … … 779 780 assert !Reminder.table_exists? 780 781 end 781 782 783 def test_migrator_interleaved_migrations 784 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") 785 786 assert_nothing_raised do 787 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2") 788 end 789 790 Person.reset_column_information 791 assert Person.column_methods_hash.include?(:last_name) 792 793 assert_nothing_raised do 794 ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3") 795 end 796 end 797 782 798 def test_migrator_verbosity 783 799 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) 784 800 assert PeopleHaveLastNames.message_count > 0 … … 839 855 assert_equal(0, ActiveRecord::Migrator.current_version) 840 856 end 841 857 842 def test_schema_ info_table_name858 def test_schema_migrations_table_name 843 859 ActiveRecord::Base.table_name_prefix = "prefix_" 844 860 ActiveRecord::Base.table_name_suffix = "_suffix" 845 861 Reminder.reset_table_name 846 assert_equal "prefix_schema_ info_suffix", ActiveRecord::Migrator.schema_info_table_name862 assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name 847 863 ActiveRecord::Base.table_name_prefix = "" 848 864 ActiveRecord::Base.table_name_suffix = "" 849 865 Reminder.reset_table_name 850 assert_equal "schema_ info", ActiveRecord::Migrator.schema_info_table_name866 assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name 851 867 ensure 852 868 ActiveRecord::Base.table_name_prefix = "" 853 869 ActiveRecord::Base.table_name_suffix = "" -
a/activerecord/test/cases/schema_dumper_test.rb
old new 16 16 output = standard_dump 17 17 assert_match %r{create_table "accounts"}, output 18 18 assert_match %r{create_table "authors"}, output 19 assert_no_match %r{create_table "schema_ info"}, output19 assert_no_match %r{create_table "schema_migrations"}, output 20 20 end 21 21 22 22 def test_schema_dump_excludes_sqlite_sequence … … 81 81 output = stream.string 82 82 assert_no_match %r{create_table "accounts"}, output 83 83 assert_match %r{create_table "authors"}, output 84 assert_no_match %r{create_table "schema_ info"}, output84 assert_no_match %r{create_table "schema_migrations"}, output 85 85 end 86 86 87 87 def test_schema_dump_with_regexp_ignored_table … … 92 92 output = stream.string 93 93 assert_no_match %r{create_table "accounts"}, output 94 94 assert_match %r{create_table "authors"}, output 95 assert_no_match %r{create_table "schema_ info"}, output95 assert_no_match %r{create_table "schema_migrations"}, output 96 96 end 97 97 98 98 def test_schema_dump_illegal_ignored_table_value -
/dev/null
old new 1 class InnocentJointable < ActiveRecord::Migration 2 def self.up 3 create_table("people_reminders", :id => false) do |t| 4 t.column :reminder_id, :integer 5 t.column :person_id, :integer 6 end 7 end 8 9 def self.down 10 drop_table "people_reminders" 11 end 12 end -
/dev/null
old new 1 class PeopleHaveLastNames < ActiveRecord::Migration 2 def self.up 3 add_column "people", "last_name", :string 4 end 5 6 def self.down 7 remove_column "people", "last_name" 8 end 9 end -
/dev/null
old new 1 class InnocentJointable < ActiveRecord::Migration 2 def self.up 3 create_table("people_reminders", :id => false) do |t| 4 t.column :reminder_id, :integer 5 t.column :person_id, :integer 6 end 7 end 8 9 def self.down 10 drop_table "people_reminders" 11 end 12 end -
/dev/null
old new 1 class PeopleHaveLastNames < ActiveRecord::Migration 2 def self.up 3 add_column "people", "last_name", :string 4 end 5 6 def self.down 7 remove_column "people", "last_name" 8 end 9 end -
/dev/null
old new 1 class IRaiseOnDown < ActiveRecord::Migration 2 def self.up 3 end 4 5 def self.down 6 raise 7 end 8 end -
/dev/null
old new 1 class InnocentJointable < ActiveRecord::Migration 2 def self.up 3 create_table("people_reminders", :id => false) do |t| 4 t.column :reminder_id, :integer 5 t.column :person_id, :integer 6 end 7 end 8 9 def self.down 10 drop_table "people_reminders" 11 end 12 end -
a/activerecord/test/schema/sybase.drop.sql
old new 31 31 DROP TABLE numeric_data 32 32 DROP TABLE mixed_case_monkeys 33 33 DROP TABLE minimalistics 34 DROP TABLE schema_ info34 DROP TABLE schema_migrations 35 35 go