diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2008-03-28 21:21:01 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2008-03-28 21:21:01 +0000 |
commit | c00de99f69358b58ca2bd6bc732e2de1b667800e (patch) | |
tree | fd5d4855a64bb15a99494b7efdb62f4798321a52 /activerecord/lib/active_record | |
parent | ad8df03f9c831d88b8a7eb80c1b7dbcf02fc1b19 (diff) | |
download | rails-c00de99f69358b58ca2bd6bc732e2de1b667800e.tar.gz rails-c00de99f69358b58ca2bd6bc732e2de1b667800e.tar.bz2 rails-c00de99f69358b58ca2bd6bc732e2de1b667800e.zip |
Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches (closes #11458) [jbarnette]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9122 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib/active_record')
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb | 16 | ||||
-rw-r--r-- | activerecord/lib/active_record/migration.rb | 148 |
3 files changed, 100 insertions, 66 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ff72656e85..af480a0797 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -431,7 +431,7 @@ module ActiveRecord #:nodoc: # adapters for, e.g., your development and test environments. cattr_accessor :schema_format , :instance_writer => false @@schema_format = :ruby - + class << self # Class methods # Find operates with four different retrieval approaches: # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index b1f1fd4d73..c8913d0157 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -232,12 +232,20 @@ module ActiveRecord # Should not be called normally, but this operation is non-destructive. # The migrations module handles this automatically. - def initialize_schema_information + def initialize_schema_information(current_version=0) begin - execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})" - execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)" + execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})" + execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})" rescue ActiveRecord::StatementInvalid - # Schema has been initialized + # Schema has been initialized, make sure version is a string + version_column = columns(:schema_info).detect { |c| c.name == "version" } + + # can't just alter the table, since SQLite can't deal + unless version_column.type == :string + version = ActiveRecord::Migrator.current_version + execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}" + initialize_schema_information(version) + end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 9945f9cd75..58374a01ad 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -8,6 +8,12 @@ module ActiveRecord end end + class UnknownMigrationVersionError < ActiveRecordError #:nodoc: + def initialize(version) + super("No migration with version number #{version}") + end + end + class IllegalMigrationNameError < ActiveRecordError#:nodoc: def initialize(name) super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)") @@ -308,8 +314,6 @@ module ActiveRecord class Migrator#:nodoc: class << self def migrate(migrations_path, target_version = nil) - Base.connection.initialize_schema_information - case when target_version.nil?, current_version < target_version up(migrations_path, target_version) @@ -319,6 +323,16 @@ module ActiveRecord return # You're on the right version end end + + def rollback(migrations_path, steps=1) + migrator = self.new(:down, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + return unless start_index + + finish = migrator.migrations[start_index + steps] + down(migrations_path, finish ? finish.version : 0) + end def up(migrations_path, target_version = nil) self.new(:up, migrations_path, target_version).migrate @@ -327,6 +341,10 @@ module ActiveRecord def down(migrations_path, target_version = nil) self.new(:down, migrations_path, target_version).migrate end + + def run(direction, migrations_path, target_version) + self.new(direction, migrations_path, target_version) + end def schema_info_table_name Base.table_name_prefix + "schema_info" + Base.table_name_suffix @@ -344,73 +362,90 @@ 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? - @direction, @migrations_path, @target_version = direction, migrations_path, target_version Base.connection.initialize_schema_information + @direction, @migrations_path, @target_version = direction, migrations_path, target_version end def current_version self.class.current_version end + + def current_migration + migrations.detect { |m| m.version == current_version } + end + + def run + target = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if target.nil? + target.migrate(@direction) + end def migrate - migration_classes.each do |migration_class| - if reached_target_version?(migration_class.version) - Base.logger.info("Reached target version: #{@target_version}") - break - end - - next if irrelevant_migration?(migration_class.version) + 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 + 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) + end + end - Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})" - migration_class.migrate(@direction) - set_schema_version(migration_class.version) + def migrations + @migrations ||= begin + files = Dir["#{@migrations_path}/[0-9]*_*.rb"] + + migrations = files.inject([]) do |klasses, file| + version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first + + raise IllegalMigrationNameError.new(f) unless version + version = version.to_i + + if klasses.detect { |m| m.version == version } + raise DuplicateMigrationVersionError.new(version) + end + + load(file) + + klasses << returning(name.camelize.constantize) do |klass| + class << klass; attr_accessor :version end + klass.version = version + end + end + + migrations = migrations.sort_by(&:version) + down? ? migrations.reverse : migrations end end def pending_migrations - migration_classes.select { |m| m.version > current_version } + migrations.select { |m| m.version > current_version } end private - def migration_classes - classes = migration_files.inject([]) do |migrations, migration_file| - load(migration_file) - version, name = migration_version_and_name(migration_file) - assert_unique_migration_version(migrations, version.to_i) - migrations << migration_class(name, version.to_i) - end.sort_by(&:version) - - down? ? classes.reverse : classes - end - - def assert_unique_migration_version(migrations, version) - if !migrations.empty? && migrations.find { |m| m.version == version } - raise DuplicateMigrationVersionError.new(version) - end - end - - def migration_files - files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f| - m = migration_version_and_name(f) - raise IllegalMigrationNameError.new(f) unless m - m.first.to_i + def set_schema_version_after_migrating(migration) + version = migration.version + + if down? + after = migrations[migrations.index(migration) + 1] + version = after ? after.version : 0 end - down? ? files.reverse : files - end - - def migration_class(migration_name, version) - klass = migration_name.camelize.constantize - class << klass; attr_accessor :version end - klass.version = version - klass - end - - def migration_version_and_name(migration_file) - return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first - end - - def set_schema_version(version) - Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}") + + Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") end def up? @@ -420,14 +455,5 @@ module ActiveRecord def down? @direction == :down end - - def reached_target_version?(version) - return false if @target_version == nil - (up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version) - end - - def irrelevant_migration?(version) - (up? && version.to_i <= current_version) || (down? && version.to_i > current_version) - end end end |