tags:

views:

640

answers:

1

I'm writing a non-Rails ruby application (gasp!) and would like to be able to include all the gem dependencies which the application requires in a vendor subdirectory. This would be similar to how http://gemsonrails.rubyforge.org/ works for Rails apps.

The goal here is to avoid the situation my team currently experiences when a new dependency is added. Every developer on my team has to install the gem manually, and then someone has to manually update each test and staging and production machine. If we can freeze the dependencies into the distributed application itself then a simple svn update (or git pull for those hipsters in the crowd) would be all that is needed.

+3  A: 

UPDATE (New Solution):

Try Yehuda Katz's new bundler gem. gem install bundler then create a Gemfile with all your dependencies. See the documentation for more info.

Old Recommendation:

One easy way is to just manually unpack the gems into your vendor directory and add the lib path of the unpacked gems to the front of the $LOAD_PATH.

To unpack a gem:

$ cd vendor/gems
$ gem unpack active_support
Unpacked gem: '/path/to/myproject/vendor/gems/activesupport-2.3.2'

Just make sure you unpack all the necessary gems and their dependencies (using the correct versions).

To add all gems under vendor/gems to your $LOAD_PATH, try adding something like this to your application's initialization:

Dir.glob(File.join("vendor", "gems", "*", "lib")).each do |lib|
  $LOAD_PATH.unshift(File.expand_path(lib))
end

Update: Sarah (in the comments) convinced me it might also be necessary to override the GEM_PATH. Here's one way to do that:

require 'rubygems'
gem_paths = [File.expand_path(File.join("vendor", "gems")),  Gem.default_dir]
Gem.clear_paths
Gem.send :set_paths, gem_paths.join(":")


Another option is to look into Rip (Ruby’s Intelligent Packaging) for managing your dependencies. Rip looks really sweet, but it's still new.

Ryan McGeary
Don't you also have to manage GEM_PATH? Otherwise gem dependencies at runtime won't resolve correctly.
Sarah Mei
Sarah, not if you assume that all gem dependencies are already in vendor/gems. Basically, this tactic circumvents RubyGems altogether. In other words, there's no need to require 'rubygems'. I admit there are some holes with this approach (especially with binary gems), but it should go a long way and still keep things simple.
Ryan McGeary
Ah, ok, so require 'some_gem' will still work because some_gem/lib is in the $LOAD_PATH. That's an interesting approach.
Sarah Mei
However, if a gem uses require 'rubygems' to find another gem (as many do) then you're in trouble.
Sarah Mei
Sarah, maybe I'm missing something, but I still don't think there's a problem. Even if another gem uses require 'rubygems', that doesn't change the fact that the libs under vendor/gems are at the front of the $LOAD_PATH. Subsequent requires will still use the code under vendor/gems, assuming all the dependencies are there. RubyGems doesn't search the gem_path until it sees a LoadError.
Ryan McGeary
Sarah, Ah, here's a possible problem though. If a gem uses the `gem` method to load a gem instead of `require`, that's where there might be trouble, and where GEM_PATH might be necessary.
Ryan McGeary
Interesting - I didn't know RubyGems uses the $LOAD_PATH before GEM_PATH. In addition to Rip, there's also Yehuda and Carl's new bundler: http://yehudakatz.com/2009/07/08/rails-bundling-revisited/
Sarah Mei
Thanks guys. I tried the LOAD_PATH approach originally suggested and it works fine for now. If I start seeing the issues you discuss with gem which pull in other gems then I'll experiment with the GEM_PATH addition. Thanks again for the help, much appreciated.
Pete Hodgson
Turns out that the solution as given doesn't quite work for all gems. Specifically, I'm using database_cleaner (http://github.com/bmabey/database_cleaner), which when unpacked has a file vendor/gems/bmabey-database_cleaner-0.2.2/examples/lib/activerecord.rb. the Dir.glob given in Ryan's answer leads to that activerecord.rb being preferred over the Active Record gem's activerecord.rb. I tweaked the Dir.glob given to use '*' rather than '**' and everything was fine, at least for my set of gems. YMMV.
Pete Hodgson
Pete, Whoops. My bad. Yes, that should have just been a "*" rather than "**". Fixed in the example above.
Ryan McGeary