Let's Make a Gem!

What is a Gem?

A gem is a collection of Ruby code that is shareable. It is a way for Ruby developers to share functionality across projects. There are heavier gems, like devise, that can handle user authentication in a Rails app, and lighter gems that can offer your application just a snippet of additional functionality.

Even better, Ruby gems offer that functionality without you having to include and maintain third party code directly in your app. Instead, you can just include the name of the gem (and the version) in your Gemfile. Then, package managers like Bundler, which we'll be using today, will handle the dependencies required by that gem.

Why Build a Gem?

There are a number of reasons why you might find it worthwhile to a built a gem.

Keeping it DRY -- if you find yourself using the same code again and again throughout your application, it should raise a red flag. That's what we call a code smell. There are many ways you could refactor in such a situation. For example, in my "Title-ize" application that inspired the Titlegen gem we'll be discussing today, I first refactored the code that generates a random title into a service object. However, if you feel your repetitious code could be useful to other developers if packaged, you might want to considered extracting it into a gem and offering it to the world. Which brings me to the second reason you might want to make a gem...

Being a nice person -- if you've written code that you feel could be useful to other developers by offering commonly needed functionality, you should offer that code to other developers. People will thank you! Which brings me to the third reason...

Becoming famous -- just kidding (sorry). But, packaging a gem and offering it to the open source community is a great way to get your name and work out into the world. This has the added benefit of open sourcing your code to other developers who can improve upon it.

Now that I've completely convinced you beyond all shadow of a doubt to make your very own gem, let's do it!

The Titlegen Gem

Recently, I developed a chatting application with Rails that lets users play a rousing game of “Titlie-ize!”. The game generates a random movie title and you and your friends take turns coming up with tag lines and descriptions. That application required me to write a class that would randomly generate a new movie title for each round of the game. I thought my nifty title generator might be useful in other contexts as well (perhaps you’d like to generate a haiku for instance?), so I decided to package it as a gem for future to developers to use.

Turns out, making a simple gem is just that—simple.

Using Bundler

You've probably used Bundler to manage the gems and dependencies in your Rails applications. Bundler has some additional functionality that you may be less familiar with--it has a built in gem skeleton that you can use to create a base gem for you to edit.

We begin by running bundle gem <gem_name>

This generates our gem skeleton with the following file structure:

titlegen 
    |- bin
    |- lib
        |- titlegen.rb
        |- titlegen
             |- version.rb
             |- < other modules/classes that             
                 titlegen.rb might use>
    |- spec
        |-titlgen_spec.rb
        |-spec_helper.rb
    |- .gitignore
    |- .rspec
    |- .travis.yml
    |- Gemfile
    |- LICENSE.txt
    |- README.md
    |- titlegen.gemspec

Let's talk a about what each of these files is responsible for.

The code that gives our gem its actual functionality (i.e. the code to generate a title) goes in the lib directory. We give that file the same name as the gem itself. The reason for this is that, when a user runs require ‘titlegen’, the lib files are loaded. This is the same reason that we create another directory under lib called titlegen that houses any additional classes or modules that titlegen.rb might utilize. Our titlgen subdirectory also holds the version.rb file. This file is responsible for specifying the version number of your gem. Every time you deploy a new version, you must update this file.

Your README.md file you can edit to include the appropriate descriptions and instructions for your gem.

Your LICENSE.txt file is an automatically generated MIT license for your gem.

The last file we'll discuss before we actually start coding our gem is the Gemspec file

The Gemspec

Your automatically generated gemspec files should look something like this:

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

Gem::Specification.new do |spec|
  spec.name          = "titlegen"
  spec.version       = Titlegen::VERSION
  spec.authors       = ["SophieDeBenedetto"]
  spec.email         = ["myemail@email.com"]

  spec.summary       = %q{< add your summary >}
  spec.description   = %q{< add your description >.}
  spec.homepage      = "< your gem's github repo >"
  spec.license       = "MIT"

  # Prevent pushing this gem to RubyGems.org by      
    setting 'allowed_push_host', or
  # delete this section to allow pushing this gem to         
  any host.
  if spec.respond_to?(:metadata)
    spec.metadata['allowed_push_host'] = "< set this           
    to rubygems.org>"
  else
    raise "RubyGems 2.0 or newer is required to                   
    protect against public gem pushes."
  end

  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) {              
                          |f| File.basename(f) }
  spec.require_paths = ["lib"]
  spec.add_development_dependency "bundler", "~> 1.9"
  spec.add_development_dependency "rspec", "~> 1.9"
  spec.add_development_dependency "rake", "~> 10.0"
end

The Specification class contains the information for a Gem. The gemspec defines what’s in the gem, who made it, and the version of the gem and contains other metadata about your gem. It also interfaces with RubyGems.org--all of the information you see on a gem page there comes from the gemspec.

The first set of lines (name, version, email, authors, summary, description, homepage, license) in our specification block are automatically generated and are fairly self-explanatory.

Let's take a look at some of the other useful attributes:

spec.files specify the files to be included in the gem.

spec.require_paths specify the paths in the gem to add to $LOAD_PATH when your gem is activated.

spec.add_development_dependency adds any gems that your code requires in development mode.

spec.bindir specifies the path in the gem for executable scripts. This is usually 'bin'

spec.executables specifies the path to any executables included in the gem.

Loading Code

Lastly, at the top of this file we have the following lines:

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

The $LOAD_PATH is how the require statement (that future users of your gem will use to include your gem) picks up new code. We are appending the files in your lib directory to Ruby's load path and the lib directory is where we house the code that gives our gem its functionality.

Now that we understand our file structure and have begun to set up our Gemspec, we're ready to start developing! Since we'll be creating our gem with Test Driven Development, we'll start by writing out tests.

Setting Up Testing

We've already set our development dependencies in our Gemspec to include rspec and rake. The Gemfile that was created when we bundled the skeleton of our gem looks like this:

source 'https://rubygems.org'

# Specify your gem's dependencies in titlegen.gemspec

gemspec

No need to to write anything else--rspec and rake are available to us.

Our spec_helper.rb file was bundled with everything it needs:

$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'titlegen'

And our titlegen_spec.rb bundled with some preliminary tests:

>require 'spec_helper'

describe Titlegen do
  it 'has a version number' do
    expect(Titlegen::VERSION).not_to be nil
  end
end

We can now run rspec in the command line and see one passing test!

If you'd prefer to run your tests via rake by executing rake in the command line, we need to make some additions to our Rakefile:

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new

task :default => :spec
task :test => :spec

Now we're ready to start writing our own tests. The Titlegen gem needs to generate a random title that consists of three words: a pronoun, an adjective and a noun. For example: "Their Glamorous Station". Our first test is very basic. We'll confirm that the result of generating a title with a .generate_title method does in fact contain three words.

describe Titlegen do
  it 'has a version number' do
    expect(Titlegen::VERSION).not_to be nil
  end

  it 'generates a random title' do
    title = Titlegen.generate_title
    title_array = title.split

    expect(title_array.length).to eq 3
  end
end

Now if we run rake or rspec we should pass the first test and fail the second.

We know that we need to select from a group of pronouns, adjectives and nouns to generate each title. We will therefore need to store each of these groups of words somewhere and sample from those storage units each time we create a new title. Let's extract this functionality into a Dictionary module that we can include in our Titlegen class.

Why use a module?

The single responsibility principle - The Titlegen class is responsible for putting a title together. That is the single responsibility it holds. It should not know about parts of speech or how to acquire them.

Code Reuse - The ability to sample from a collection of different parts of speech is its own discrete functionality. We or other developers could easily have use of this capability without wanting to generate a title (think about my earlier haiku generation example). For this reason, it makes sense to keep this code separate from the Titlegen class.

Before we move on to getting out tests to pass, let's write some for the Dictionary module.

In the spec directory, create a new file: dictionary_spec.rb. Then, in your spec_helper.rb, add the following line: require 'titlegen/dictionary'

What does our Dictionary module need to be capable of? What are we testing for?

Our dictionary module should contain a constant, DICTIONARY, that acts as just that. It will be a hash with keys for adjectives, pronouns and nouns, which point to an array of adjectives, pronouns and nouns respectively. We'll develop a method for each part of speech. .pronoun will return a random pronoun, .adjective a random adjective and .noun a random noun.

