Monday, July 5, 2010

Install a Rails App's dependencies where 'rake gems:install' fails.

Using rake gems:install to install the gem dependencies of your Rails 2 app just doesn't seem to work. There are a number of problems you may encounter:

Problem 1: It depends on Rails.
You have to manually install Rails before you can use rake gems:install. Ok, so this might not be that big of a deal, but consider this scenario:

  • Your app is running in production on rails 2.3.4.
  • You upgrade your app to rails 2.3.5, including the RAILS_GEM_VERSION.
  • You deploy your app.
  • Your deployment script calls rake gems:install to keep the dependencies up to date with each new release.
  • That fails with something that looks like:

Problem 2: Sometimes, for some reason, it just doesn't work.
Sometimes (most times) you add a new config.gem command to environment.rb. The next deployment runs rake gems:install and it fails with the complaint that you are missing the very gem you are hoping it will install for you. For example, in a working Rails 2.3.5 project, add this line to environment.rb:

config.gem 'sanitize', :version => '1.2.1'

Then, run rake gems:install, and it fails with a "Missing these required gems:" message, e.g.:

WTF? It's telling me to run the command I just ran, to install the missing gem that's preventing that command from running.

Problem 3: It depends on your Rails environment.
This can create a circular dependency where some file (particularly vendored plugins/gems) can require a gem to be loaded before the task to install it runs. For example, consider that some file in your vendor directory does this:

require 'gdata'

Then naturally, you add this to environment.rb:

config.gem 'gdata'

The next time you run rake gems:install, it will fail with a "no such file to load" error, e.g.:

The Solution: rails_gem_install
I have created a new tool called rails_gem_install to help alleviate this problem. Use this instead of rake gems:install, and you should be much more successful at getting all your Rails app's dependencies installed without manual intervention.

To install:

gem install rails_gem_install

To use:

cd my_rails_app
RAILS_ENV=production rails_gem_install

This will install all the gems required to run your app in the production environment, including Rails. Replace rake gems:install in your deployment scripts with rails_gem_install, and as you change config.gem requirements, those gems will be properly installed.

See the github project page for more details.

How It Works
The main principle behind this tool is that it does not depend on Rails. It creates its own module named Rails that provides some of the functionality from the real Rails that it needs, up until the point that Rails is installed. Then, it only loads some very specific parts of Rails that implement the gem dependency and installation mechanisms, without actually running the app's Rails::Initializer and loading all of its environment, plugins, etc.

First, it runs a simple rake -T to see if it complains about Rails missing. It parses the output of this, and if necessary, installs the indicated version of Rails.

Next, it parses out the config.gem statements and uses those to ensure all the listed gems requirements are met, and installs those that aren't. It does this without actually loading Rails or the app environment. This carries us most of the way.

Finally, it runs the rake gems command and parses its output to detect all the kinds of errors described above and install the corresponding gems. This step is repeated until rake gems does not complain anymore.


Claudio said...

Sounds good!
I will try it soon.
Thank you for this plugin.

scottwb said...

@Claudio - Just last night I did a full capistrano deployment to multiple hosts using this tool. The app required an upgrade of the Rails version and dozens of new gems, and using rails_gem_install, the whole thing worked great with a single deploy command and no manual installation on the servers.

My biggest concern is missing out some dependency corner case or dealing with various permutations of rake/ruby/rubygems if you run into any snags, let me know and I'll see if I can fix it.

baboo said...

thanks for taking the time to share this script. I am trying to use it, because alas, I don't know ruby on rails. It seems to work fine until the very end, then it gives this error:

/usr/lib64/ruby/gems/1.9.1/gems/rails_gem_install-0.3.2/lib/rails_gem_install/rails.rb:41:in `install

I am running slackware. Could you point me to how I could track this problem down?

thanks again for your contribution to community.

scottwb said...

@baboo - I am guessing there's a little more output than just the one line. If so, can you please paste the whole thing? That'll make it easier to figure out what's going on.

Also, what version of rubygems are you running? (Try running `gem --version` and pasting the output here.)

And lastly, what version of Rails are you trying to use? (Note that this tool was intended for use with Rails 2.3.x and has not been tested on Rails 3, where you will probably be better served by bundler).

One other things to note: I only tested this on ruby 1.8.6 and 1.8.7. It looks like you are using 1.9.1 -- I haven't had time to test it there yet so YMMV.

Anyway, if you can give me some more details on the failure, I try to see if I can figure out what's going wrong.


baboo said...

that's kind of you to offer. We found out that by downgrading ruby 1.8.7 apps work. I was testing several ruby apps and going nuts. My buddy really liked your script and is modifying to work with 1.9. If he is successful, we would post back the source.

You nailed it about the versions incompatibility.


scottwb said...

Cool! I'm happy to accept a patch to support 1.9. That would be awesome.