From 8a5a9dcbf64843f064b6e8a0b9c6eea8f0b8536e Mon Sep 17 00:00:00 2001 From: Rick Olson Date: Wed, 9 Apr 2008 16:20:15 +0000 Subject: Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9244 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/lib/active_record/migration.rb | 83 +++++++++++++++++++---------- 1 file changed, 54 insertions(+), 29 deletions(-) (limited to 'activerecord/lib/active_record/migration.rb') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4c10cd806c..573c6a4f02 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -123,7 +123,8 @@ module ActiveRecord # # To run migrations against the currently configured database, use # rake db:migrate. This will update the database by running all of the - # pending migrations, creating the schema_info table if missing. + # pending migrations, creating the schema_migrations table + # (see "About the schema_migrations table" section below) if missing. # # To roll the database back to a previous migration version, use # rake db:migrate VERSION=X where X is the version to which @@ -216,6 +217,21 @@ module ActiveRecord # # The phrase "Updating salaries..." would then be printed, along with the # benchmark for the block when the block completes. + # + # == About the schema_migrations table + # + # Rails versions 2.0 and prior used to create a table called + # schema_info when using migrations. This table contained the + # version of the schema as of the last applied migration. + # + # Starting with Rails 2.1, the schema_info table is + # (automatically) replaced by the schema_migrations table, which + # contains the version numbers of all the migrations applied. + # + # As a result, it is now possible to add migration files that are numbered + # lower than the current schema version: when migrating up, those + # never-applied "interleaved" migrations will be automatically applied, and + # when migrating down, never-applied "interleaved" migrations will be skipped. class Migration @@verbose = true cattr_accessor :verbose @@ -315,15 +331,12 @@ module ActiveRecord class << self def migrate(migrations_path, target_version = nil) case - when target_version.nil?, current_version < target_version - up(migrations_path, target_version) - when current_version > target_version - down(migrations_path, target_version) - when current_version == target_version - return # You're on the right version + when target_version.nil? then up(migrations_path, target_version) + when current_version > target_version then down(migrations_path, target_version) + else up(migrations_path, target_version) end end - + def rollback(migrations_path, steps=1) migrator = self.new(:down, migrations_path) start_index = migrator.migrations.index(migrator.current_migration) @@ -346,12 +359,13 @@ module ActiveRecord self.new(direction, migrations_path, target_version).run end - def schema_info_table_name - Base.table_name_prefix + "schema_info" + Base.table_name_suffix + def schema_migrations_table_name + Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix end def current_version - Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i + Base.connection.select_values( + "SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).max || 0 end def proper_table_name(name) @@ -362,7 +376,7 @@ module ActiveRecord def initialize(direction, migrations_path, target_version = nil) raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? - Base.connection.initialize_schema_information + Base.connection.initialize_schema_migrations_table @direction, @migrations_path, @target_version = direction, migrations_path, target_version end @@ -383,25 +397,31 @@ module ActiveRecord def migrate current = migrations.detect { |m| m.version == current_version } target = migrations.detect { |m| m.version == @target_version } - + if target.nil? && !@target_version.nil? && @target_version > 0 raise UnknownMigrationVersionError.new(@target_version) end - start = migrations.index(current) || 0 - finish = migrations.index(target) || migrations.size - 1 + start = up? ? 0 : (migrations.index(current) || 0) + finish = migrations.index(target) || migrations.size - 1 runnable = migrations[start..finish] - # skip the current migration if we're heading upwards - runnable.shift if up? && runnable.first == current - # skip the last migration if we're headed down, but not ALL the way down runnable.pop if down? && !target.nil? runnable.each do |migration| Base.logger.info "Migrating to #{migration} (#{migration.version})" - migration.migrate(@direction) - set_schema_version_after_migrating(migration) + + # On our way up, we skip migrating the ones we've already migrated + # On our way down, we skip reverting the ones we've never migrated + next if up? && migrated.include?(migration.version.to_i) + + if down? && !migrated.include?(migration.version.to_i) + migration.announce 'never migrated, skipping'; migration.write + else + migration.migrate(@direction) + record_version_state_after_migrating(migration.version) + end end end @@ -412,7 +432,7 @@ module ActiveRecord migrations = files.inject([]) do |klasses, file| version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first - raise IllegalMigrationNameError.new(f) unless version + raise IllegalMigrationNameError.new(file) unless version version = version.to_i if klasses.detect { |m| m.version == version } @@ -433,19 +453,24 @@ module ActiveRecord end def pending_migrations - migrations.select { |m| m.version > current_version } + already_migrated = migrated + migrations.reject { |m| already_migrated.include?(m.version.to_i) } + end + + def migrated + sm_table = self.class.schema_migrations_table_name + Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort end private - def set_schema_version_after_migrating(migration) - version = migration.version - + def record_version_state_after_migrating(version) + sm_table = self.class.schema_migrations_table_name + if down? - after = migrations[migrations.index(migration) + 1] - version = after ? after.version : 0 + Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") + else + Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") end - - Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") end def up? -- cgit v1.2.3