Bundler is a dependency management tool available as a gem. You can install it through RubyGems which comes built-in from Ruby 1.9 with gem install bundler. Bundler will read the Gemfile for the list of gems need to be installed, fetches metadata from the source provided, resolves the dependencies of each gem, installs them and requires them while booting. Without bundler, we need to handle the installation and manage the dependencies manually.
Gemfile is a ruby file. You can specify the gem dependencies of the project in Gemfile. During the gem installation and while auto requiring the gems, bundler will search for the Gemfile under the directory mentioned by the environment variable BUNDLE_GEMFILE or at the root of the project directory.
Bundler provides a utility called bundle init which creates a simple Gemfile on the current working directory with default source as RubyGems. By default a new rails application ships with Gemfile along with the default gems.
When you run bundle install (Or simply bundle), bundler will install the gems listed on Gemfile along with their dependencies (Includes the dependencies of dependencies). Bundler will create a file called Gemfile.lock with the list of the gems installed along with versions of the installed gems. Next time when you run bundle install, bundler will read the Gemfile.lock and install the exact same versions of the gems listed on Gemfile.lock. This makes sure that the application runs with the same version of the gems in all the environments.
Whenever you install a new gem or remove a gem or updating a gem, the Gemfile.lock will get updated. Be sure to commit the Gemfile.lock to the version control to avoid breaking your app.
A gem can be declared under more than one group. Bundler will consider the gems as default group if no gem group specified. On the below Gemfile, gem rails isn’t specified under any group, bundler will consider this gem in default group.
group :development do gem 'spring' end gem 'rails', '5.2.3'
1. By default, bundler will install the gems listed in all the groups. You can override it by passing the option without. It accepts the list of groups to exclude from the installation. Please note that bundler will only skip the installation of the gems. It will still download the gems to exactly resolve the dependencies of the gems listed in the Gemfile.
For example, bundle install –without test will exclude installing the gems from the test group. It will create a file called config under the directory root of the app/.bundle to remember the option. The next time when you bundle install without the without option, bundler will still remember the option from the config file.
--- BUNDLE_WITHOUT: "test"
2. Most Rails applications contain few gems which are used only for development and testing. Source code of these gems doesn’t needs to be loaded on the production environment. With bundler, you can auto require gems from the specific groups.
When you specify a gem without version on the Gemfile, Bundler will try to install the gems with the latest version based on its dependencies. RubyGems suggests a versioning convention for the Gems as follows:
Patch – 0.0.X – Includes bug fixes, All the features worked on v0.0.1 will also work on v0.0.2
Minor – 0.X.0 – Includes additional features, All the features worked on v0.1.0 will also work on v0.2.0
Major – X.0.0 – Includes breaking changes, Some of the features worked on v1.0.0 may break on version v2.0.0
You can specify the version of the gem that needs to be installed on the Gemfile. For example
gem ‘factory_bot_rails‘, ‘>= 4.0‘ – Specifies any of the gems greater than or equal to version 4.0 can be installed.
You can also provide a pessimistic version using ‘~>’. Bundler will increment the last digit of the specified version to identify the range of versions applicable for installation. For example gem ‘rails‘, ‘~> 4.2.0‘ allows installation of rails gem with version ‘>= 4.2.0‘ and ‘< 4.3.x‘.
How does bundler auto require the gems?
We need to require a dependency in another file in order to use the code inside it. Ruby provides a commonly used method called require which allows us to execute the code from another file.
The require method allows the file to be executed only once. It works with the help of global variables $LOAD_PATH(simply $:) and $LOADED_FEATURES.
Consider a file called test.rb with the following:
p 'Hello World'
When you launch irb and require the above file, ruby will execute the code inside it and returns true. If you require the same file again, code inside the file will never get executed and require method will return false. Ruby keeps track the absolute path of the required files in the global variable called $LOADED_FEATURES. With the help of $LOADED_FEATURES, ruby ensures the file is required only once at any point.
D061:TestApp gomathi$ irb 2.6.0 :001 > require '/Users/gomathi/Documents/Projects/Project/TestApp/test' "Hello World" => true 2.6.0 :002 > $LOADED_FEATURES => […, "/Users/gomathi/Documents/Projects/Project/TestApp/test.rb"] 2.6.0 :003 > require '/Users/gomathi/Documents/Projects/Project/TestApp/test' => false
$LOAD_PATH is an array which contains the path to the directories. When you require a file with an absolute path, it will look for the file on the given path, execute the code inside it. If the name passed to the require method is not an absolute path, then it searches for the given file name by iterating through the list of directories on $LOAD_PATH. If it doesn’t find any, then LoadError will be raised.
2.6.0 :001 > require 'test' Traceback (most recent call last): LoadError (cannot load such file -- test) 2.6.0 :002 > $LOAD_PATH << '/Users/gomathi/Documents/Projects/Project/TestApp/' => […, "/Users/gomathi/Documents/Projects/Project/TestApp/"] 2.6.0 :003 > require 'test' "Hello World" => true
Bundler uses the same process for requiring the gems. During the initialisation process of Rails app bundler loads boot.rb and application.rb.
- require ‘bundler/setup’ adds the paths provided on require_paths option of the gemspec(which defaults to lib directory) of gems listed in the Gemfile to $LOAD_PATH. Hence the gem files can be required without the need for specifying the absolute path of each file.
- The next step Bundler.require accepts an array of groups for auto requiring. It then requires the gems along with their dependencies on the specified groups using ruby’s built-in require method. It eliminates the need for manually requiring the gems.
What does require: false do?
gem ‘rubocop’, require: false
The require: false after the gem name instructs bundler to not to auto require the gem. Please note that the bundler will still add the gem to $LOAD_PATH. You can manually require the gem whenever required using require ‘gem_name’.
Hope you would have got an idea on Bundler & Gemfile in Ruby & Rails and we shall see some other interesting concepts in the future blogs.