Sunday, December 27, 2009

Making a gem out of your ruby-on-rails plugin

Generally, now, when we have the gemcutter service, making a gem is not a brainer. Just find a similar project on github, copypaste its gemspec, then say gem build my.gemspec and gem push my.gem and you're basically done.

But there are some options you might want to know about.

First of all to that gemspec business, as I said, if you never did that before, you'd better just find a famous plugin on github that is more or less close to your project, copy its spec in a file named my-plugin.gemspec in the root of your plugin and fill it up with descriptions of your project.

You might find the gemspec option descriptions here and here. For example if you need to show the user some post install message, use the post_install_message option like that

spec.post_install_message = %Q{
Warning! Warning! Annoying message!
}

And one more, some gemspecs on github use plain lists of files that should go into the spec, sometimes with several dozens of entries. Don't do that. First of all, it will be pain in the ass for you, because you might just forget to update the list when you create a new file, second of all it will be pain for another people who might be interested in evolving your project. Ruby is a nice language and has everything you need to process those stuff automatically. For example like that

spec.files = Dir['lib/**/*'] + Dir['spec/**/*'] + %w{
README
LICENSE
CHANGELOG
}

For the second thing, you need to understand how rails hooks up plugins and gems.

When you create a ruby-on-rails plugin there is a file called init.rb in the plugin directory. When rails fires up, it adds your plugin lib/ directory to the load-path and then includes this init.rb file. Basically that's a good idea, you can keep the actual code clean in one place, and the dirty monkey patching script in another. This way you can test them separately and use separately, like say you want your module be available as is, outside of the rails stack, say with Rack apps or something.

But when it comes to a gem, it's a bit different story. Gem supposed to be just a standalone piece of code which you require from your application. It doesn't know anything about initialization, and when ruby-on-rails hooks it up, it just requires a file with the same name as the rubygem.

Say you have a plugin named super_duper then you will have a file lib/super_duper.rb, which rails will include when the gem is specified in the config. So, if you want to convert your plugin into a gem, you basically have two options. Include your init script into the super_duper.rb file, probably with some additional dirty conditions, or create another file like lib/super_duper_lib.rb and make your user define the gem in rails config like that

conf.gem 'super_duper', :lib => 'super_duper_lib'

Both are quite dirty and yet pretty common.

But, there is another, nicer option that lets you avoid disadvantages of those two approaches.

The trick is simple. All you need is to name your ruby-gem in a dashed style. Like say you have a plugin super_duper, so you create a ruby-gem named super-duper, after that you just create a file named lib/super-duper.rb with one simple line of code in it

require File.dirname(__FILE__) + '/../init.rb'

When ruby-on-rails hooks up the gem it will load the dashed file, which will load your init script. This way you can share your initialization script between plugin and gem, and you still keep your actual code clean and easily available outside of the rails stack with the standard require 'super_duper' call.

No comments: