Friday, December 14, 2007

Generator replacement for plugin_migration

I struggled last Friday trying to make loaded_plugins and plugin_migrations work in Rails 2.0. I was deep in the Rails source code when Seth suggest I simply create a generator. This is pretty easy. Here's what I came up with:


class MyPluginGenerator < Rails::Generator::NamedBase

def initialize(runtime_args, runtime_options = {})
super
@argument = runtime_args.shift
end

def manifest
relative_migration_path = "../../../db/migrate/"
recorded_session = record do |m|
get_migrations.each do | mf |
new_migration_file_name = mf.sub(/^(\d{3})_/,'')
new_migration_file_name.sub!(/\.rb$/,'')
unless migration_exists(new_migration_file_name)
m.migration_template "#{relative_migration_path}/#{mf}", 'db/migrate',
:assigns => { :migration_name => new_migration_file_name.camelize},
:migration_file_name => new_migration_file_name
puts " CREATING #{new_migration_file_name}."
else
puts " exists #{new_migration_file_name}, skipping."
end
end
end
end

protected
# Override with your own usage banner.
def banner
"Usage: #{$0} my_plugin migration"
end

def migration_exists(file_name)
migration_directory = File.dirname(__FILE__) + "/../../../../../db/migrate"
not Dir.glob("#{migration_directory}/[0-9]*_*.rb").grep(/[0-9]+_#{file_name}.rb$/).blank?
end

def get_migrations
migration_files = []
my_plugin_migration_path = File.dirname(__FILE__) + '/../../db/migrate'
Dir.entries(my_plugin_migration_path).sort.each do | migration_file |
if migration_file =~ /^(\d{3})/
migration_files << migration_file
end
end
migration_files
end


end

I place this in the file:
vendor/plugins/my_plugin/generators/my_plugin/my_plugin_generator.rb

To run it, I do:

ruby script/generate my_plugin migration


The generator will copy migrations from vendor/plugins/my_plugindb/migrate to db/migrate. It uses Rails functionality to ensure that the migration files are numbered sequentially starting with my application's next available migration number. If the migration is already in db/migrate, it will be skipped but it will not abort. This means I can add migrations to the plugin and run the generator again without worrying about it overwriting the old migrations.

Most of the work is done in the manifest method. By default, the Rails method migration_template looks for the template in a directory called template. In this case it would be looking for the migration file in vendor/plugins/my_plugin/generators/my_plugin/template which is what my relative_migration_path is relative to. My migration_exists method is basically a cut-and-paste from the Rails source code. I don't know why but they these methods are protected in Rails.

I hope this helps, it's a temporary fix. I'm sure the guys at plugin a week will have plugin_migrations fixed in no time :)

0 comments: