Gem Creation with Bundler

Building a gem used to be a complex task that would require either a precise knowledge of the gem format, itself, or some dedicated tools to generate a suitable boilerplate. These days, we can use the excellent Bundler to remove this complexity and keep the amount of generated code to a minimum.


What We're Building

The test gem we're going to build is a dummy content generator you might use during development. Instead of generating "lorem ipsum" sentences, it uses Bram Stoker's Dracula to generate an arbitrary amount of sentences taken from the book. Our workflow will start by generating the gem, testing and implementing the minimum amount of code necessary to get our gem ready, and then publishing it on RubyGems.


Generating a Skeleton

I'm going to assume that you have a Ruby environment already setup. For this tutorial, we will use Ruby 1.9.3 as a baseline. If you plan, however, to develop a real gem, it might be good to also test it against Ruby 1.8 and other interpreters. For that purpose, a tool like Travis CI is a godsend; with a solid test suite in place, Travis will let you test your gem against a wide variety of platforms without any hassle. Let's start by generating the skeleton:

I'm really sorry if you don't like the name I chose, as a matter of fact one of the hardest tasks in developing a gem is finding the right name. The command will create a directory, called bramipsum with a few files:

Gemfile

The Gemfile is very minimal:

Note that it clearly tells you to move your gem dependencies to bramipsum.gemspec, in order to have all the relevant data for your gem in the file that will be used to populate the metadata on Rubygems.

bramipsum.gemspec

The gemspec file contains a good deal of information about our gem; we can see that it relies heavily on Git to assign the right values to all the variables that involve file listing.

Next, we can run bundle to install Rake. As it was added as a development dependency, it won't be installed by Bundler when someone uses our gem.

A few interesting notes about the file:

  • It includes the Ruby 1.9 opening comment that specifies the file encoding. This is important, as some data in the file (like the email or the author name) can be a non-ascii character.
  • description and summary need to be changed to be correctly displayed on Rubygems.
  • The version is defined inside the lib/bramipsum/version file, required at the top. It defines the VERSION constant, called right before the end of the file.

The lib folder

The lib folder contains a generic bramipsum.rb file that requires the version module. Even if the comment in the file suggests that you add code directly to the file itself, we will use it just to require the separate classes that will form our small gem.


Updating the Base Data and Adding a Test Framework

Let's start by updating the data in bramipsum.gemspec:

Very simple stuff. Next, let's add support for proper testing. We will use Minitest, as it's included by default in Ruby 1.9. Let's add a test directory:

Next, we need a test_helper.rb file and a test for the presence of the Bramipsum::VERSION constant.

Let's open the test_helper.rb file and add a few lines:

It requires both Minitest and Pride for colored output; then it requires the main bramipsum file.

The version_test.rb file needs to be updated with the following code:

We use the expectation format for our tests. The test itself is fairly self-explanatory and can easily be run by typing:

You should have a passing test!

Let's now update the Rakefile to have a more comfortable way to run our tests. Erase everything and paste the following code:

This will let us run our tests by typing rake from the gem root folder.


Adding Functionality

As the focus of this tutorial is creating a gem, we will limit the amount of functionality we're going to add.


Base Class

Bramipsum is still an empty shell. As we want to use Dracula's book to generate sentences, it's time to add it to the repository. I have prepared a version of the book where I have removed any content, except the story itself: let's add it to the project.

Let's now create a Base class, where will add all the methods needed to extract data from the book.

The test file will have only a few expectations:

Running rake now will show an exception, as the base.rb file is still empty. Base will simply read the content of the file and return an array of lines (removing the empty ones).

The implementation is very straightforward:

We define a series of class methods that hold the original text and a processed version, caching the results after the first run.

We then need to open lib/bramipsum.rb and add the right require statement:

If you save and run rake now, you should see all tests passing.


Sentence Class

Next, we need to add a new class to generate sentences. We will call it Sentence.

As before, we have to open lib/bramipsum.rb and require the newly created file:

This class will inherit from Base, so we can keep the implementation minimal. The test will need only three expectations:

The idea is that we can call Bramipsum::Sentence.sentence or Bramipsum::Sentence.sentences(10) to generate what we need.

The content for sentence.rb is also very concise:

As we are on Ruby 1.9, we can use the sample method to return a random element from an array.

Once again, running rake should show all tests passing.


Building and Distributing the Gem

If you run gem build from the command line, a local copy of the gem will be built and packaged for you. If you don't need to distribute it, or if you need to keep it private, you can stop here. But if it's a project you can open-source, I encourage you to do that.

The obvious step is to add our brand new gem to RubyGems.org.

After creating an account on the site, visit your profile. You will find a command you need to run to authorize your computer. In my case it was:

You're now only one step away to publish the gem on Rubygems, however don't do it unless you really want to. The command you would run is:


Conclusion

Congratulations! You now know how to create a gem from scratch just using Bundler. There are however, other things to take into account:

  • Compatibility: you may want to support Ruby 1.8 as well. That will require refactoring all the require_relative calls; additionally, you will need to use the Minitest gem as it's not included by default in Ruby 1.8
  • Continuous Integration: you can add support to Travis CI and your gem will be tested in the cloud against all major Ruby releases. This will make it simple to be sure that there are no issues with platform-specific behavior changes.
  • Documentation: this is important, it's good to have RDoc comments that can help in generating automatic docs and a good README file with examples and guidelines.

Thanks for reading! Any questions?

Tags:

Comments

Related Articles