Making a Ruby Gem Beginners Guide

Ruby Gems are one of the most innovative ways to distribute software ever created. They allow people to package their code in a neat and distributable shiny little suit case and show it off to the world using rubygems.org. Gems have been used to package all types of tools, from Rails plug-ins to OS X operating system patches in order to help the Java Developer’s Kit run. Gems have many purposes but all have a few things in common: They are written in ruby and are packaged in a certain way so they can be easily shared on RubyGems.org

Why Make a Gem?

The notion to create a gem will usually present itself to you. If you find yourself repeating the same process over and over again, all while thinking, “I bet there are more people dealing with this than just me”, you may have a prime situation for a gem.

With you now, I’ll be creating a gem that certainly has a real world practical application. Like lots of municipalities, the city of Chicago provides some data end-points that return JSON data on all sorts of things. We’re going to create a gem that will handle that communication to the JSON end-point for you and simply return more manageable PORO’s (Plain Old Ruby Object).


https://data.cityofchicago.org/resource/xzkq-xp2w.json

When you visit this url in your browser, you’ll predictably get a JSON response consisting of a few thousand public employees. If you have a JSON formatter installed in your browser, you’ll see something like this:

[
  {
    job_titles: "WATER RATE TAKER",
    department: "WATER MGMNT",
    name: "AARON, ELVIA J",
    employee_annual_salary: "90744.00"
  },
 ...
]

This is obviously totally useful information and I find it impressive that it is so easily available to me. But what if I’m writing a Rails app and I don’t want have to worry about working with all the JSON conversion right in my app? How many times have you searched for a gem that accomplishes a small task for you that people need a thousand times a day?

Getting Started

You’ll probably want to have a rubygems.org account so start by going there and signing up. You don’t need one to create gems locally on your own machine but it doesn’t hurt to get a non-working version of your gem up early so you can protect your gem’s name which is first come-first serve like url’s. While you’re there, make sure your desired gem name isn’t taken by searching for it. If it is, just go back to the drawing board and come up with a modification. There is nothing more frustrating than getting yourself into naming problems on purpose. Once you have a name you like that isn’t taken, use your terminal to navigate to the drive location you want your gem’s folder structure to reside in for development purposes.

We’ll be using the bundler gem to create our new gem structure but it isn’t the only game in town. I can’t tell you what they are, but I’ve heard they exist.

~ $ bundle gem sample_gem

### then you should see this code

Creating gem 'sample_gem'...
      create  sample_gem/Gemfile
      create  sample_gem/.gitignore
      create  sample_gem/lib/sample_gem.rb
      create  sample_gem/lib/sample_gem/version.rb
      create  sample_gem/sample_gem.gemspec
      create  sample_gem/Rakefile
      create  sample_gem/README.md
      create  sample_gem/bin/console
      create  sample_gem/bin/setup
      create  sample_gem/.travis.yml
      create  sample_gem/.rspec
      create  sample_gem/spec/spec_helper.rb
      create  sample_gem/spec/sample_gem_spec.rb
Initializing git repo in /Users/jamesgates/sample_gem  #no need to run git init

All that happened here is that bundler created some files for you and put them into a nice new folder called “sample_gem” and initialized a git repo for you automatically. Move to that directory.

~ $ cd sample_gem
~ sample_gem $ subl .    #only works if you have a sublime shortcut set up.

Open this file path with your favorite text editor you’re ready to hack away. There are 4 key files in this tree that every beginner should know about and look at.

  • sample_gem.gemspec
  • lib/sample_gem.rb
  • lib/sample_gem/version.rb
  • README.md

/sample_gem.gemspec

this is your gem’s configuration file. A lot of settings and behaviors are configured here.

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

Gem::Specification.new do |spec|
  spec.name          = "sample_gem"
  spec.version       = SampleGem::VERSION
  spec.authors       = ["gatorjuice"]
  spec.email         = ["name@email.com"]

  spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = "TODO: Put your gem's website or public repo URL here."

  # 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'] = "TODO: Set to 'http://mygemserver.com'"
  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 "rake", "~> 10.0"
end

Lines 12, 13, and 14 all the term “TODO” in them, do you see that? You’ll need to eliminate the TODO’s in order for your eventual “rake install” task to run. If that syntax surrounding it looks strange, that perfectly understandable, especially if you’re new to Ruby. It’s just a different way of writing a string in Ruby and it’s actually pretty cool. Without getting too far into it,

