Building a Ruby Gem

Let me start off with a helpful guide: http://guides.rubygems.org/make-your-own-gem/

Having said that, I personally had some problems developing a gem of my own.

I started out with a single .rb file that was supposed to replace a lengthy (over 400 lines) shell script. Once that ruby file was complete (in less than half the number of lines in the shell script I might add) I thought to myself, hey instead of having to use ./ or ruby to call the script, what if they could just run it from anywhere with a single command? Hence, the shelter gem was born.

So here’s the breakdown -

These things are required for a gem (say, with the name shelter).

.
├── shelter.gemspec
└── lib
          └── shelter.rb

You’ve gotta have the *.gemspec, which contains information about the gem, including description, author, what files to include, etc.

Here is a sample gemspec.

Gem::Specification.new do |s|
  s.name = ‘shelter’
  s.version = ’0.0.0′
  s.date = ’2010-04-28′
  s.summary = “Shelter Gem”
  s.description = “This sample says ‘hello world”
  s.authors = ["Taylor Price"]
  s.email = ‘tayworm@gmail.com’
  s.files = ["lib/shelter.rb"]
  s.homepage =
    ‘http://rubygems.org/gems/shelter’
  s.license = ‘MIT’
end

You must also put all your ruby files inside lib, and the primary file should have the same name as the gem (more for consistency than anything else).

In that sample .rb file, we have a few simple lines.

class shelter
  def self.hi
    puts “Hello world!”
  end
end

All this does is define a class and function (that says ‘Hello world!’).

And that’s it! you can now run

gem build shelter.gemspec

gem install shelter

and you will be good to go!

But wait, how do you run this gem? Currently you have you go into irb.

% irb

> require ‘shelter’

But that isn’t our end goal. Didn’t I say I wanted this to be a single command?

Well, let’s add an executable to our gem.

We need a folder called ‘bin’ and we will name our executable the same as our gem (again, just for consistency).

Our shelter directory now looks like this:

.
├── shelter.gemspec
└── lib
         └── shelter.rb

└── bin

         └── shelter

All this executable has to do for us is require the shelter code we already wrote in a way that is recognizable to our terminal outside of irb.

Here is the contents of a simple executable:

#!/usr/bin/env ruby

require ‘shelter’
puts shelter.hi

This tells the terminal we are using ruby for this script, and that we should require the shelter gem. The last statement actually calls the function we created earlier.

Now if we want this executable to be included in our gem, we must add it to the gemspec. Just add a line like this one inside the gemspec.

s.executables << ‘shelter’

Now we are all set!

For me, the next step was more of an organizational one than a requirement. I wanted to separate my single ruby file into multiple other files in order to create smaller chunks of code. In my case, each file was named after its primary functionality.

So my directory tree starts to look more like this:

.
├── shelter.gemspec
└── lib
         └── shelter.rb

         └── shelter

                  └── code.rb

                  └── othercode.rb

                  └── yetmorecode.rb

                  └── etc.rb

└── bin

         └── shelter

and my gemspec started to look more like this:

s.files = ["lib/shelter.rb",
"lib/shelter/code.rb",
"lib/shelter/othercode.rb",
"lib/shelter/yetmorecode.rb",
"lib/shelter/etc.rb"]

Remember, every file you want to include in your gem must be in the gemspec.

As I took pieces out of the main shelter.rb, I had to reference them from within shelter.rb

This is done like so:

require ‘shelter/code’

require ‘shelter/othercode’

require ‘shelter/yetmorecode’

require ‘shelter/etc.rb’

Doesn’t that seem like a lot of typing, especially as the gem grows? Let’s not do that. Instead, lets iterate through a loop!

@needs = ['code', 'othercode', 'yetmorecode', 'etc']
@needs.each do |needs|
  require “shelter/#{needs}”
end

In this case, I’m using the same number of lines, but I’m sure you can see the benefit once the gem gets more complicated.

And that’s it! You’ve got a passable gem.

Some important things to note -

You reference your classes and methods the same way you would if they were all in one file.

Every ruby file should start with #!/usr/bin/env ruby so that it will know to use ruby to read it.

If you require external gems, require them in your main .rb and also add them to the gemspec.

s.add_dependency “colorize”, “~> 0.6.0″

Please note that if you are too restrictive, your gem may not play nicely with others. It is better to be as loose as possible with your external requirements.

And that’s it for now! More to come later I’m sure.

 
0
Kudos
 
0
Kudos

Now read this

ext_conf and its uses with omnibus!

So I’ve mentioned in the past that I was struggling to integrate kitchen-cabinet with omnibus tools like chef (or chefdk). My old solution was to do this: def self.chef_check if File.exist?('/opt/chef') ENV['GEM_HOME'] =... Continue →