The venv
module in python isolates packages of one python project from another project. I remember trying to install flask and running into dependency conflicts until I learned about venv
. Recently, I started wondering why I don’t run into the same issues when working with rails. This led me down the rabbit hole of learning about bundler and dependency isolation.
What is bundler?
Bundler is a popular ruby gem used to install project dependencies instead of installing each gem via ruby gem. An application can define a Gemfile
with all the project’s gem dependencies. Then bundle install
will install each of the gems. It will also resolve any dependency conflicts. For example, assume the application depends on gem_a
and gem_b
. It requires gem_a
to be version 3 or higher. gem_b
also depends on gem_a
but it requires version 4 or higher. Bundler will install version 4 since that satisfies all dependencies. Bundler then creates a Gemfile.lock
file which lists all the gems installed and their versions. This makes it easy for another developer to install the same dependencies on their computer.
What is bundle exec?
So how does that offer dependency isolation? Well, it is common to run rails with bundle exec
(ie bundle exec rspec <path/to/file>
). Running bundle exec
ensures all the gems specified in the Gemfile
are automatically available to the ruby application via require
. More so, it ensures that only those gems are available. So if you have many versions of a gem installed, it will ensure only the version specified in the Gemfile
is available to the application.
For example, require 'json'
will always use the latest version of json
installed on the computer. So if another application is using a higher version of the gem, that version may be imported instead of the version you intended.
With bundle exec
, only the versions specified in the Gemfile
will be available, which ensures the correct version is imported by require
.
Ruby load path
So, how exactly does bundle exec
ensure the correct version is used by the application?
RubyGem uses a global variable called $LOAD_PATH
, which stores the path to a gem on a computer. require
uses the $LOAD_PATH
to find the gem and import it. By default, the $LOAD_PATH has the path to the latest version of a gem.
However bundle exec
overrides the $LOAD_PATH
to contain paths to the gems in the Gemfile (with the version specified in the Gemfile) and only those gems. This ensures that the correct version of each gem is always used regardless of which other versions may be installed on the computer.
Testing this in practice
You can see this in action by running code that requires the JSON gem and then prints the load path. It also converts a hash to JSON.
1
2
3
4
require 'json'
pp $LOAD_PATH
print JSON.generate({"key"=>"http://www.example.com/test"}, escape_slash: true)
Without bundler, it loads the latest json gem I have installed (2.6.3). Notice this version of the json gem escapes the slashes.
1
2
3
4
5
6
7
8
% ruby json_with_escape.rb
["/Users/veerpalbrar/.rvm/gems/ruby-2.7.2/gems/json-2.6.3/lib",
"/Users/veerpalbrar/.rvm/gems/ruby-2.7.2/extensions/x86_64-darwin-21/2.7.0/json-2.6.3",
"/Users/veerpalbrar/.rvm/rubies/ruby-2.7.2/lib/ruby/site_ruby/2.7.0",
...]
{"key":"http:\/\/www.example.com\/test"}
When I create a Gemfile specifying version 2.3.1, and run the ruby file with bundle exec
, you can see that version 2.3.1 is listed in the $LAOD_PATH. You can also see this version of the gem doesn’t escape the slashes in the url.
1
2
3
4
5
6
7
8
% bundle exec ruby json_with_escape.rb
["/Users/veerpalbrar/.rvm/gems/ruby-2.7.2/gems/bundler-2.3.19/lib",
"/Users/veerpalbrar/.rvm/gems/ruby-2.7.2/gems/json-2.3.1/lib",
"/Users/veerpalbrar/.rvm/gems/ruby-2.7.2/extensions/x86_64-darwin-21/2.7.0/json-2.3.1",
"/Users/veerpalbrar/.rvm/rubies/ruby-2.7.2/lib/ruby/site_ruby/2.7.0",
...]
{"key":"http://www.example.com/test"}
This is why your application needs to specify its dependencies. If the gem is updated, you want the application to continue to use the older version and not break existing behaviour. bundler
is one tool you can you for this dependency management.