Pete Hodgson

Software Delivery Consultant

Creating and publishing your first ruby gem

November 20, 2010

Introduction

In this post I’m going to cover the basics of creating and publishing a gem using the bundle gem command provided by Bundler. We’re going to use bundler to create a gem template for us. We’ll then take that skeleton gem, add some functionality to it, and publish it for all the world to use.

For the purposes of this tutorial I need a very simple example of something which you could conceivably want to release as a gem. How about a simple Sinatra web app which tells you the time? Sure, that’ll work. We’ll call it Didactic Clock. In order to make this server implementation need more a couple of lines of code we’ll add the requirement that the clock tells you the time in a verbose form like “34 minutes past 4 o’clock, AM”.

Preparing to create a gem

A great way to create and test gems in a clean environment is to use the awesome rvm and in particular rvm’s awesome gemset feature. I assume you’re already set up with rvm. If not go get set up now!

First off we’ll create a seperate gemset so that we can create and install our gem in a clean environment and be sure that someone installing our gem will have all the dependencies they need provided to them. We’re going to be creating a gem called didactic_clock, so we’ll name our gemset similarly. We’ll create the gemset and start using it by executing:

 rvm gemset create didactic_clock
 rvm gemset use didactic_clock

From now on I’ll assume we’re always using this clean-room gemset.

Creating the skeleton

First lets install bundler into our gemset:

gem install bundler

Now we’ll ask bundler to create the skeleton of a gem. In this tutorial we’re going to be creating a gem called didactic_clock. We’ll ask bundler to create a skeleton for a gem with that name by calling:

bundle gem didactic_clock

You should see some output like:

  create  didactic_clock/Gemfile
  create  didactic_clock/Rakefile
  create  didactic_clock/.gitignore
  create  didactic_clock/didactic_clock.gemspec
  create  didactic_clock/lib/didactic_clock.rb
  create  didactic_clock/lib/didactic_clock/version.rb
Initializating git repo in /Users/pete/git/didactic_clock

Modifying our gemspec

Bundler creates a basic .gemspec file which contains metadata about the gem you are creating. There are a few parts of that file which we need to modify. Let’s open it up and see what it looks like:

 
   # -*- encoding: utf-8 -*-
   $:.push File.expand_path("../lib", __FILE__)
   require "didactic_clock/version"
 
   Gem::Specification.new do |s|
    s.name        = "didactic_clock"
    s.version     = DidacticClock::VERSION
    s.platform    = Gem::Platform::RUBY
    s.authors     = ["TODO: Write your name"]
    s.email       = ["TODO: Write your email address"]
    s.homepage    = "http://rubygems.org/gems/didactic_clock"
    s.summary     = %q{TODO: Write a gem summary}
    s.description = %q{TODO: Write a gem description}
 
    s.rubyforge_project = "didactic_clock"
 
    s.files         = `git ls-files`.split("\n")
    s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
    s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
    s.require_paths = ["lib"]
   end

You can see that Bundler has set up some sensible defaults for pretty much everything. Note how your gem version information is pulled out of a constant which Bundler was nice enough to define for you within a file called version.rb. You should be sure to update that version whenever you publish any changes to your gem. Follow the principles of Semantic Versioning.

Also note that there are some TODOs in the authors, email, summary, and description fields. You should update those as appropriate. Everything else can be left as is for the time being.

Adding a class to our lib

We’ll start by creating a TimeKeeper class which will report the current time in the verbose format we want the Didactic Clock server to use. To avoid polluting the client code’s namespace it is important to put all the classes within your gem in an enclosing namespace module. In our case the namespace module would be DidacticClock, so we’re creating a class called DidacticClock::TimeKeeper. Another convention which is important to follow when creating gems is to keep all your library classes inside a folder named after your gem. This avoids polluting your client’s load path when your gem’s lib path is added to it by rubygems. So taking both of these conventions together we’ll be creating a DidacticClock::TimeKeeper class in a file located at lib/didactic_clock/time_keeper.rb. Here’s what that file looks like:

 
    module DidacticClock
    class TimeKeeper
     def verbose_time
      time = Time.now
      minute = time.min
      hour = time.hour % 12
      meridian_indicator = time.hour < 12 ? 'AM' : 'PM'
 
      "#{minute} minutes past #{hour} o'clock, #{meridian_indicator}"
     end
    end
   end

Adding a script to our bin

We want users of our gem to be able to launch our web app in sinatra’s default http server by just typing didactic_clock_server at the command line. In order to achieve that we’ll add a script to our gem’s bin directory. When the user installs our gem the rubygems system will do whatever magic is required such that the user can execute the script from the command line. This is the same magic that adds the spec command when you install the rspec gem, for example.

So we’ll save the following to bin/didactic_clock_server

 
   #!/usr/bin/env ruby
 
   require 'sinatra'
   require 'didactic_clock/time_keeper'
 
   # otherwise sinatra won't always automagically launch its embedded 
   # http server when this script is executed
   set :run, true
 
   get '/' do
    time_keeper = DidacticClock::TimeKeeper.new
    return time_keeper.verbose_time
   end

Note that we require in other gems as normal, we don’t require rubygems, and that we don’t do any tricks with relative paths or File.dirname(__FILE__) or anything like that when requiring in our TimeKeeper class. Rubygems handles all that for us by setting up the load path correctly.

Adding a dependency

Our little web app uses Sinatra to serve up the time, so obviously we need the Sinatra gem installed in order for our own gem to work. We can easily express that dependency by adding the following line to our .gemspec:

s.add_dependency "sinatra"

Now Rubygems will ensure that sinatra is installed whenever anyone installs our didactic_clock gem.

Building the gem and testing it locally

At this point we’re done writing code. Bundler created a git repo as part of the bundle gem command. Let’s check in our changes to the git repo. git commit -a should do the trick, but obviously feel free to use whatever git-fu you prefer.

Now we’re ready to build the gem and try it out. Make sure you’re still in the clean-room gemset we created earlier, and then run:

rake install

to build our didactic_clock gem and install it into our system (which in our case means installing it into our didactic_clock gemset). If we run gem list at this point we should see didactic_clock in our list of gems, along with sinatra (which will have been installed as a dependency).

Now we’re ready to run our app by calling didactic_clock_server from the command line. We should see sinatra start up, and if we visit http://localhost:4567/ we should see our app reporting the time in our verbose format. Victory!

Publishing our gem

The last step is to share our creation with the world. Before we do that you’ll need to set up rubygems in your system to publish gems. The instructions at rubygems.org are easy to follow.

Bundler provides a rake publish task which automates the steps you would typically take when publishing a version of your gem, but it’s fairly opinionated in how it does so. The task will tag your current git commit, push from your local git repo to some upstream repo (most likely in github), and then finally build your gem and publish your .gem to rubygems.org. If you don’t have an upstream repo configured then you’ll probably get an error like:

   rake aborted!
   Couldn't git push. `git push  2>&1' failed with the following output:
 
   fatal: No destination configured to push to.

So, now would be the time to set up an upstream repo. Doing that with github is really straightforward. Once you have your local git repo configured with an upstream repo you can finally publish your gem with rake publish.

Now anyone who wants to install your gem can do so with a simple gem install command. Congratulations! Fame and fortune await you!

Conclusion

Hopefully I’ve shown that creating and publishing a well-behaved gem is pretty simple. The didactic_clock sample I created is up on github, and of course the gem is published on rubygems.org and can be installed with gem install didactic_clock.