%q{Im treated like a single quote string, dont try to interpolate.}
%Q{Im treated like a double quote string, interpolate away.}

Replace those strings with the pertinent information and move on with your life

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

Gem::Specification.new do |spec|
  spec.name          = "sample_gem"
  spec.version       = SampleGem::VERSION
  spec.authors       = ["gatorjuice"]
  spec.email         = ["name@email.com"]

  spec.summary       = %q{Ruby Wrapper for Chicago Employees}
  spec.description   = %q{Creates ruby objects from the Chicago Employees API end point.}
  spec.homepage      = "https://github.com/gatorjuice/sample_gem"

  # 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'] = "TODO: Set to 'http://mygemserver.com'"
  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 "rake", "~> 10.0"
  spec.add_dependency "unirest", "~> 1.1.2"
end

Line 31 is essentially like adding a gem to a Rails app’s gemfile. We still need to “require” the gem in our working code but this establishes a dependency (notice not specified to development like the two above it are) upon that gem. Unirest is a gem that handles http requests. It’s really, really, really, really, really, really a good idea to include your gem’s version number. Just check which version you’re using locally and add it in. You can look that info up my running this command:

~ sample_gem $ gem list --local

/lib/sample_gem.rb

This is the heart of your gem. When a gem is “required” this is the file that is moved into the load path. Let’s take a closer look…

require "sample_gem/version"

module SampleGem
  # Your code goes here...
end

The first thing you might notice is that we are going to wrap our code with a module. This is purely a module being used as a namespace. It’s important to namespace your gem like this so that your classes contained within can be referenced explicitly as to not cause a naming conflict with a class in your application, or some other gem you are relying upon. Here comes the time that we should remember to “require” any gems we need before we get to deep and start banging our heads on the wall shoutsking, “WHY ISN’T MY UNIREST CALL WORKING?”

require "sample_gem/version"
require "unirest"

module SampleGem
  # Your code goes here...
end

Now we can start writing some code. We want our users to be able to run

employees = SampleGem::Employee.all

And have an array of ruby objects returned to them, each one a separate Chicago employee. Let’s start with that code:

require "sample_gem/version"
require "unirest"

module SampleGem
  class Employee
    def self.all
      ruby_data = []
      bulk_data = Unirest.get(
        "https://data.cityofchicago.org/resource/xzkq-xp2w.json"
      ).body
      bulk_data.each do |employee|
        ruby_data << employee
      end
      ruby_data
    end
  end
end

This code will work, but you may have noticed a problem. What this method is returning is an array but it’s not an array of ruby objects, it’s an array of JSON objects. If we could turn each JSON object into a ruby object it will give us the ability to work more with the values and fine tune our user experience. Let’s create a way to do that.

require "sample_gem/version"
require "unirest"

module SampleGem
  class Employee
    attr_reader :job_title, :department, :name, :salary, :first_name, :last_name
    
    def initialize(input_options)
      @job_title = input_options["job_titles"]
      @department = input_options["department"]
      @name = input_options["name"]
      @salary = input_options["employee_annual_salary"].to_i
      @first_name = @name.split(",  ")[1]
      @last_name = @name.split(",  ")[0]
    end

    def self.all
      Unirest.get("https://data.cityofchicago.org/resource/xzkq-xp2w.json")
             .body
             .map { |employee_object| Employee.new(employee_object) }
    end
  end
end

Now we’ll get an array of instantiated Employees to work with and we gave the new ruby object attr_reader access otherwise the info is kind of useless. (I also got out self.all method down to a one liner.) This also allows us to play with the specific data if we don’t like the format, for example we could convert the string based salary into an integer or eliminate the extra space between the last and first names. Hell, we could separate the last and first names and push out two new attributes – @first_name and @last_name or create a method that calculates a weekly salary – @weely_salary.

/lib/sample_gem/version.rb

This file is where you specify your version. It’s especially important for developers to update this number before pushing updates to their gem online. If you alter your gem and push up to RubyGems.org with a change that breaks functionality for users of your gem, you’ll lose street cred and be labeled a — I can’t even say it.

Making a Ruby Gem Beginners Guide

Leave a comment