Gems are cool. They are shiny, colorful, and worth up to 5000 gold pieces each, according to the Game Master’s Guide. In the Ruby world, though, gems are sometimes a bit mysterious – they are magical pieces of software that do stuff for you once you’ve invoked them. To make things even more complicated, nowadays we don’t even handle gems directly – most of the time, we let another tool, Bundler, do it for us. It’s a shame, because understanding Ruby gems is also worth a lot. So, before we go on our journey, let’s take a detour to see how gems work and how to build one ourselves.
How gems work
RubyGems
, first released in 2004, is “just” a Ruby library. But it is such an important one that it has been bundled
with Ruby since 2007 (and Ruby 1.9) When you install Ruby on a computer, RubyGems is installed, too; and when you run a
run a Ruby script or a REPL, RubyGems is automatically required for you.
And when it is required, RubyGems “hijacks” the native Kernel#require
method so that files are looked for in more places
than normal – including certain directories that RubyGems knows about, and where it can install specifically packaged
Ruby libraries, called gems.
RubyGems also comes with an executable, gem
, that can (among other things) fetch, unpack, and install gems in those
directories. Gems installed by the gem
command will be found by the hijacked require
method, and voilà: Ruby
programmers can enjoy a very easy way to distribute and integrate libraries in their Ruby programs.
In order for RubyGems to be able to install it, a gem must follow certain specifications. They are rather light, and well documented in the RubyGems guides. The minimal setup for a gem is:
- A
lib/
directory, which will contain the gem’s code – at the very least, in a single file, which by convention is named after the gem. - A gemspec file, also named after the gem (but with the
.gemspec
extension)
So, two files and one directory are enough for RubyGems to package everything into a single archive, or more importantly, to unpack said archive and install the library’s code in the right place.
Creating our gem
Several tools can generate a scaffolding for a new gem (such as Bundler or Gemsmith), but we’ll do it from scratch, both as a learning exercice and to keep things minimal. And the first step in creating our gem is to name it.
Finding a good name is hard. The RubyGems guides provide great advice on naming a gem,
but they are more about conventions to follow (which we will!) than naming ideas. I like whimsical and colorful names,
so something boring like dnd_character_creator
is out of the question. Instead, let’s use our imagination. What
“builds character”, in a fantasy world? Conan would probably say that it’s action and combat - or more poetically,
steel. And we’ll eventually write our character down on a character sheet – a piece of
paper, or in a fantasy world, vellum. So let’s name our gem Steel Vellum – or rather, steel_vellum
. It sounds D&D-y
enough for me.
Now that we have a name, we can create the files and folder that we need:
According to the documentation, the gemspec file must contain
the gem’s specifications – a lot of them can be defined, but only 5 are required: a name, a version number, the list
of files that constitute the library, a short description and a list of authors. So let’s add these to the
steel_vellum.gemspec
file.
As for the code of the libary itself, let’s do the very bare minimum for now, and only provide a module. We could leave it empty, but let’s also add a version number in the form of a constant – just to have something to try out the gem with:
It is enough? Will it work? Let’s see if we can build the gem – i.e. package it into a .gem
file – and install it.
RubyGems gave us a few warnings when it built the gem (and we’ll address them later), but so far, everything seems fine. Let’s check it out in a Ruby console:
It works! And we can see that the metadata we’ve added to our gem is indeed used:
(Note that the actual installation path will vary according to your Ruby installation.)
Test setup
We now have the right foundations for our gem, and we could start adding code to the lib/steel_vellum.rb
file. But we’ve
decided to go tests-first as much as possible, so let’s setup our project so that we can indeed write and run tests.
RSpec is a popular and extremely complete testing framework, but I prefer Minitest – it’s lean and fast, and does everything you need but nothing more, which means that it’s hard to shoot yourself in the foot (by abusing mocks or over-DRYing, for example), even if you can miss the syntactic sugar, sometimes. Plus, like RubyGems, Minitest comes bundled with Ruby.
However, even though Minitest doesn’t need to be installed (normally), it still needs to be declared as a dependency of our gem. This is done through the gemspec file:
The file itself only needs to require Minitest, but we’ll add a placeholder test to ensure that everything works well:
To run the test, when only need to run this file – but we need to make sure that the lib/
directory will be included
in Ruby’s $LOAD_PATH
.
Our test suite – with its single test – runs fine. But typing the name of every single test file to run will eventually become tedious, so (as suggested in the documentation), let’s add a Rake task to run the whole suite for us. This is very easy, since Minitest provides one for us – we only need to set it as the default Rake task for our project. And because we’ve stuck to the conventions when namimg files and directories, we need almost nothing:
And that’s it! Now, executing rake
without specifying a Rake task will run the whole test suite:
Final touches
And so, we have the basis for our Steel Vellum library, written with its tests and distributable as a gem. Let’s wrap
things up by smoothing the rough edges of this scaffold. We have a few warnings to fix, and our test runner could
benefit from a more colorful output. More importantly, the gem’s version number is currently written twice, which
means extra maintenance – or potential inconsitencies. Let’s fix all that by removing the VERSION
declaration from
the main library file……
… and placing it in its own file…
… which can then be required directly in the gemspec file:
Note that said gemspec file features new declarations, including a globbing approach to list files, and a development dependency on minitest-reporters, a Minitest plugin that improves the tests output, even when sticking to the defaults, like so:
And now, we’re good to go! Our detour is over and we’re back on the road – see you in part 3!
Next: Meet Bruenor