How To Build A Ruby Gem With Bundler, Test-Driven Development, Travis CI And Coveralls, Oh My!

Advertisement

Ruby is a great language. It was designed to foster happiness and productivity in developers, all the while providing tools that are effective and yet focused on simplicity. One of the tools available to the Rubyist is the RubyGems521 package manager. It enables us both to include “gems” (i.e. packaged code) that we can reuse in our own applications and to package our own code as a gem to share with the Ruby2 community. We’ll be focusing on the latter in this article.

I’ve written an open-source gem named Sinderella3 (available on GitHub4), and in this article I’ll go through all of the steps I took to write the code (including the test-driven development5 process) and how I prepared it for release as a gem via RubyGems. I’ll also show you how to set up your tests to run through a continuous integration6 (CI) server using the popular Travis CI35137 service.

In case you’re unfamiliar with CI, it refers to the process of merging code with a central repository, with the aim of preventing integration problems down the road in a project’s life cycle. (If you use a version control system such as git8 and a decentralized code repository such as GitHub9, then you might already be familiar with these concepts.)

Finally, I’ll show you how to use Coveralls3710 to measure the code coverage of your tests and to obtain a statistical history of your commits.


Image credits: The Ruby11 and Bundler5012 logos, along with the Travis CI35137 mascot.

What We’ll Cover

32What Does Sinderella Do?

As described in the README on GitHub, Sinderella allows the author to “pass a code block to transform a data object for a specific period of time.” So, if we provide data like the following…

{ :key => 'value' }

… then we could, for example, convert it to the following for a set period of time:

{ :key => 'VALUE' }

Once the time period has expired, the data is returned to its normal state.

Sinderella is made up of two files: the main application and a data store that holds the original and transformed data.

Later in this article, I’ll describe my development process for creating the gem, and we’ll review some of the techniques required to produce a robust and stable gem.

33What We Won’t Cover

To be clear, this article is focused on creating a Ruby gem using Bundler and on following best practices, such as test-driven development and CI.

We won’t cover how to write Ruby code or how we developed the Sinderella gem. Nor will we cover how to write RSpec tests (although we will demonstrate how to set up RSpec). RSpec is a detail of implementation and can be swapped out for any testing library that you deem appropriate.

34Additional Requirements

To get started, you’ll need to register for accounts with the following services:

Registering for these services is free. Travis CI is free for all open-source projects (which this will be). You may pay for a Pro account, which allows you to set up CI for your private code repositories, but that’s not needed for what we’ll be doing here.

You’ll also need to be comfortable working in the command line. You don’t have to be a Unix shell scripting wizard, but I’ll be working here exclusively in a shell environment (specifically, using the Terminal38 on Mac OS X) to do everything, including running shell commands, opening multiplexers (such as tmux39) and editing code (with Vim40).

41Which Version Of Ruby To Use

Ruby has many different flavors:

  • Ruby42 (also known as Matz’s Ruby Interpreter) is the original language, written in C.
  • Rubinius43 is an implementation of Ruby that is written mainly with Ruby.
  • JRuby44 is an implementation of Ruby built on top of the Java Virtual Machine (JVM), with Java.

I deliberately used JRuby to implement Sinderella because part of the gem’s code relies on “threads45,” and MRI doesn’t provide true threading.

JRuby provides a native thread implementation because it is built on top of the JVM. But really, using any of the above variations would have been fine.

Unfortunately, though, it’s not all clear sailing with JRuby. Quite a few gems still use C extensions (i.e. code written in C that Ruby can import). At the moment, you can enable a flag in JRuby that allows it to use C extensions, but doing so is merely a temporary solution because this option is expected to be removed from JRuby in future releases.

This could be an issue, for example, if you’re using Pry5546 (a replacement for Ruby’s irb REPL). Pry works fine with JRuby, but you wouldn’t be able to take advantage of the equally amazing pry-plus47 extension, which offers many extra debugging capabilities, because some of its dependencies rely on C extensions.

I’ve worked around this limitation somewhat by using pry-nav48. It’s not as good and can be a little buggy in places when used under JRuby, but it gets the job done.

49Bundler

To help us create the gem, we’ll use the popular Bundler5012 gem.

Bundler is primarily designed to help you manage a project’s dependencies. If you’ve not used it before, then don’t worry because we’ll be taking advantage of a lesser known feature anyway, which is its ability to generate a gem boilerplate. (It also provides some other tools that will help us manage our gem’s packaging, which I’ll get into in more detail later on.)

Let’s begin by installing Bundler:

gem install bundler

Once Bundler is installed, we can use it to create our gem. But before doing that, let’s review some other dependencies that we’ll need.

51Dependencies

Developing the Sinderella gem requires five dependencies. Four are needed during the development process and won’t be needed in production. The fifth is a “hard” dependency, meaning that it is needed for the Sinderella gem to function properly.

Of these dependencies, Crimp and RSpec are specific to Sinderella. So, when developing your own gem, you would likely replace them with other gems.

RubyGems

We need to install RubyGems57 in order to take advantage of the package manager and its built-in gem commands (which Bundler will wrap with its own enhancements).

RSpec

RSpec is a testing framework for the Ruby programming language. We’ll cover this in more detail later on in the article.

When building your own gem, you might want to swap RSpec for a different testing tool. Another popular option is Cucumber58.

Guard

Guard is a command-line tool that responds to events. We’ll be using it to more easily write code for test-driven development. It works by monitoring files that you tell it to watch and then, when it notices changes to those files, triggering some command that you specify based on the type of file that was changed.

This comes in really handy when you’re running tests in a multiplexer such as tmux59 or when using a terminal such as iTerm260 (which supports multiple terminal windows being open at once), because while you’re editing the code in one terminal, you can get instant feedback to breaking tests as you work on the code. This is known as a tight feedback loop (more on this later).

Pry

Pry is a replacement REPL for Ruby’s standard irb. It offers everything the standard irb does but with a lot of additional features. It’s useful for testing code to see how it works and whether the Ruby interpreter fails to run it. It’s also useful for debugging code when something doesn’t work the way you expect.

It didn’t have much of a presence in the development of Sinderella, but it is such an important tool that I felt it deserved more than a cursory mention. For example, if you’re unsure of how a particular Ruby feature works, you could test drive it in Pry.

If you want to learn more about how to use it, then watch the screencast on Pry’s home page61.

Crimp

Crimp is a gem released by the BBC that allows you to convert a piece of data into a MD5 hash.

62Generating A Boilerplate

OK, now we’ve finally gotten to the point where we can generate the set-up files that will configure our gem file.

As mentioned, Bundler has the tools to generate the foundation of a gem so that we don’t have to type it all out by hand.

Now, open up the terminal and run the following command:

bundle gem sinderella

When that command is run, the following is generated:

❯ bundle gem sinderella
  create  sinderella/Gemfile
  create  sinderella/Rakefile
  create  sinderella/LICENSE.txt
  create  sinderella/README.md
  create  sinderella/.gitignore
  create  sinderella/sinderella.gemspec
  create  sinderella/lib/sinderella.rb
  create  sinderella/lib/sinderella/version.rb
Initializing git repo in /path/to/Sinderella

Let’s take a moment to review what we have.

Folder Structure

Bundler has automatically created a lib directory for us, which holds a single Ruby file named after our project. The name of the directory is extracted from the name provided via the bundle gem command.

Be aware that if you specify a hyphen (-) in the gem’s name, then Bundler will create a deeper folder structure by using the hyphen as a delimiter. For example, if your command looks like bundle gem foo-bar, then the following directory structure would be created:

├── lib
│   └── foo
│       ├── bar
│       │   ├── bar.rb
│       │   └── version.rb
│       └── bar.rb

This is actually quite useful when you’re producing multiple gems that are all namespaced under a single project. For a real-world example of this, look at BBC News’ GitHub repository63, which has multiple open-source gems published under the namespace alephant.

gemspec

The gemspec file is used to define the particular configuration of your gem. If you weren’t using Bundler, then you would need to manually create this file (according to RubyGems’ documentation).

Below is what Bundler generates for us:

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'sinderella/version'

Gem::Specification.new do |spec|
  spec.name          = "sinderella"
  spec.version       = Sinderella::VERSION
  spec.authors       = ["Integralist"]
  spec.email         = ["mark.mcdx@gmail.com"]
  spec.summary       = %q{TODO: Write a short summary. Required.}
  spec.description   = %q{TODO: Write a longer description. Optional.}
  spec.homepage      = ""
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.5"
  spec.add_development_dependency "rake"
end

As you’ll see later, this is a basic outline of the final gemspec file that we’ll need to create. We’ll end up adding to this file some of the other dependencies that our gem will need to run (both development and production dependencies).

