Ruby Package and Version Management

One of my stories this week was to begin using simplecov. simplecov is a test coverage tool in Ruby, which creates neat HTML reports informing you how much of the testable lines of your source code (ignoring non-substantive lines that don’t need to be tested, in theory at least) are covered by your tests. simplecov’s README file directed me to add it as a gem to my ‘Gemfile’.

At this moment, the terrible truth dawned on me. I had no real idea of what a a Gemfile was, as opposed to a gem or a gemset. I had installed RVM but was quite unsure about what it was supposed to do, exactly. And while we’re on the subject, what exactly is Bundler doing if I already have RVM? Down the rabbit-hole (Ruby-hole?) we go…

Wait, what are Packages vs Libraries vs Frameworks vs all the rest?!

Back to basics. What exactly are the differences between these things?

Firstly, all of these concepts basically refer to bundles of code which do useful things. The differences between the terms are often language-specific and therefore they should always be used in context. For example, Ruby packages are referred to as gems and Ruby libraries are usually distributed as gems. Ruby comes with a standard library enabling you to add (using require at the top of your Ruby files) extra functionality to your code, such as ‘date’ methods and classes. In Ruby, native extensions are essentially packages (again, usually gems) written in C or C++ (which is required for certain gems for reasons beyond the scope of this post). Despite this, most programmers will have a feel for what is meant in a general sense by each of the three major terms above so I will briefly delve into them.

If we start with the grandest term, a framework can be thought of as a full set of different bundles of code (normally a set of libraries) which are used together to facilitate development of types of applications. You may have a web framework (Ruby ones being — Ruby on Rails, Sinatra or Padrino, for example) for web sites or a UI framework (Semantic-UI or Material-UI, perhaps) for making applications with a user interfaces (obviously ;)). Frameworks aim to give you all the tools you need to develop the specific product type right out of the box.

Libraries provide specific feature sets such as classes, interfaces, functions and so on. They are simply files with pre-written code in, which you can import into your files. For example, in C you have stdlib.h and stdio.h which give you operating input, output, and basic system functions at no code cost to yourself. As mentioned, Ruby ships with a standard library giving you easy access to lots of additional features. Note that the reason these features aren’t included with Ruby at the outset is because they aren’t essential — and loading takes precious time, so the idea is to make applications only as ‘heavy’ as is strictly required.

So what about packages? One programmer describes these as pieces of isolated, encapsulated code which enable separate distribution — separate from the rest of a library or framework’s features, for example. So it’s sensible to speak of libraries as being easily packaged (a library package) but more rare to see references to a ‘framework package’ as they’re much larger (although these do exist). In other words, packages describe a wider, more general concept than libraries or frameworks and can potentially encompass both.

Overall, when considering different concepts of bundles of code in different languages, it might be useful to think first of ‘libraries’ as smallish bundles of code which provide sets of features which naturally go together. Ruby gems (such as a RubyGems itself which manages your gems) can be thought of as Ruby’s version of libraries. Frameworks tend to be comprised of libraries to provide more of an overarching system, binding together lots of different types of features to make it easy to develop entire programs. And packages can generally describe any type of bundle of code in a general, but tend to be used for smaller things like libraries, gems and even just small features. It is clear that these definitions are not set in stone, however, and should certainly be thought of only as general guidelines. For example, Ruby’s ‘standard library’ actually seems to be somewhere between a framework and a library, and the Ruby on Rails gem is traditionally described as a (web) framework, even though it is packaged as a gem/library.

Package Management in Ruby

Ruby’s default package management system, which is installed with Ruby itself, is RubyGems. This enables you to install gems simply by typing the below in your terminal.

gem install Bundler

Applications and gems themselves often have various dependencies. In other words, they will only work if you also have the other packages that they depend on installed, which makes sense. RubyGems automatically installs the application’s dependencies — however, it does not maintain a consistent environment for applications.

But what does that mean or matter? Well, the more time that passes, the more versions of software get released, such as new versions of Ruby itself (new rubies). This can be problematic for projects — for example, the meaning of a function or method might change, classes might be done away with or extended and this may cause gems and ultimately your application to break or otherwise alter their behaviour. Programmers therefore need to maintain the specific environment (versions of Ruby and gems) in which the project was created until they are ready to upgrade it to the newer versions in a safe way.

Therefore, RubyGems will update your system gems but that’s no good if the newest gems and their dependencies break your program. Instead we use Bundler (a Ruby gem itself, as you can see from the code snippet above) which is able to load up the specific gem environment that you have stipulated for your project. Simply type the following command before beginning to use the required project or application:

bundle install

Bundler will then find the Gemfile of the project or application which states the correct dependencies (including versions) to use. Then, by inserting bundle exec before running your program (e.g. bundle exec ruby app.rb ) the program will run with the bundled versions of the gems and their corresponding dependency versions. For example, I have specified in the Gemfile of my current Battleships project that simplecov is a required gem. Anyone who pulls my project will now be able to run the bundle install command to automatically install the correct simplecov gem and its dependencies and then play the game with bundle exec .

Bundler is also smart at working out which versions of gems play nicely with each other if, for example, two gems ask for the same dependency but different versions of it. Bundler is by far the most used gem for this purpose so there is not really a need to compare any others.

Ruby Version and Environment Management — rbenv vs RVM

Two of the biggest Ruby version managers are RVM and rbenv. RVM has been the go-to manager for a long time, but rbenv is now gaining in popularity.

It is beyond the scope of this post to describe exactly how rbenv works compared with RVM, but boiling it right down, it comes to the following. rbenv inserts a directory of ‘shims’ at the beginning of your PATH which divert Ruby commands like ruby or irbto rbenv. rbenv observes an ordered list of sources to determine which Ruby version to use, beginning with the Ruby environment set in your current shell session, if you have one, and otherwise using the version you have set in your project.

RVM, on the other hand, overrides the shell itself, for example overriding commands like cd — which can lead to incorrect results like loading rubies and gemsets when switching directories. With RVM you manage dependencies directly by building ‘gemsets’ for each project instead of using Bundler.

Why rbenv over RVM?

It is helpful to think of rbenv as a Ruby Version Manager, while RVM is an entire Ruby enVironment (Version) Manager (which, in fact, is exactly what it now stands for).

This means that rbenv is a lot ‘lighter’ than RVM — as a Ruby version manager only, it leaves Bundler to handle package dependencies — and Bundler is very good at this, without making you do much work. With RVM, you define gemsets yourself per Ruby version per project, which can get quite complicated and is overall a lot more difficult, especially for new users.

I hope this has helped provide some context to the sea of terms for seemingly interchangeable concepts, and that my example working Ruby setup environment might be useful for some Ruby newbies out there.

--

--