Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats and on Github.
Rubygems Good Practice
July 24th, 2009
Rubygems provides two things for the Ruby community.
- A remote repository/packaging format and installer
- A runtime dependency manager
The key to good rubygems practice is to treat these two elements of Rubygems as separate from each other. Someone might use the Rubygems packaging format and the Rubygems distribution but not want to use the Rubygems runtime.
And why should they? The Rubygems runtime is mainly responsible for setting up the appropriate load-paths, and if you are able to get the load paths set up correctly, why should you care about Rubygems at all?
In other words, you should write your libraries so that their only requirement is being in the load path. Users might then use Rubygems to get your library in the load path, or they might check it out of git and add it themselves.
It sounds pretty straight-forward but there are a few common pitfalls:
gem inside your gems
It’s reasonably common to see code like this inside of a gem:
gem "extlib", ">= 1.0.8" require "extlib"
This should be entirely unnecessary. While using
Kernel.gem in an application makes perfect sense, gems themselves should use their gem specification to provide dependent versions. When used with Rubygems, Rubygems will automatically add the appropriate dependencies to the load path. When not using Rubygems, the users can add the dependencies themselves.
Keep in mind that whether or not you use Rubygems, you can use
require and it will do the right thing. If the file is in the load path (because you put it there or because Rubygems put it there), it will just work. If it’s not in the loadpath, Rubygems will look for a matching gem to add to the load path (by overriding require).
Rescuing from Gem::LoadError
This idiom is also reasonably common:
begin gem "my_gem", ">= 1.0.6" require "my_gem" rescue Gem::LoadError # handle the error somehow end
The right solution here is to avoid the
gem call, as I said above, and rescue from plain
LoadError. The Rubygems runtime sometimes raises
Gem::LoadError, but that inherits from regular
LoadError, so you’re free to rescue from that and catch cases with and without the rubygems runtime.
Declare you gem version dependencies in your gem specification and use simple requires in your library. If you need to catch the case where the dependency could not be found, rescue from LoadError.
And that’s all there is to it. Your library will work fine with or without the Rubygems runtime :)