Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save x-yuri/32e9e8aa9696029618b27e541f7250b6 to your computer and use it in GitHub Desktop.

Select an option

Save x-yuri/32e9e8aa9696029618b27e541f7250b6 to your computer and use it in GitHub Desktop.
Can I require gems that are not part of the bundle?

The straightforward answer is: "You can't." But a more deliberate one would be: "You can't after Bundle.setup has finished executing."

But how does it achieve that? First we've got to clarify a couple of things about rubygems and the require method. The bare require method knows nothing about gems. It requires files, resolving their names (paths indeed) relative to paths from the $LOAD_PATH variable. $LOAD_PATH is not particularly big at the start of an invocation:

$ ruby -e '$LOAD_PATH.each { |p| puts p }'
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/did_you_mean-1.3.0/lib
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/x86_64-linux
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/site_ruby
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/vendor_ruby/2.6.0
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/vendor_ruby/2.6.0/x86_64-linux
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/vendor_ruby
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/2.6.0
/home/yuri/.rubies/ruby-2.6.3/lib/ruby/2.6.0/x86_64-linux

With this, you can require files from the standard library, and those that are part of the did_you_mean gem (since it's required before your script gets a chance to be executed).

That's where rubygems enters the scene. It overrides Kernel.require and things get more interesting (or involved). Again, before execution of your script starts.

With that out of the way, at some point Bundler.setup invokes the Bundler.rubygems.replace_entrypoints method. Bundler.rubygems is a facade over the rubygems api. And the abovementioned method particularly executes reverse_rubygems_kernel_mixin method, which restores the original Kernel.require method. Before that it cleans up $LOAD_PATH, after that adds gems from Gemfile to $LOAD_PATH.

One more thing I'd like to add. When does Bundler.setup get executed? In case of rails that would be your config/boot.rb file. In case you have a standalone script:

require 'bundler`
require 'bundler/setup`

Or:

require 'bundler`
Bunder.setup(:default)

There's not much to add, other than that you can not require bundler and execute Bundler.setup explicitly. You can just run it with bundler exec:

$ gem list -e sinatra
*** LOCAL GEMS ***
sinatra (2.0.7, 2.0.0)

$ cat 1.rb
require 'sinatra'

$ cat Gemfile

$ ruby 1.rb
[2019-10-24 01:37:09] INFO  WEBrick 1.4.2
[2019-10-24 01:37:09] INFO  ruby 2.6.3 (2019-04-16) [x86_64-linux]
== Sinatra (v2.0.7) has taken the stage on 4567 for development with backup from WEBrick
[2019-10-24 01:37:09] INFO  WEBrick::HTTPServer#start: pid=2313556 port=4567

$ bundle exec ruby 1.rb
Traceback (most recent call last):
        1: from 1.rb:1:in `<main>'
1.rb:1:in `require': cannot load such file -- sinatra (LoadError)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment