/ DEVELOPMENT

Ruby - Brew, rbenv and bundle explained

An explanation to the intricate relationships between your mac computer and required packages to run ruby in an optimal way.

We will start with a brief history lesson followed by further explanation of included parts.

It all begins with Unix… well macOS

Your computer runs an operating system called macOS. Without going into too much detail it is based on another, more primitive operating system called Unix. Linux is also unix-like. And they all have two things in common, they are composed of several components packed together - open sourced and distributed. And they can be accessed via a terminal.

Open source means that anyone can freely review the code, make changes or write new applications. Unix and Linux are open source and developed by thousands of developers. Some applications are called packages or libraries (libs). They either come pre installed or can be installed through something called a “package manager”. And because they are open source and freely distributed, as counterintuitive as it might sound, we need to centralize it somehow. That is the purpose of a “package manager”. Its job is to keep track of the latest versions of these packages and distribute it in a more controlled fashion.

macOS also relies on packages. A normal user rarely comes in contact with these applications/ packages since they are mainly acessible via the terminal or runs in the background. They are of interest if you are a developer, or so called “power user”.

Most users of macOS are happy with applications they can download from the internet or purchase via the App Store. We are not. Because we are power users (wieehoo! Right?). To help us, we need a package manager…

Brew - our package manager of choice

How would Wikipedia describe a package manager?

“A package manager or package-management system is a collection of software tools that automates the process of installing, upgrading, configuring, and removing computer programs for a computer’s operating system in a consistent manner.”

I couldn’t have said it better myself. One of the most used package managers for macOS and linux is called Homebrew, or brew for short. You could run ruby without it, in fact - ruby comes preinstalled with macOS. But it’s tedious, and usually outdated. Most pressingly, it’s complicated to install and update packages yourself. So we will use brew to help us.

Rbenv

Ruby Version Manager, more commonly known as RVM or rbenv is exactly what it sounds like: a version manager for ruby. You could survive by using the ruby version that comes with macOS and update it as soon as new versions come out, but using rbenv has many advantages. It helps you to contain ruby and gems to one location on your computer. It makes it easier to install new versions of ruby. And it allows you to install and run multiple versions of ruby at the same time.

ruby

Ruby is a popular programming language. Ruby and it’s environment also provides a subset of packages that is open source and distributed. This is great since it allows you as a developer to reuse other developer’s code and not reinvent the wheel every time you build something. To complicate things further, these ruby packages are not called packages. They are called gems.

It doesn’t stop there… Very much like we use brew to help us keep track of different macOS/linux packages ruby itself also has a package manager for its gems. It is called Bundler, or bundle for short. (If you think this sounds very complicated, you are not alone. But you will soon learn how complicated it would be if we didn’t have brew, rbenv and bundler)

To summarize

  • brew - package manager for macOS and linux code
  • rbenv - a version manager for ruby
  • ruby - the program language of our choice today
  • gem - confusing name for a ruby package
  • bundle - a package manager for ruby to keep track of your gems (for a specific project)

So we need brew to be able to install rbenv. We use rbenv to install new versions of ruby on your computer (or run many different versions in parallel). Then, since we are using ruby and gems we need bundle to keep track of all of them for a specific project.

Walkthrough of brew, rbenv and bundle

Let’s take a closer look at how they work together and how we interact with them. Long story short: we want to keep everything up to date, mainly for security (better safe than sorry!) but sometimes also to access new features that have just been released. Let’s start from the bottom up. Let’s look at bundle.

How bundle works

So. When developing something in ruby you can always write everything from scratch. But why? This is a golden opportunity to be “Standing on the shoulders of giants”? Meaning, we can use stuff that other people have built, so we don’t have to redo the hard work. This is the pinnacle of open source, it’s why it exists in the first place. The ruby community has a huge library of packages, sorry… gems… that we can utilize. The most common centralised list of ruby gems is called https://rubygems.org/. At the time of writing they host more than 160 thousand gems and they have been downloaded more hand 50 billion (?!) times combined.

Example. Let’s say we are building an online community where we want people to login and signup with their Facebook username and password. Instead of spending days reading through Facebook’s documentation of their API and implement every single function we need, we can simply just use this gem: https://rubygems.org/gems/omniauth-facebook. As you can see on the page, it has been downloaded by other developers more than 22 million times. Instead of spending days/weeks to implement the required Facebook functionality, it will now only take seconds to have this infrastructure in place. All packaged in a nice little gem from a third-party. Brilliant! As a big bonus, we don’t need to keep track of changes Facebook makes to their API. The people behind the gem will quickly release an update if this happens.

Another great thing is that we don’t necessarily need to store the source code provided inside the gem in our project on GitHub. We can simply just reference to it, so when someone in our team downloads the project, they can use bundle to “automatically” fetch the latest and most updated version instead.

Right. So how is that done? In any given Ruby project, in the root folder, you will find a file called Gemfile that specifies which gems this project is dependent on (normally called dependencies). You can open a Gemfile for your project in your text editor if you want to take a look. But let’s look at an example…

First, install bundler if you don’t have it already by running this command in your terminal: gem install bundler

Example Gemfile

Filename: Gemfile

1
2
3
4
source 'https://rubygems.org' #Tells bundle which gem hosting service we want to use.
ruby '2.6.4'                  #Tells bundle which version of ruby we want to use
gem 'omniauth-facebook'       #Tells bundle to install the gem of our choice
gem 'sass'                    #Another random gem in this example

Now we have everything to get started with our new ruby project. In the terminal we write the following command: bundle install

Now bundle will read the Gemfile from top to bottom:

  1. It will first take note that we want to use rubygems.org as a source
  2. Then it checks if ruby 2.6.4 is installed on your computer
  3. Then it will start to download the gems one by one.

But there is a catch. All these gems might have used other gems to build their project, so there are more dependencies behind the scene. In order for our project to work, we not only need the gems we specify, but all their dependencies as well. You can check if a gem has a dependency by going to their page. E.g: sass is dependent on another gem called sass-listen (check for yourself at https://rubygems.org/gems/sass under “Runtime Dependencies”).

This is where bundle comes in handy. If we didn’t use bundle we would need to install all dependencies manually, by adding them as well to our Gemfile. Thankfully, bundle handles this automatically for us. Once everything is downloaded, bundle will also check which version of each gem it’s supposed to use, and it will store it in a new file that it creates called Gemfile.lock

This is a way to make sure that your app/project doesn’t break just because one gem decides to release a major version update that is not backwards compatible. So not only does bundle keep track of which gems are dependent on each other, it will also keep track of which version of each gem that is compatible with exactly the right version of a dependent gem. It’s nice to alleviate this headache to bundle. Homework: open up a Gemfile.lock file in any ruby project to take a look, there you will see that it reflects the gems specified in your Gemfile but also lists all their dependencies and their versions.

Sidenote: you might sometimes want to specify a specific version of a gem in your Gemfile. Let’s change the omniauth-facebook line to use the version 5.0.0

3  gem 'omniauth-facebook', '5.0.0'

How rbenv works

rbenv can be used to install multiple different versions of ruby. A good practice is to use the latest stable release which is always stated on Ruby’s website.

Another option to list available versions is by typing this in your terminal (make sure rbenv is up to date): rbenv install -l or rbenv install --list

There will be a bunch of forks of ruby on the list as well, like: jruby, maglev, mruby, rbx, truffleruby. Those you can ignore.

2.5.8
2.6.6
2.7.1
jruby-9.2.13.0
maglev-1.0.0
mruby-2.1.2
rbx-5.0
truffleruby-20.2.0

The goal is to find the highest number that doesn’t have -dev or -preview* in the name. In the example above you will see that 2.7.1 is the number we want.

(Please note: The latest stable version has likely changed since the time of writing. Check http://www.ruby-lang.org/en/downloads/ for up to date information.)

Global - Installing a specific version of ruby and then setting it to global

Now, to install it, we simply write this in our terminal:
rbenv install 2.7.1

Great. Rbenv will now install ruby version 2.7.1 on your computer. But you also have to tell your rbenv that this is the version you want to use on your computer from now on, because rbenv assumes that you only want to take a look. So let’s go ahead and set the new version to be our preferred option. In your terminal, write:
rbenv global 2.7.1

Global - this kind of means “everywhere on your computer”. So when it is done, wherever you run ruby it will use the version 2.7.1.

To test this you can navigate to any folder on your computer in your terminal and write: ruby -v

Local - Setting another version of ruby for a specific project

Sometime you might want to use another version for a specific project. Then you can set this “locally” for that project. Navigate to your project folder in the terminal and type:
rbenv local 2.6.3

What actually happens behind the scenes is that rbenv will create a file in the project folder called .rubyversion that just contains the version number. The dot in front of a file name means it is hidden. But you should be able to see it in your text editor (Atom, VScode or similar). Or you can also see it via the terminal by typing: ls -la

If you change your mind you can delete the local .rubyversion file or run this command:
rbenv local --unset

Quick side note
Using a local version in a project will of course require the specific version to be installed via rbenv already. Otherwise you will get an error saying this version is not installed. Use the rbenv install command to install it. To see which versions you already have installed on your computer you can write:
rbenv versions

The global version will be marked with a * sign. And to uninstall a version you no longer need you simply run this command:
rbenv uninstall 2.2.1

How brew works

We can check if brew is installed on your computer by typing the following command in your terminal:
brew or brew -v

If it is not installed you will get an error. Head over to https://brew.sh/ and run the command that is mentioned on top of the page to install it. This might take a while.

Once brew is installed we can go ahead and install rbenv by running:
brew install rbenv

In case you encounter problems there is a great guide over here: https://github.com/rbenv/rbenv

Updating brew and rbenv

Every now and then we need to update rbenv and brew. E.g. if you want to install the latest version of ruby but this version is not included in the list when you run rbenv install -l. This is a common sign that it’s time to update. You can update brew and rbenv at the same time by running the following command in your terminal:
brew upgrade rbenv ruby-build

Need help?

Even if you grasp the concepts you still might still run into some issues, it happens to all of us. The best remedy is to search on Google/Stackoverflow or ask a friend if you need help. This guide also covers a lot of common problems: https://github.com/rbenv/rbenv

Bundle, rbenv and brew cheatsheet

Bundler

Command Description
bundle install Installs all gems specified in Gemfile for your project
gem install bundler Installs bundler
bundle update --bundler Updates to new version of bundler (rarely used)

rbenv

Command Description
rbenv install 2.6.4 Installs version 2.6.4
rbenv global 2.6.4 Sets 2.6.4 to your preferred ruby version on your computer
rbenv install -l Lists all available versions ready to be installed. An up to date list requires rbenv to be of latest version.
rbenv uninstall 2.6.4 Uninstalls 2.6.4 on your computer
rbenv local 2.6.3 Set local ruby version to 2.6.3. Project specific.
rbenv local --unset Unset local version to rely on global version again
rbenv versions List all versions installed on your computer. Current global version is marked with *
ruby -v Will tell you which version that is actually used, in whatever folder you are.

Homebrew

Command Description
brew install rbenv Installs rbenv on your computer
brew upgrade rbenv ruby-build Updates brew and rbenv. Needs to be done every now and then to be able to install latest version of ruby via rbenv.
Install brew Use command on top of the page to install brew.

Header photo by Max Duzij on Unsplash