require 'reactor/tools/versioner'
module Reactor
# Class responsible for running a single migration, a helper for Migrator
class MigrationProxy
def initialize(versioner, name, version, direction, filename)
@versioner = versioner
@name = name
@version = version
@filename = filename
@direction = direction
end
def load_migration
load @filename
end
def run
return down if @direction.to_sym == :down
return up
end
def up
if @versioner.applied?(@version) then
puts "Migrating up: #{@name} (#{@filename}) already applied, skipping"
return true
else
result = class_name.send(:up) and @versioner.add(@version)
class_name.contained.each do |version|
puts "#{class_name.to_s} contains migration #{version}"
#@versioner.add(version) # not neccesary!
end if result
result
end
end
def down
result = class_name.send(:down) and @versioner.remove(@version)
class_name.contained.each do |version|
puts "#{class_name.to_s} contains migration #{version}"
@versioner.remove(version)
end if result
result
end
def class_name
return Kernel.const_get(@name)
end
def name
@name
end
def version
@version
end
def filename
@filename
end
end
# Migrator is responsible for running migrations.
#
# You should not use this class directly! Use rake cm:migrate instead.
#
# Migrating to a specific version is possible by specifing VERSION environment
# variable: rake cm:migrate VERSION=0
# Depending on your current version migrations will be run up
# (target version > current version) or down (target version < current version)
#
# MIND THE FACT, that you land at the version nearest to target_version
# (possibly target version itself)
class Migrator
# Constructor takes two parameters migrations_path (relative path of migration files)
# and target_version (an integer or nil).
#
# Used by a rake task.
def initialize(migrations_path, target_version=nil)
@migrations_path = migrations_path
@target_version = target_version.to_i unless target_version.nil?
@target_version = 99999999999999 if target_version.nil?
@versioner = Versioner.instance
end
# Runs the migrations in proper direction (up or down)
# Ouputs current version when done
def migrate
return up if @target_version.to_i > current_version.to_i
return down
end
def up
rem_migrations = migrations.reject do |version, name, file|
version.to_i > @target_version.to_i or applied?(version)
end
run(rem_migrations, :up)
end
def down
rem_migrations = migrations.reject do |version, name, file|
version.to_i <= @target_version.to_i or not applied?(version)
end
run(rem_migrations.reverse, :down)
end
def migrations
files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort.collect do |file|
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
[version, name, file]
end
end
def applied?(version)
@versioner.applied?(version)
end
def current_version
@versioner.current_version
end
def run(rem_migrations, direction)
begin
rem_migrations.each do |version, name, file|
migration = MigrationProxy.new(@versioner, name.camelize, version, direction, file)
puts "Migrating #{direction.to_s}: #{migration.name} (#{migration.filename})"
migration.load_migration and migration.run or raise "Migrating #{direction.to_s}: #{migration.name} (#{migration.filename}) failed"
end
ensure
puts "At version: " + @versioner.current_version.to_s
puts "WARNING: Could not store applied migrations!" if not @versioner.store
end
end
end
end