diff options
Diffstat (limited to 'activerecord/lib')
-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 |