Don’t Make Your Ruby App a Gem as an Afterthought – Do it Now!

Jun 9, 2011

RubyI have been working with Ruby and independently with Ruby on Rails for a few years now and although I am really familiar with Gems I have managed to avoid ever creating one myself – recently I had to convert an existing project to a gem and I realise how foolish I was not to “gemify” it from day one. I’m going to talk through the process I went through to help anyone in a similar situation out, but mostly to persuade you to create things as Gems from the start of the project.

My project was a reasonably complex command line program – it was simply arranged into a number of folders and subfolders to logically group different areas of the functionality. Essentially there was a big object model and a separate folder with several command line scripts to act on the object model. I have been programming for years and the structure was reasonably sensible – but did not follow the way Gems do.

So my first task was to find out how a gem should be arranged. Seemed simple, the folders and files are shwon below in case anyone needs reminding…

lib/
test/
bin/
README
LICENSE
CHANGELOG
Rakefile
yourgemname.gemspec

The lib folder would normally contain all of the rb files, test is optional and useful if you build automated tests. The bin folder is optional and can contain scripts that will be executed directly once the gem is installed. README, LICENSE and CHANGELOG are all self-explanatory and finally the .gemspec file contains the important config data for your gem.

In the case of my program it seemed an obvious choice to put the object model files into the lib folder and the command line scripts into the bin folder – which actually worked out really well. But more on that later.

I had seen a few gems that help generate you a template for a new gem – and that approach seemed sensible. I opted for Echoe but [Hoe][2] also seemed perfectly adequate. This allowed me to generate an empty gem and then copy my files into the template.

At this point I also had to change the way I was including (or requiring) the rb files. I was originally setting a “codepath” variable and using that to set the root of my project. Once the project was being installed as a gem, that becomes a difficult thing to expect users to do as many will not know where the gem has been installed. Rather, I found what seems to be best practice online.

require File.join(File.dirname(__FILE__), 'include_this_file.rb')

I then realised that I was going to need to wrap my project with a namespace. This was a slightly painful task but very simple – I just wrapped every file with a module and that was that.

I also had to make a few minor changes to my executable scripts. First they needed a bash file header, so that they could be run as if they were executable (add #!/usr/bin/ruby to the top). And then I needed to adjust for my namespace.

Packaging and installing the gem was the simple, using the Rake tasks provided by Echoe.

rake manifest
rake package
rake install

One thing I realised at this point was that when you include a gem, for some reason you don’t require the gem name – you actually specify a file within the gem. My core file that included the important files was called global.rb and so to include my gem I needed to use require ‘global’. I quickly changed this file over to the name of my gem and re-ran the commands above.

So to summarise the experience was reasonably simple once I worked out what needed to be done. The important lesson is that this would have been almost zero effort had I don’t it from the start. Everything I had done in structring my project was sensible and similar to a gem – so I just wish I had done it at the start and saved myself a headache.

In fact I would recommend that for anyone starting a new language. Find out how to package files for modular deployment and follow the general standards as best you can from day one. Even for projects you think will never need it – doing it right from the start will take very little effort and will perhaps help you structure your code better and save you time when you realise you want to share your code.

[2]: http://rubygems.org/gems/hoe