On key factor of Ruby’s success as a language - apart from its beautiful expressiveness - is it’s package management ecosystem - RubyGems. Just like Ruby, RubyGems is optimised for developer happiness - it makes the packaging and dependency requirements of distributed software a pleasure.

This post is a brief introduction to using, writing, and sharing gems in Ruby based on a talk I gave to the BrisRuby group this month. The intended audience is the beginner Rubyist. The source code found here.

What is a Gem?

Gems are self contained chunks of Ruby code - modules, packages, & libraries - that are designed to be shared and used either independent or more frequently, to extend functionality in other code bases.

RubyGems is Ruby’s package management system. Along with Bundler and the Gem gems, it provides a standardised format for creating, sharing, and reusing ruby gems. And just like Rails, this consistent set of conventions is what makes it so powerful.

Using Gems

The two most common ways to use gems is to install them directly or with a dependency manager like Bundler. The direct method installs a gem on your system and is the best option if your gem is a stand alone program or includes a console based API. To install Rails for example, you’d just execute the following one liner:

gem install rails

The other method is to use a gemfile and bundler. In this case, you declare all the gems you want to use in the gemfile, along with any group namespaces:

# Gemfile
source 'https://rubygems.org'
gem 'geokit'
gem 'rack', '~>1.1'

group :production do
  gem 'pg'
end

group :test do
  gem 'sqlite3'
end

Then launch your program with bundler…

bundle exec my_cool_app

Two points to note here are that the pg and sqlite3 gems will only be loaded in their respective environments and that rake gem also has a version argument. Versions can be exact or approximately greater than so ~> 2.0.1 means equal to or greater than in the last digit, ie 2.0.x.

Naming a gem

Before you build and share a gem, it’s a good idea to make sure the functionality your are producing doesn’t already exist. Search for the behaviour on the RubyGems website and The Ruby Toolbox to prevent needless duplication.

If you didn’t find anything there, then think up snappy name that preferably contains some word-play, innuendo, or double entendre.

Need full text search in PostGres? You need Texticle.

Written a command line interface time tracker? Call it Clitt.

Or looking for a way to extend hashes? Hash Dealer has you covered.

And if you are still struggling for a name, then just choose the 17th word on a random page from 4chan. Now the gem we are going to build today is possibly the most useless gem every created. And given the dubiuous quality surrounding it, and what it actually does, the only appropriate name for it is ify.

Building a gem

Bundler makes building a gem effortless so fire up terminal and type:

bundle gem ify

Bundle has created a gem skeleton for us that we now need to populate. The most important files are be the README and the gemspec.

  create  ify/Gemfile
  create  ify/Rakefile
  create  ify/LICENSE.txt
  create  ify/README.md
  create  ify/.gitignore
  create  ify/ify.gemspec
  create  ify/lib/ify.rb
  create  ify/lib/ify/version.rb

Every good open source project has a compressive README with a clear and unambitious description of what the project does and how it does it. Make sure you document the public interface of your gem by showing how it can be used. Write your readme first.

Now it’s time to code the awesome sauce and we’ll start with a spec.

# spec/ify_spec.rb
require 'ify'
require 'minitest/autorun'

describe "Ify" do
  it "ifyifies a string" do
    "string".ify.must_equal "stringify"
  end
end

Running our test results in a failure because we haven’t written any code yet :(

# Running:

E

Finished in 0.001509s, 2650.7621 runs/s, 0.0000 assertions/s.

  1) Error:
Ify#test_0001_ifyifies a string:
NoMethodError: undefined method `ify' for "string":String
    /Users/dave/Desktop/Projects/ify/spec/ify_spec.rb:6:in `block (2 levels) in <top (required)>'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

Our gem is going to extend the functionality of the String core library via a technique call monkey patching. The simplest way to get our test to pass is to simply append “ify” to the end of a string when the ify() method is called

# lib/ify.rb
class String
  def ify()
    "#{self}ify"
  end
end

Running our test again results in a success!

# Running:

.

Finished in 0.001744s, 573.3945 runs/s, 573.3945 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

We then iterate this fail-code-pass process until we achieve the desired behaviour outlined in our readme.

# spec/ify_spec.rb
describe "Ify" do
  it "ifyifies a string" do
    "string".ify.must_equal "stringify"
  end

  it "is vowel aware" do
    "awesome".ify.must_equal "awesomify"
  end

  it "ignores trailing whitespace" do
    "spot ".ify.must_equal "spotify"
  end

  it "plays well with small animals" do
    "bee".ify.must_equal "beeify"
    "fruit fly".ify.must_equal "fruit flyify"
  end
end


# lib/ify.rb
class String
  def ify()
    str = self.rstrip
    str.sub! /[aeiouy]$/, '' if str.split.last.length > 3
    "#{str}ify"
  end
end

Packaging a gem

Once our code is working as desired, it’s time to package it up into gem to make sharing and reuse a breeze. First, we ensure that our gemspec is up to date. The bundler generator has does most of the work for use, so we just need to fill out a few more fields.

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

Gem::Specification.new do |spec|
  spec.name          = "ify"
  spec.version       = Ify::VERSION
  spec.authors       = ["Dave Kinkead"]
  spec.email         = ["[email protected]"]
  spec.description   = %q{Ifyify anything}
  spec.summary       = %q{A vowel aware ifyfier of dubious quality.}
  spec.homepage      = "https://github.com/davekinkead/ify"
  spec.license       = "MIT"

  spec.files         = `git ls-files`.split($/)
  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.3"
  spec.add_development_dependency "rake"
end

Then, we package it with the command and we are done!

gem build ify.gemspec

Sharing a gem

Our gem of dubious quality is now ready to be shared with the world. There are a few ways we can do this:

  • locally so only those with access to our file system can use it.
  • on our own private or public gem server.
  • on the rubygems.org server.

Opens source is awesome sauce so we’ll use the latter approach. First you’ll need to create an account on rubygems.org. Once that’s done, all you need is:

gem push ify-0.0.1.gem

That’s it. You are now an open source contributor! Of dubious quality…

Resources