module ActiveRecord class Migrator def self.migrate(migrations_path, target_version = nil) begin ActiveRecord::Base.connection.execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{ActiveRecord::Base.connection.type_to_sql(:float)})" ActiveRecord::Base.connection.execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)" rescue ActiveRecord::StatementInvalid # Schema has been intialized end 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 end end def current_version (ActiveRecord::Base.connection.select_one("SELECT version FROM #{ActiveRecord::Migrator.schema_info_table_name}") || {"version" => 0})["version"].to_f end def migrate migration_classes.each do |(version, migration_class)| if reached_target_version?(version) Base.logger.info("Reached target version: #{@target_version}") break end if irrelevant_migration?(version) next end Base.logger.info "Migrating to #{migration_class} (#{version})" migration_class.migrate(@direction) set_schema_version(version) end end private def migration_classes migrations = 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_f) migrations << [ version.to_f, migration_class(name) ] end @decimal_migrations = down? ? migrations.sort.reverse : migrations.sort @decimal_migrations end def migration_files files = Dir["#{@migrations_path}/[0-9.]*_*.rb"].sort_by do |f| migration_version_and_name(f).first.to_f end down? ? files.reverse : files end def migration_version_and_name(migration_file) return *migration_file.scan(/([0-9.]+)_([_a-z0-9]*).rb/).first end def ordered_migration_numbers @decimal_migrations.transpose.first.sort end def previous_migration_number(f) index = ordered_migration_numbers.index(f) ordered_migration_numbers[index - 1].eql?(ordered_migration_numbers.last) ? 0 : ordered_migration_numbers[index - 1] end def set_schema_version(version) set_version_to = down? ? previous_migration_number(version.to_f) : version.to_f Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{set_version_to}") end def assert_unique_migration_version(migrations, version) if !migrations.empty? && migrations.transpose.first.include?(version) raise DuplicateMigrationVersionError.new(version) end end def reached_target_version?(version) @target_version = 1 if @target_version.eql?(0) (up? && previous_migration_number(version) == @target_version) || (down? && version.to_f == @target_version) end def irrelevant_migration?(version) (up? && version.to_f <= current_version.to_f) || (down? && version.to_f > current_version.to_f) end end end