Saturday, November 20, 2010

Creating and publishing your first ruby gem

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.

timestamped-scenarios, a custom Cucumber formatter

I just published a new Ruby gem called timestamped-scenarios. As usual the code is also available on github.

What's this gem for?

This gem provides custom cucumber formatters which append a test run timestamp to each scenario name as it is generated. a Timestamped::PrettyFormatter and Timestamped::HtmlFormatter are supplied already, but you can use the AddsTimestamp module to dynamically add timestamping to pretty much any Cucumber formatter.


You might wonder why I created these formatters. I'm currently working on an iOS project where we are using Frank and Cucumber to run automated acceptance tests against the application. We have integrated these acceptance tests into our CI build, and as part of that integration we record a screen capture movie of each test run. By adding timestamps to each scenario name we can easily skip to the part of the movie which shows that scenario executing. This is useful when trying to figure out the context around why a
specific scenario failed.


How do you use it?

As with my other cucumber formatter gem (slowhandcuke), getting set up with timestamped-scenarios is super easy. just install the gem with

gem install timestamped-scenarios
and you're ready to use them by executing cucumber with something like
cucumber --format 'Timestamped::HtmlFormatter' --out cucumber.html
You can also add these parameters to your rake cucumber task, or to your cucumber.yml config file. There's also a little sample project in the timestamped-scenarios repo on github which shows how to use the formatters.


What's it look like?

Here's an example of what the Timestamped::PrettyFormatter output looks like:


∴  cucumber 
Using the default profile...
Feature: demonstration

  Scenario: The First [0:00]      # the.feature:3
    Given I wait 5 seconds # step_definitions/steps.rb:1

  Scenario: The Second [0:05]       # the.feature:6
    Given I wait 0.7 seconds # step_definitions/steps.rb:1
    Given I wait 3 seconds   # step_definitions/steps.rb:1

  Scenario: The Third [0:09] # the.feature:10

3 scenarios (3 passed)
3 steps (3 passed)
0m8.709s