Script Runs With 'bundler Exec Ruby Script.rb' But Fails When Using 'ruby Script.rb'

- 1 answer

I have a script that requires a gem that i'm getting from a private github repo with a specific branch (lets just call this gem foobar).

I'm using rvm, rails verson 2.3.0, and a gemset (lets call it testset). I'm using bundler to install my gems.

When I execute my script using bundler exec ruby script.rb it runs fine. However when I try running it using ruby script.rb I get the following error:

require': cannot load such file -- foobar (LoadError)

Its also worth noting that using only ruby works if I use the path to the gem as follows:

require '~/.rvm/gems/[email protected]/bundler/gems/foobar-a41fed1fcab7/lib/foobar.rb'

Is there anyway I can use ruby script.rb while only using require 'foobar?



If you don't use bundle exec, then require will work to require gems that are installed in your system gems. It will require the latest (larger number version) gem currently installed in the system; if no gems with that name are installed in the system, it will give you the LoadError.

(Note, require isn't only used for loading gems, it can also be used to load other files on the load path, but for the purpose of this question we're talking about it's ability to load gems).

You don't have the foobar gem installed on your system, so you get the LoadError.

So why does it work with bundle exec? bundler is a dependency management tool. When you use bundle exec, bundler loads the exact versions of gems you specified in your Gemfile/Gemfile.lock files, and makes them available to require. In this case, require gemname won't neccesarily load the latest version installed on your system, it will load exactly the version specified in the Gemfile.lock -- because bundle exec sets things up to do that.

Ordinarily bundler also installs all gems to the system location when you run bundle install. But there are some cases where bundler installs the gems only "locally" in the current directory, and not in the general system gem location. One of these cases is if you tell it to with the path argument. But that's not what's going on for you. Another case is if you tell bundler to load a gem directly from a git repo, with the :git or :github arguments in a Gemfile. The general gem system isn't capable of loading gems directly from a git repo, that's only a bundler feature. So if you have something listed in your Gemfile with :git option, it will always only be installed for the local app, not in the system-wide gem location. I think that's what's going on in this case, the clue was the gems/foobar-a41fed1fcab7 in the path of your example that works -- gems listed with something like -a41fed1fcab7 on the end of their name are typically direct-from-git-repo gems, the a41fed1fcab7 is a git SHA identifying exactly what version from the git repo is currently being used.

I can tell from your path that you are also using rvm gemsets, which complicates the matter a bit further, but this is confusing enough so I didn't go into that -- everything I wrote still stands, except "your system-wide gems" is really "the current rvm gemset". Personally, I believe you'd be better off not using the rvm gemset feature -- things are confusing enough without it, and it isn't really needed anymore now that we have bundler, rvm gemsets were invented before bundler existed (several years ago). And I'm not totally sure why people keep using them now!

Is there anyway I can use ruby script.rb while only using require 'foobar?

Sure. You just need to make sure foobar is installed to your system gems, with gem install foobar. Then require "foobar" will load the latest version installed to your system gems. (But you can only install released gems with specific version numbers to your system gems, not arbitrary checkouts from git). What if you want to load a specific version? Well, you can do something like gem 'foobar', "1.0.2" instead of require 'foobar'. But with a list of gem dependencies that are at all non-trivial, you will soon start to get into a terrible mess of managing exactly what version of a gem you are using is compatible with what version of another gem you are using. As well as setting up a new system and figuring out exactly what version of gems you need to install from scratch to work with script.rb. It becomes a real mess. It is for this that bundler was invented, and you'd be wise to use it.

(Although sure, you could use rvm gemsets instead of bundler -- but not with gems installed direct from git, only bundler supports that, at least without a lot of manual hackery, and rvm gemsets aren't very good when it comes to setting up a new system from scratch, bundle solves that a lot better).

If you do have a Gemfile and Gemfile.lock in the same directory as script.rb (as you seem to), and you just want to avoid having to write bundle exec, you can just put this at the top of script.rb (before any other requires!):

 require 'bundler'

That basically just tells bundler to do the equivalent of bundle exec when script.rb is run, without actually having to write bundle exec. Then you can just run ruby script.rb (or even ./script.rb), and it'll still do the same thing as bundle exec, but without having to write it on the command line.

Hope this helps.