For now, note the following details:

  • $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
    This adds the lib directory to Ruby’s load path, which makes require’ing files elsewhere in the code a little cleaner.
  • require 'sinderella/version'
    This loads in a version.rb file, which was generated when Bundler constructed our boilerplate. This file serves as a way to implement semantic versioning64 in our gem releases. Every time we release the gem, we’ll need to update the version number; then, when we run the particular Bundler command to release the gem, it will automatically pull in the updated value to our gemspec file.
  • Gem::Specification.new do |spec|
    Here, we define a new specification and include properties such as the name of the gem, the version number (see the previous point), a list of the authors of the gem and a contact email address. We can also include some descriptive text about the gem.
  • Next, we define the files to include in the gem. Any executable files found are injected dynamically into the file by looping through a bin directory (if one is found). We also dynamically inject a list of test files (which we’ll see later on when we create a spec folder to hold the tests that will ensure that the gem works as expected).
  • Finally, we define the dependencies, including both runtime and development dependencies. At the moment, there is only the latter, but soon enough we’ll have one runtime dependency to add.

The RubyGems guides has full details on the specification65. You could configure a whole host of settings, but Bundler helps us by defining the essential ones.

Gemfile

In a typical Ruby project, you’ll find that the Gemfile is filled with a list of dependencies, which Bundler then collates and installs for you. In this instance, because we’re generating a gem and not writing a standard application, our Gemfile will actually be pretty bare, made up of two lines: one to tell Bundler where to source the gems from, and the other to inform Bundler that the dependencies are listed in the gemspec file instead.

Rakefile

Again, in a typical Ruby application, a Rakefile will contain many different tasks (written in Ruby) that you can execute via the command line. In this case, a one-line Rakefile has been provided that loads bundler/gem_tasks. That in turn loads additional rake commands that Bundler adds to make it easier to build and deploy your gem. We’ll see how to use these commands later.

LICENSE.txt

Because we’re releasing code that could potentially be used by other developers, Bundler generates an MIT licence by default and dynamically injects the current year and your user name into it.

Feel free to either delete it or replace it with another license if the MIT one doesn’t fit your needs, although it’s pretty standard and relevant to most projects.

README

Lastly, Bundler has taken the tediousness out of generating a README file. It includes TODO messages wherever relevant, so that you know what needs to be manually added before the gem can be built (such as a description of the gem and a code example that shows how you expect the gem to be used). It also automatically generates installation instructions and a section on how other developers can fork your code and contribute new features and bug fixes.

One other benefit of Bundler is that it delivers a consistent code base across all gems you create. All gems will have the same structure, and the consistency across content such as the README file will make it easier for users who integrate more than one of your gems to understand them.

66Test-Driven Development

Test-driven development (TDD) is the process of building code on top of supporting tests. Sinderella was developed using its principles.

The guiding steps are “red, green, refactor,” and TDD fundamentally breaks down as the following:

  1. Write a test.
  2. Run the test and watch it fail (because there is no code yet for it to pass).
  3. Write the least amount of code to pass the test (literally, hack it together).
  4. Refactor the code so that it’s cleaner and better written.
  5. If the test has failed through refactoring, then start the red, green, refactoring process again.

This is sometimes referred to as a tight feedback loop: getting quick or instant feedback on whether code is working.

By writing the tests first, you ensure that every line of code exists for a reason. This is an incredibly powerful principle and one you should recall when caught in a debate over whether TDD “sucks” or “takes too long.”

Starting a project with tests can feel daunting. But in addition to ensuring that every line of code exists for a reason, it provides an opportunity for you to properly design the APIs.

67RSpec

As for writing tests for Sinderella, I chose to use RSpec6853, which is described thus on its website:

RSpec is a testing tool for the Ruby programming language. Born under the banner of behaviour-driven development, it is designed to make test-driven development a productive and enjoyable experience

In order to use RSpec in our gem, we’ll need to update the gemspec file to include more dependencies:

spec.add_development_dependency "rspec"
spec.add_development_dependency "rspec-nc"
spec.add_development_dependency "guard"
spec.add_development_dependency "guard-rspec"
spec.add_development_dependency "pry"
spec.add_development_dependency "pry-remote"
spec.add_development_dependency "pry-nav"

As you can see, we’ve added RSpec to our list of dependencies, but we’ve also included rspec-nc69, which provides native notifications on Mac OS X (rspec-nc is a nicety and not essential to produce the gem). Having notifications at the operating-system level can be quite handy, allowing you to do other things (perhaps check email) while tests run in the background.

We’ve also added (as you would expect) guard as a dependency, as well as guard-rspec, which Guard will need in order to understand how to handle RSpec-specific requests. This suite of Pry tools will debug any problems we come across and will be useful for any gems you develop in future.

RSpec Rake Tasks

Now that we’ve updated gemspec to include RSpec as a dependency, we’ll need to add an RSpec-related Rake task to our Rakefile, which will enable us (manually) or Guard to execute the task and run the RSpec test suite:

require 'rspec/core/rake_task'
require 'bundler/gem_tasks'

# Default directory to look in is `/specs`
# Run with `rake spec`
RSpec::Core::RakeTask.new(:spec) do |task|
  task.rspec_opts = ['--color', '--format', 'nested']
end

task :default => :spec

In the updated version of Rakefile above, we are loading an additional file that is packaged with RSpec (require 'rspec/core/rake_task'). This new file adds some RSpec-related modules and classes for us to use.

Once this code has loaded, we create a new instance of the RakeTask class (created when we loaded rspec/core/rake_task) and pass it a code block to execute. The code block we pass will define the options for our RSpec test suite.

Spec Files

Now that the majority of the RSpec test suite configuration is in place, the last thing we need to do is add a test file.

Let’s create a spec directory and, inside that, create sinderella_spec.rb:

require 'spec_helper'

describe Sinderella do
  it 'does stuff' do
    pending # no code yet
  end
end

You’ll see that we’ve included a temporary specification that states that the code “does stuff.” When the test suite is run, then this test will not cause any errors, even though no code has been implemented yet, because we have marked the test as “pending” (an RSpec-specific command). At this point, we’re only interested in getting a barebones set-up in place; we’ll flesh out the tests soon enough.

You may have noticed that we’re also loading another file, named spec_helper.rb. This type of file is typical in an RSpec suite and is used to load any dependencies or libraries that are required for the tests to run. The content of the spec helper file will look like this:

require 'pry'
require 'Sinderella'

All we’ve done here is load Pry (in case we need it for debugging) and the main Sinderella gem code (because this is what we want to test).

70Guard And tmux

At this point, we’ve gone over the set-up and preparation of RSpec and Rake (to get our testing framework in place). We also know what Guard is and how it helps us to test the code. Now, let’s go ahead and add a Guardfile to the root directory, with the following contents:

guard 'rspec' do
  # watch /lib/ files
  watch(%r{^lib/(.+).rb$}) do |m|
    "spec/#{m[1]}_spec.rb"
  end

  # watch /spec/ files
  watch(%r{^spec/(.+).rb$}) do |m|
    "spec/#{m[1]}.rb"
  end
end

This file tells Guard that we’re using RSpec to run our tests. It also defines which directories to watch for changes and what to do when it notices changes. In this case, we’re using regular expressions71 to match any files in the lib or spec directory and to execute the relevant RSpec command that runs our tests (or to run one specific test).

We’ll see in a minute how to actually run Guard. For now, let’s see how tmux fits this workflow.

tmux

Some developers prefer to have separate applications open (for example, a code editor such as Sublime Text72 and a terminal application to run tests). I prefer to use tmux to have multiple terminal shells open on one screen and to have Vim open on another screen to edit code. Thus, I can edit code and get visual feedback from the terminal about the state of the tests all on one screen. You don’t need to follow the exact same approach. As mentioned, there are other ways to get feedback, but I have found tmux and Vim to be the most suitable.

So, we have two tmux panes open, one in which Vim is running, and the other in which a terminal runs the command bundle exec guard (this is how we actually run Guard).

That command will return something like the following back to the terminal:

❯ bundle exec guard
09:53:55 - INFO - Guard is using Tmux to send notifications.
09:53:55 - INFO - Guard is using TerminalTitle to send notifications.
09:53:55 - INFO - Guard::RSpec is running
09:53:55 - INFO - Guard is now watching at '/path/to/Sinderella' 

From: /path/to/Sinderella/sinderella.gemspec @ line 1 :

 => 1: # coding: utf-8
    2: lib = File.expand_path('../lib', __FILE__)
    3: $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
    4: require 'sinderella/version'
    5: 
    6: Gem::Specification.new do |spec|

From this point on, you can press the Return key to run all tests at once, which will display the following message in the terminal:

09:57:41 - INFO - Run all
09:57:41 - INFO - Running all specs

This will be followed by the number of passed and failed tests and any errors that have occurred.

73Continuous Integration With Travis CI

As mentioned at the beginning, continuous integration (CI) is the process of merging code with a central repository in order to prevent integration problems down the road in a project’s life cycle.

We’ll use the free Travis CI service (which you should have signed up for by now).

Upon first viewing your “Accounts74” page in Travis CI, you’ll be presented with a complete list of all of your public GitHub repositories, from which you can select ones for Travis CI to monitor. Then, any time you push a commit to GitHub, Travis CI will run your tests.