We're ready to write out tests:

require 'spec_helper'

describe "Dictionary Module" do

  it "generates a randon pronoun" do
    my_pronoun = Dictionary.pronoun

    expect(Dictionary::DICTIONARY[:pronouns]).to include(my_pronoun)
  end

  it "generates a random adjective" do
    my_pronoun = Dictionary.adjective

    expect(Dictionary::DICTIONARY[:adjectives]).to include(my_pronoun)
  end

  it "generates a randon noun" do
    my_pronoun = Dictionary.noun

    expect(Dictionary::DICTIONARY[:nouns]).to include(my_pronoun)
  end
end

Now when we run rake or rspec in the command line, we should see our Titlgen tests and our Dictionary tests, all of which should fail.

Let's make those tests pass!

Dictionary Module

The dictionary.rb file belongs in the titlegen subdirectory of our lib directory. Later, we will require it at the top of our titlegen.rb file.

Let's build our dictionary hash and the above-mentioned methods:

module Dictionary

   DICTIONARY = {
     :pronouns => ["A", "The", "Their", "Some",                
        "None" etc...], 
     :adjectives => ["a really long list I won't 
        reproduce here"],
      :nouns => ["another really long list I won't       
        reproduce here"]
   }

   def Dictionary.pronoun 
    DICTIONARY[:pronouns].sample
   end

   def Dictionary.adjective 
    DICTIONARY[:adjectives].sample
   end

   def Dictionary.noun
    DICTIONARY[:nouns].sample
   end
end

Now, if we run our dictionary tests, you should seem them all passing.

Titlegen Class

First, let's add the necessary requirements to the top of the file:

require "titlegen/version"
require "titlegen/dictionary"
require 'active_support/inflector'

The third line introduces something new: the active support inflector gem. Them gem gives us the ability to call .pluralize and .singularize on any word. We need this functionality to generate titles with good grammar. In order for our gem to utilize the inflector gem, we need to add it to our Gemspec. In your gemspec file, add the following line:

spec.add_dependency "activesupport-inflector", ['~> 0.1.0']

We don't specify runtime vs. development dependency because we need this gem for both.

Make sure you bundle install before continuing.

Okay, let's generate some titles:

class Titlegen
  
  include Dictionary

  def self.generate_title
    adjective = Dictionary.adjective
    pronoun = self.correctly_voweled_pronoun(adjective)
    noun = self.correctly_pluralized_noun(pronoun)
    self.make_title(pronoun, adjective, noun)
  end

  private

    def self.correctly_voweled_pronoun(adjective)
      pronoun = Dictionary.pronoun
      if vowel?(adjective) && vowel?(pronoun)
        pronoun = "An"
      end
      pronoun
    end

    def self.vowel?(word)
      word.start_with?("a", "e", "i", "o", "u")
    end

    def self.correctly_pluralized_noun(pronoun)
      if pronoun = "Some" || "A Few" || "No"
        Dictionary.noun.pluralize
      else
        Dictionary.noun.singularize
      end
    end

    def self.make_title(pronoun, adjective, noun)
      "#{pronoun} #{adjective} #{noun}".titleize
    end

end

We include our Dictionary module and then define our generate_title class method. This method relies on our Dictionary module to grab the necessary parts of speech, as well as some private methods that ensure we use correct grammar.

Let's run rspec one last time and you should see all your tests pass.

Now that we know our code is correctly generating three word titles, we're ready to deploy our gem to the world.

Deploying your Gem

First, push up your latest changes to your git repository for your gem. Bundler assumes you're working with a remote repo and won't allow you to push to RubyGems and release a new version if you have changes that haven't been committed.

Next, set up your account on RubyGems if you haven't already.

Once you've set up your account, from your command line type bundle exec rake release. This will accomplish a number of things.

  1. Creates a pkg directory in the top level of your file structure. This directory contains one file (so far): < your gem name>-< version no. >.gem

  2. Pushes your gem to RubyGems.org and creates a page for your gem using the metadata in your Gemspec.

  3. Tags your github repo with a version number

And that's it! Your very own gem is now available to the whole wide world. You can test it out yourself by gem installing < your gem name >, dropping into irb and requiring it and then taking it for a spin. Enjoy!

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus