Vendor Everything — err.the_blog (original) (raw)
- Seriously. You’ll thank me later.
What do I mean, exactly? Well, let’s say you’re working on a small Rails team. You decide to start using test/spec. Be dee dee. As usual, you gem install test-spec then Pistonize the plugin. You start writing specs and begin converting existing tests to specs. You’re on a tear. Nothing can stop you. Behavior is king. You commit your changes.
You break the build.
What? Why? Well, all your new specs depend on the test-spec gem, a gem your comrades and continuous integration builder do not have.
Quickly: fix it! Tell everyone to install the gem locally. Install the gem on your staging server. Carefully install the gem on your production server. Phew. Everyone’s got the same version, right? Right. Well, maybe. (At least the build works.)
See, there’s something wrong with this scenario: it’s not very DRY. Why should only our code be DRY? Why not our environment, too? It should be.
The solution we’ve come up with is to throw every Ruby dependency in vendor. Everything. Savvy? Everyone is always on the same page: we don’t have to worry about who has what version of which gem. (we know) We don’t have to worry about getting everyone to update a gem. (we just do it once) We don’t have to worry about breaking the build with our libraries. (we leave that up to the internet entrepreneurs)
Adding Depended Sees
Alright, alright, let’s see how we’d vendor the test-spec gem. First, the vendor directory:
$ ls -1 vendor/
bin
data
gems
plugins
rails
We obviously added a few directories, namely gems, data, and bin. The rails and plugins directories should be familiar, one hopes.
Let’s focus on the the vendor/gems directory:
$ ls -1 vendor/gems/
RedCloth-3.0.4
RubyInline-3.6.2
crypt-1.1.4
image_science-1.1.1
memcache-client-1.3.0
session-2.4.0
sphinx-0.9.7-rc2
We’ll cd in there and then use the handy gem unpack command to, erm, unpack the contents of our test-spec gem:
$ gem unpack test-spec
Unpacked gem: 'test-spec-0.3.0'
Cool. Now we need to dive into our config/environment.rb file to ensure Rails knows to look in vendor/gems/test-spec-0.3.0/lib when we try to require ‘test/spec’. It’s easier than you think.
Add this to your Rails::Initializer.run block:
config.load_paths += Dir["#{RAILS_ROOT}/vendor/gems/**"].map do |dir|
File.directory?(lib = "#{dir}/lib") ? lib : dir
end
Now all the libraries in vendor/gems will automatically be included in your load path, complete with a lib check for libraries like RubyInline and crypt (which don’t come with lib directories).
Want to only include some libraries in a specific environment? Maybe you don’t want RubyInline or image_science in development mode. Put this under the above snippet o’ code:
if %w(development test).include? RAILS_ENV
config.load_paths.delete_if { |f| f =~ /RubyInline|image_science/ }
end
Good to go.
It should be noted that this trick will not auto-require the gems for you. You still need to do that in your config file, or in your gems, or wherever. Maybe with a line like %w(crypt/blowfish redcloth).each { |f| require f } in your config/environment.rb if it pleases you.
Other Approaches
For one, Dr Nic has something cool: this patch. It lets you run tasks from within vendor/gems right from your RAILS_ROOT. Nifty. He’s also got his gemsonrails plugin which is a similar (but different) approach than we illustrate here.
Jay Fields has his own method for autoloading gems in vendor. Worth a look, and a listen.
Classic Railer topfunky, back in the day, rolled a rake task to scratch the same itch.
Then there’s this thread on Rails-Core about adding some kinda gemy-ness to Core which, unfortunately, hasn’t yet transpired. The gem in question, the one to metaly manage other gems, lives in technoweenie’s repository.
I even do this (for better or worse) with cache_fu, btw. I am starting to really like not being dependent on the environment.
All Set!
The goal here is simple: always get everyone, especially your production environment, on the same page. You don’t want to guess at which gems everyone does and does not have. Right.
There’s another point lurking subtlety in the background: once all your gems are under version control, you can (probably) get your app up and running at any point of its existence without fuss. You can also see, quite easily, which versions of what gems you were using when. A real history.