Once you have selected repositories, you’ll be redirected to a GitHub “hooks” page, where you can confirm and authorize the configuration.

The Travis CI page for the Sinderella gem75 is where you can view the entire build history, including both passed and failed tests.

.travis.yml

To complete the configuration, we need to add a .travis.yml file. If you’ve enabled your repository from your Travis CI account and you don’t have a .travis.yml file, then Travis CI will throw an error and complain that you need one. Let’s look at the one we’ve set up for Sinderella:

language: ruby
cache: bundler

rvm:
  - jruby
  - 2.0.0

script: 'bundle exec rake'

notifications:
  email:
    recipients:
      - my@email.com
    on_failure: change
    on_success: never

Let’s go through each property to understand what it does:

  • language: ruby
    Here, we’re telling Travis CI that the language in which we’re writing tests is Ruby.
  • cache: bundler
    This tells Travis CI that we want it to cache the gems we’ve specified. (Running Bundler can be a slow process, and if your gems are unlikely to change often, then you don’t want to keep running bundle install every time you push a commit, because we want our tests to run as quickly as possible.)
  • rvm:
    This specifies the different Ruby versions and engines that we want our tests to run against (in this case, JRuby and MRI 2.0.0).
  • script: 'bundle exec rake'
    This gives Travis CI the command it requires to run the tests.
  • notifications:
    This indicates how we want Travis CI to notify us. Here, we’re specifying an email address to receive the notifications. We’re also specifying that an email should be sent only if a failure has occurred (there’s no point in getting thousands of emails telling us that nothing is wrong).

Preventing a Test Run

If you’re committing a change that doesn’t affect your code or tests, then you don’t want to waste time watching those non-breaking changes trigger a test run on Travis CI (no matter how fasts the tests are).

The easiest way to avoid this is to add [ci skip] anywhere in your commit message. Travis CI will see this and then happily ignore the commit.

76Code Coverage And Statistics With Coveralls.io

One last service we’ll use is Coveralls77, which you should have already registered for.

Coveralls works with your continuous integration server to give you test coverage history and statistics. Free for open source, pro accounts for private repos.

When you log into Coveralls for the first time, it will ask you to select repositories to monitor. It works like Travis CI, listing all of your repositories for you to enable and disable access. (You can also click a button to resynchronize the repository list, in case you’ve added a repository since last syncing).

To set up Coveralls, we need to add a file that tells Coverall what to do. For our project, we need to add a file to the root directory named .coveralls.yml, in which we’ll include a single line of configuration:

service_name: travis-ci

This tells Coveralls that we’re using Travis CI as our CI server. (If you’ve signed up for a Pro account, then use travis-pro instead.)

We also need to add the Coveralls gem to our gemspec:

spec.add_development_dependency "coveralls"

Finally, we need to include Coveralls’ code in our spec_helper.rb file:

require 'coveralls'
Coveralls.wear!

require 'pry'
require 'sinderella'

Notice that we have to load the code before the Sinderella code. If you load Coveralls after the application’s code has loaded, then it wouldn’t be able to hook into the application properly.

Let’s return to our TDD process.

78Skeleton Specification

When following TDD, I prefer to create a skeleton of a test suite, so that I have some idea of the type of API to develop. Let’s change the contents of the sinderella_spec.rb file to have a few empty tests:

require 'spec_helper'

describe Sinderella do
  let(:data) {{ :key => 'value' }}
  let(:till_midnight) { 0 }

  describe '.transforms(data, till_midnight)' do
    it 'returns a hash of the passed data' do
      pending
    end

    it 'stores original and transformed data' do
      pending
    end

    it 'restores the data to its original state after set time' do
      pending
    end
  end

  describe '.get(id)' do
    context 'before midnight (before time expired)' do
      it 'returns transformed data' do
        pending
      end
    end

    context 'past midnight (after time expired)' do
      it 'returns original data' do
        pending
      end
    end
  end

  describe '.midnight(id)' do
    it 'restores the data to its original state' do
      pending
    end
  end
end

Notice the pending command, which is provided by RSpec and allows the tests to run without throwing an error. (The suite will highlight pending tests that still need to be implemented so that you don’t forget about them.)

You could also use the fail command, but pending is recommended for unimplemented tests, particularly before you’ve written the code to execute them. Relish demonstrates some examples.79

From here on, I follow the full TDD process and write the code from the outside in: red, green, refactor.

For the first test I wrote for Sinderella, I realized that my code needs a way to create an MD5 hash from a data object, and that’s when I reached for the BBC News’ gem, Crimp80. Thus, I had to update the gemspec file to include a new runtime dependency: spec.add_runtime_dependency "crimp".

I won’t go step by step into how I TDD’ed the code because it isn’t relevant to this article. We’re focusing more on the principles of creating a gem, not on details of implementation. But you can get all of the gruesome details from the public list of commits in Sinderella’s GitHub repository81.

Also, you might not even be interested in the RSpec testing framework and might be planning on using a different framework to write your gem. That’s fine. Anyway, what follows is the full Sinderella specification file (as of February 2014):

sinderella_spec.rb

require 'spec_helper'

describe Sinderella do
  let(:data) {{ :key => 'value' }}
  let(:till_midnight) { 0 }

  def create_new_instance
    @id = subject.transforms(data, till_midnight) do |data|
      data.each do |key, value|
        data.tap { |d| d[key].upcase! }
      end
    end
  end

  describe '.transforms(data, till_midnight)' do
    it 'returns a MD5 hash of the provided data' do
      create_new_instance
      expect(@id).to be_a String
      expect(@id).to eq '24e73d3a4f027ff81ed4f32c8a9b8713'
    end
  end

  describe '.get(id)' do
    context 'before midnight (before time expired)' do
      it 'returns the transformed data' do
        Sinderella.stub(:check)
        create_new_instance
        expect(subject.get(@id)).to eq({ :key => 'VALUE' })
      end
    end

    context 'past midnight (after time expired)' do
      it 'returns the original data' do
        create_new_instance
        Sinderella.reset_data_at @id
        expect(subject.get(@id)).to eq({ :key => 'value' })
      end
    end
  end

  describe '.midnight(id)' do
    context 'before midnight (before time expired)' do
      it 'restores the data to its original state' do
        Sinderella.stub(:check)
        create_new_instance
        subject.midnight(@id)
        expect(subject.get(@id)).to eq({ :key => 'value' })
      end
    end
  end
end

data_store_spec.rb

require 'spec_helper'

describe DataStore do
  let(:instance)    { DataStore.instance }
  let(:original)    { 'bar' }
  let(:transformed) { 'BAR' }

  before(:each) do
    instance.set({
      :id => 'foo',
      :original => original,
      :transformed => transformed
    })
  end

  describe 'set(data)' do
    it 'stores original and transformed data' do
      expect(instance.get('foo')[:original]).to eq(original)
      expect(instance.get('foo')[:transformed]).to eq(transformed)
    end
  end

  describe 'get(id)' do
    it 'returns data object' do
      expect(instance.get('foo')).to be_a Hash
      expect(instance.get('foo').key?(:original)).to be true
      expect(instance.get('foo').key?(:transformed)).to be true
    end
  end

  describe 'reset(id)' do
    it 'replaces the transformed data with original data' do
      instance.reset('foo')
      foo = instance.get('foo')
      expect(foo[:original]).to eq(foo[:transformed])
    end
  end
end

Passing Specification

Here is the output of our passed test suite:

❯ rake spec
/path/to/.rubies/jruby-1.7.9/bin/jruby -S rspec ./spec/data_store_spec.rb ./spec/sinderella_spec.rb --color --format nested

DataStore
  set(data)
    stores original and transformed data
  get(id)
    returns data object
  reset(id)
    replaces the transformed data with original data

Sinderella
  .transforms(data, till_midnight)
    returns a MD5 hash of the provided data
  .get(id)
    before midnight (before time expired)
      returns the transformed data
    past midnight (after time expired)
      returns the original data
  .midnight(id)
    before midnight (before time expired)
      restores the data to its original state

Finished in 0.053 seconds
7 examples, 0 failures

82Design Patterns

According to Wikipedia83:

A design pattern in architecture and computer science is a formal way of documenting a solution to a design problem in a particular field of expertise.

Many design patterns exist, one of which in particular is usually frowned on84, the Singleton85 pattern.

I won’t debate the merits or problems of the Singleton design pattern, but I opted to use it in Sinderella to implement the DataStore class (which is the object that stores the original and transformed data), because what would be the point of having multiple instances of DataStore if the data is expected to be shared from a single access point?

Luckily, Ruby makes it really easy to create a Singleton. Just add include Singleton in your class definition.

Once you’ve done that, you will be able to access a single instance of your class only via an instance property — for example, MyClass.instance.some_method().

We saw the specification (or test file) for DataStore in the previous section. Below is the full implementation of DataStore:

require 'singleton'

class DataStore
  include Singleton

  def set(data)
    hash_data = {
      :original    => data[:original],
      :transformed => data[:transformed]
    }

    container.store(data[:id], hash_data)
  end

  def get(id)
    container.fetch(id)
  end

  def reset(id)
    original  = container.fetch(id)[:original]
    hash_data = {
      :original => original,
      :transformed => original
    }

    container.store(id, hash_data)
  end

  private

  def container
    @store ||= Hash.new
  end
end

86Badges

You might have seen some nice green badges in your favorite GitHub repository, indicating whether the tests associated with the code passed or not. Adding these to the README is straightforward enough:

[![Build Status](https://travis-ci.org/Integralist/Sinderella.png?branch=master)](https://travis-ci.org/Integralist/Sinderella) 

[![Gem Version](https://badge.fury.io/rb/sinderella.png)](http://badge.fury.io/rb/sinderella)

[![Coverage Status](https://coveralls.io/repos/Integralist/Sinderella/badge.png)](https://coveralls.io/r/Integralist/Sinderella)

The first badge is provided by Travis CI, which you can read more about in the documentation87.

The second is provided by RubyGems. You’ll notice on your gem’s page a “badge” link, which provides the required code and format (in this case, in Markdown88 format).

The third is provided by Coveralls. When you visit your repository page in the Coveralls application, you’ll see a link to “Get badge URLS”; from there, you can select the relevant format.

89REPL-Driven Development

Tests and TDD are a critical part of the development process but won’t eliminate all bugs by themselves. This is where a tool such as Pry can help you to figure out how a piece of code works and the path that the code takes during a conditioned execution.

To use Pry, enter the pry command in the terminal. As long as Pry is installed and available from that directory, you’ll be dropped into a Pry session. To view all available commands, run the help command.

Testing a Local Gem Build

If you want to run the gem outside of the test suite, then you’ll want to use Pry. To do this, we’ll need to build the gem locally and then install that local build.

To build the gem, run the following command from your gem’s root directory: gem build sinderella.gemspec. This will generate a physical .gem file.

Once the gem is built and a .gem file has been created, you can install it from the local file with the following command: gem install ./sinderella-0.0.1.gem.

Notice that the built gem file includes the version number, so that you know you’re installing the right one (in case you’ve built multiple versions of the gem).

After installing the local version of the gem, you can open a Pry session and load the gem with require 'sinderella' and continue to execute your own Ruby code within Pry to test the gem as needed.

90Releasing Your Gem

Once our gem has passed all of our tests and we’ve built and run it locally, we can look to release the gem to the Ruby community by pushing it to the RubyGems server.

To release our gem, we’ll use the Rake commands provided by Bundler. To view what commands are available, run rake --task. You’ll see something similar to the following output:

rake build    # Build sinderella-0.0.1.gem into the pkg directory
rake install  # Build and install sinderella-0.0.1.gem into system gems
rake release  # Create tag v0.0.1 and build and push sinderella-0.0.1.gem t...
rake spec     # Run RSpec code examples
  • rake build
    This first task does something similar to gem build sinderella.gemspec but placing the gem in a pkg (package) directory.
  • rake install
    The second task does the same as gem install ./sinderella-0.0.1.gem but saves us the extra typing.
  • rake release
    The third task is what we’re most interested at this point. It creates a tag in git, indicating the relevant version number, pulled from the version.rb file that Bundler created for us. It then builds the gem and pushes it to RubyGems.
  • rake spec
    The fourth task runs the tests using the test runner (in this case, RSpec), as defined and configured in the main Rakefile.

To release our gem, we’ll first need to make sure that the version number in the version.rb file is correct. If it is, then we’ll commit those changes and run the rake release task, which should give the following output:

❯ rake release
sinderella 0.0.1 built to pkg/sinderella-0.0.1.gem.
Tagged v0.0.1.
Pushed git commits and tags.
Pushed sinderella 0.0.1 to rubygems.org.

Now we can view the details of the gem at https://rubygems.org/gems/sinderella91, and other users may access our gem in their own code simply by including require 'sinderella'.

92Conclusion

Thanks to the use of Bundler, the process of creating a gem boilerplate is made a lot simpler. And thanks to the principles of TDD and REPL-driven development, we know that we have a well-tested piece of code that can be reliably shared with the Ruby community.

(al, il)

Footnotes

  1. 1 http://rubygems.org/
  2. 2 https://www.ruby-lang.org/
  3. 3 https://rubygems.org/gems/sinderella
  4. 4 https://github.com/Integralist/Sinderella
  5. 5 http://en.wikipedia.org/wiki/Test_driven_development
  6. 6 http://en.wikipedia.org/wiki/Continuous_integration
  7. 7 https://travis-ci.org/
  8. 8 http://git-scm.com/
  9. 9 https://github.com/
  10. 10 https://coveralls.io/
  11. 11 https://www.ruby-lang.org/en/
  12. 12 http://bundler.io/
  13. 13 https://travis-ci.org/
  14. 14 #a1
  15. 15 #a2
  16. 16 #a3
  17. 17 #a4
  18. 18 #a5
  19. 19 #a6
  20. 20 #a7
  21. 21 #a8
  22. 22 #a9
  23. 23 #a10
  24. 24 #a11
  25. 25 #a12
  26. 26 #a13
  27. 27 #a14
  28. 28 #a15
  29. 29 #a16
  30. 30 #a17
  31. 31 #a18
  32. 32 #
  33. 33 #
  34. 34 #
  35. 35 https://travis-ci.org/
  36. 36 https://rubygems.org/
  37. 37 https://coveralls.io/
  38. 38 http://en.wikipedia.org/wiki/Terminal_(OS_X
  39. 39 http://en.wikipedia.org/wiki/Tmux
  40. 40 http://www.integralist.co.uk/posts/a-guide-to-getting-started-with-vim/
  41. 41 #
  42. 42 http://ruby-lang.org/
  43. 43 http://rubini.us/
  44. 44 http://jruby.org/
  45. 45 http://en.wikipedia.org/wiki/Multi-threaded
  46. 46 http://pryrepl.org/
  47. 47 https://rubygems.org/gems/pry-plus
  48. 48 https://github.com/nixme/pry-nav
  49. 49 #
  50. 50 http://bundler.io/
  51. 51 #
  52. 52 http://rubygems.org/
  53. 53 http://rspec.info/
  54. 54 http://guardgem.org/
  55. 55 http://pryrepl.org/
  56. 56 https://rubygems.org/gems/crimp
  57. 57 https://rubygems.org/pages/download
  58. 58 http://cukes.info/
  59. 59 http://tmux.sourceforge.net/
  60. 60 http://www.iterm2.com/
  61. 61 http://pryrepl.org/
  62. 62 #
  63. 63 https://github.com/BBC-News/
  64. 64 http://semver.org/
  65. 65 http://guides.rubygems.org/specification-reference/
  66. 66 #
  67. 67 #
  68. 68 http://rspec.info/
  69. 69 https://github.com/twe4ked/rspec-nc
  70. 70 #
  71. 71 http://www.regular-expressions.info/
  72. 72 http://www.sublimetext.com/
  73. 73 #
  74. 74 https://travis-ci.org/profile
  75. 75 https://travis-ci.org/Integralist/Sinderella
  76. 76 #
  77. 77 https://coveralls.io
  78. 78 #
  79. 79 https://www.relishapp.com/rspec/rspec-core/v/2-3/docs/pending/pending-examples
  80. 80 https://rubygems.org/gems/crimp
  81. 81 https://github.com/Integralist/Sinderella/commits/master
  82. 82 #
  83. 83 http://en.wikipedia.org/wiki/Design_pattern
  84. 84 https://www.google.com/search?q=singleton%20design%20pattern%20is%20bad#q=singleton%20design%20pattern%20good%20or%20bad
  85. 85 http://en.wikipedia.org/wiki/Singleton_pattern
  86. 86 #
  87. 87 http://docs.travis-ci.com/user/status-images/
  88. 88 http://en.wikipedia.org/wiki/Markdown
  89. 89 #
  90. 90 #
  91. 91 https://rubygems.org/gems/sinderella
  92. 92 #

↑ Back to topShare on Twitter

Senior BBC News Engineer. Utilises Object-Oriented Design principles with Ruby, Node, PHP, BEM. Incorporates AMD, TDD and BDD. Vim and tmux user. Unix Shell Scripter (Awk, Sed, Grep... etc)

Advertising
  1. 1

    Nice article, thanks! (Comprehensive)

    0
  2. 2

    Burin Choomnuan

    May 10, 2014 11:58 pm

    Very nice article. I wish I found your article when I start writing my first ruby gem.
    I took almost the same approach as outline in your article when I first start.
    After 8th gems I found that there were some patterns that can be automated so I ended up creating a gem called ‘gem_bootstrap’ [http://rubygems.org/gems/gem_bootstrap] which create a starting template for the new thus saving tons of time for the whole process.

    0
  3. 3

    Being a trainee Ruby on Rails developer, I’m glad to find this article about building a Ruby Gem.

    Thanks,
    Harshada

    0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top