homeASCIIcasts

135: Making a Gem 

(view original Railscast)

Back in episode 33 [ watch, read ], we made a plugin for Rails. A more popular option for extending Rails’ functionality now is to make a gem, and that’s what we’re going to do in this episode.

Let’s say that we’d like to be able to generate a unique token for an ActiveRecord model. Given a model with a token column, e.g. the Recipe model below, we want to be able to call a class method called uniquify that will set up a before_create callback so that when we create a new Recipe it automatically generates a random token and ensures that it is unique in the database.

class Recipe < ActiveRecord::Base
  uniquify :token
end

Creating a Gem

Ruby gems can be made from scratch, but this involves a lot of work. An easier way is to use another gem to help create ours. There are several of these including hoe, newgem, bones, gemhub and echoe. Most of them provide generators that will create the necessary files so that all we have to do is fill in the details. For the purposes of this episode we’re going to create the files manually and then use the echoe gem to package our gem.

Before we start writing our gem we’ll need to install echoe. This is installed in the same way as any other gem.

sudo gem install echoe

That’s all we need to enable us to begin writing our gem. We’ll start by creating a directory for it which will contain another directory called lib.

mkdir -p uniquify/lib

Our gem will contain three files. We’ll use a README file (in rdoc format so that it renders correctly on GitHub) and we’ll also need a Rakefile and finally the Ruby file that will have the actual code in it. We can create these with touch.

cd uniquify
touch README.rdoc Rakefile lib/uniquify.rb

We’ll look at the Rakefile first. This is where we define our gem.

require 'rubygems'
require 'rake'
require 'echoe'
Echoe.new('uniquify', '0.1.0') do |p|
  p.description     = "Generate a unique token with ActiveRecord"
  p.url             = 
  p.author          = "Eifion Bedford"
  p.email           = "eifion@asciicasts.com"
  p.ignore_pattern  = ["tmp/*", "script/*"]
  p.development_dependencies = []
end
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }

In the Rakefile we’re using echoe to define the gem, passing in a name and a version number. In echoe’s block we define a number of parameters for the gem. The ignore_pattern defines the patterns for the files we don’t want included in the gem. We don’t have tmp and script directories in our gem, but we’ve included them as an example. We’ve set the development_dependencies to an empty array as by default echoe includes itself as a development dependency. Normally this isn’t a problem, but it can cause issues with older versions of RubyGems.

Finally we check in a tasks directory for any .rake files so that they can be loaded.

The Uniquify Code

We can now implement the gem’s functionality.

module Uniquify
  def self.included(base)
    base.extend ClassMethods
  end
  def ensure_unique(name)
    begin
      self[name] = yield
    end while self.class.exists?(name => self[name])
  end
  module ClassMethods
    def uniquify(*args, &block)
      options = { :length => 8, :chars => ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a }
      options.merge!(args.pop) if args.last.kind_of? Hash
      args.each do |name|
        before_create do |record|
          if block
            record.ensure_unique(name, &block)
          else
            record.ensure_unique(name) do
              Array.new(options[:length]) { options[:chars].to_a[rand(options[:chars].to_a.size)] }.join
            end
          end
        end
      end
    end
  end
end
class ActiveRecord::Base
  include Uniquify
end

It’s conventional to put a gem’s code into a module with the same name as the gem, so we’ve put ours into a module called Uniquify. When our gem is included into ActiveRecord it will add the uniquify method defined in the ClassMethods module. This method has a before_create callback which generates our unique token. Finally we include the Uniquify module in ActiveRecord.

Now that the code’s written we’ll need some documentation, too. This belongs in the README.rdoc file we created earlier.

= Uniquify
Rails gem for generating a unique token in an Active Record model.
(rest of file omitted).

That’s all we need to create a Ruby gem. The only gem-specific part of the project is in the Rakefile, in the few lines of code related to echoe.

Testing

Now that we’ve written our gem how do we go about testing and publishing it? The answer is to use various rake tasks. As we’re using echoe the first task we want to run is rake manifest.

$ rake manifest
(in /Users/eifion/rails/uniquify)
Cleaning
Building Manifest
+ Manifest
+ README.rdoc
+ Rakefile
+ lib/uniquify.rb

This creates a Manifest file that stores a list of the files that will be in our gem. Next we can run rake install to install the gem on our local machine. Once it’s installed it will appear in the list of gems on our machine like any other installed gem. We can then include it in a Rails application and test that it works as we expect.

$ gem list uniquify
*** LOCAL GEMS ***
uniquify (0.1.0) 

Publishing

Once we know that our gem is working correctly we can publish it, either to RubyForge or GitHub. There are a number of rake tasks that will help us with this. If we have a RubyForge account set up we can run

rake release

to build the gem and upload it to RubyForge. We can then follow this with

rake publish_docs

to build and upload the documentation.

The steps are slightly different if we want to publish to GitHub. First we need to create a new Github repository.

Creating a new repository on Github.

Next we need to turn our gem’s directory into a git repository, which we can do by running

git init

There are some directories and files we don’t want to upload so we’ll create a .gitignore file to define these. We want to ignore the pkg and doc directories and the Manifest file as these are generated by echoe, so our .gitignore file will look like this:

pkg
doc
Manifest

We can now add our files to the repository and make our first commit.

$ git add .
$ git commit -m "Initial import"
[master (root-commit) cbfe307] Initial import
 4 files changed, 57 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 README.rdoc
 create mode 100644 Rakefile
 create mode 100644 lib/uniquify.rb

That done we can add our remote repository and push the files to it.

$ git remote add origin git:eifion/uniquify.git
$ git push origin master

Our project is now on Github but there are a couple of things we need to do to turn our project into a gem. The first is to edit the project settings and click the RubyGem checkbox.

Marking our repository as a gem.

We’ll also need to create a gemspec file. As we’re using echoe this is easy, we just need to run a rake task.

$ rake build_gemspec
(in /Users/eifion/rails/uniquify)
Gemspec generated

Note that the build_gemspec task won’t appear in the list of available tasks when you run rake -T, but it is a valid task. If we look in our directory now we’ll see the gemspec file.

$ ls
Manifest        Rakefile        pkg
README.rdoc     lib             uniquify.gemspec

To finish we’ll need to upload the gemspec file to Github.

$ git add .
$ git commit -m "Adding gemspec file"
[master edfcc2f] Adding gemspec file
 1 files changed, 30 insertions(+), 0 deletions(-)
 create mode 100644 uniquify.gemspec
$ git push
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 818 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To git:eifion/uniquify.git
   cbfe307..edfcc2f  master -> master

Our project is now considered a gem on Github and we just have to wait a short while for it to build the gem.

That’s pretty much all we need to do to create a basic gem. As we develop the gem we’ll want to expand on this and add files as necessary. A good practice is to add a CHANGELOG file to keep track of the gem’s changes as we release new versions.

Releasing a new version of our gem is easy. All we need to do is change the version in our Rakefile then run

rake manifest

followed by

rake build_gemspec

We can then upload our new version to Github.

A Final Tip

We’ll wrap this episode up with a tip. If we want our gem to work as a plugin too we can do that by adding an init.rb file. All we need to do in the file is require our gem, using the name of the file in the lib directory.

require 'uniquify'

This will load the file when it’s inserted into a Rails project as a plugin. Once the file has been pushed to your repository we can run

script/plugin install git://github.com/eifion/uniquify.git

to install Uniquify as a plugin instead of a gem.