Gem Best Practice

One thing that I like most about Ruby is the ease of contributing to OpenSource. You want to share code? Just create a gem and upload it to Rubygems. The problem is, there are a lot of implicit rules that make it hard to do it right in the first place.

Enabling Rubygems

Since Ruby 1.9 Rubygems is bundled with every Ruby distribution. If you are using RVM you get Rubygems with the older versions too. Setting the RUBYOPT environment variable will enforce it even for 1.8 versions:

# no need for 'require "rubygems"' in your code
export RUBYOPT=rubygems 

The Gem command

Since the merge of Gemcutter and Rubygems there have been a lot of improvements to the gem shell command. Sharing your code is now as easy as:

# create a gem from your local source configured in your gemspec
gem build your.gemspec
# upload the created gem 
gem push your-0.0.1.gem

There are some more new commands that handle the Rubygems API communication, which are very well documented on RubyGems.org.
It is also worth taking some time to get an overview of the old command reference. Commands like which or cleanup come in handy if you know them!

I use RVM and Gemsets extensively and I do not want to have all that duplicated RDoc and RI on disc (I’d rather search the web instead of using local generated doc), so I changed my .gemrc file to skip generating the doc on gem install:

# .gemrc
--- 
:update_sources: true
:sources: 
- http://gems.rubyforge.org/
- http://gems.github.com
gem: --no-ri --no-rdoc
:verbose: true
:bulk_threshold: 1000
:backtrace: false
:benchmark: false

Building a Gem

Jeweler is a great tool for scaffolding and releasing new Gems. If you don’t care about getting to know the basics, just move right over to their documentation. The problem is, that it makes things more complicated than they really are…

Naming and Versioning

There are some simple rules when it comes to naming:

  1. Use lowercase letters only! Users of case insensitive file systems will be thankfull.
  2. Use ASCII characters only! You know, those US guys just don’t know there is UTF-8.
  3. Use _ underscores for parts of the name and - dashes for extensions of existing Gems.
  4. Name your Gem like the file to require in your code. If not, put a big, fat warning in your documentation!

Choose the Gem name with care, because you know: naming is everything!

It became popular lately to start the development cycle with a version 0.0.1 instead of 1.0.0. I think this is just a matter of taste. More importantly you should handle your MAJOR.MINOR.FIX versions carefully. Increment your FIX version on bugfixes or additions to the API that won’t brake any existing code. Increment your MINOR version if you added features with an noticable impact to the API or it’s usage. Push a new MAJOR version if you changed your API completely or if you want to emphasize that the latest version is a major improvement or a new stable release. In an ideal world all those decisions and changes are documented in a CHANGELOG file.

In addition to these rules, Rubygems provides a mechanism to roll out beta versions of a Gem. In order to do this, just append a .beta etc. to the end of your version. These Gems can then be installed with the —pre flag:

gem install rails --pre # might install rails 3.1.0.beta some day

The Gemspec

One thing that is essential for creating a Gem is the .gemspec file. Put one in the root of your project folder and name it alike. The .gemspec file contains plain old Ruby and simply specifies all important attributes for your Gem. For an example, have a look at the gem_best_practice.gemspec:

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

require 'gem_best_practice'

spec = Gem::Specification.new do |s|
  s.name = s.rubyforge_project = 'gem_best_practice'
  s.version = GemBestPractice::VERSION

  s.author = 'Peter Schröder'
  s.description = 'A Gem that shows best practice for Gem creation.'
  s.email = 'phoetmail@googlemail.com'
  s.homepage = 'http://github.com/phoet/gem_best_practice'
  s.summary = 'If you ever wondered how to create a Gem and what best practice should be applied, this project is for you.'

  s.has_rdoc = true
  s.rdoc_options = ['-a', '--inline-source', '--charset=UTF-8']
  
  s.files = Dir.glob('lib/**/*.rb') + Dir.glob('bin/*') + %w(README.rdoc)
  s.test_files = Dir.glob('test/**/*.rb')
  
  s.executables = %w(gbp)
  s.default_executable = 'gbp'
  
  s.add_dependency("erubis", "~> 2.6.0")
end

After the .gemspec file is done, the Gem may be built via the gem command:

gem build your.gemspec

Or with a custom Rake task:

spec = eval(File.new("your.gemspec").readlines.join("\n"))
Rake::GemPackageTask.new(spec) do |pkg|
  pkg.need_zip = true
  pkg.need_tar = true
end

Folder Structure

Ruby is an open language with very little restrictions. You can place your code anywhere, but there are some guidelines of how code should be structured so that nobody has to mess with the $LOAD_PATH in order to use it. This is an example of standard Gem folders:

gem_best_practice/
|- bin
|- example
|- lib
|- test

Your actual Ruby code should go under lib and subfolders. It’s a best practice to put just one file there and the other stuff right into another subfolder in order do avoid name clashes or the like. Extensions to the standard Ruby libraries should be separated out, so that they are easier to spot:

|- lib
|   |- gem_best_practice
|   |   |- core_ext
|   |   |   |- kernel.rb
|   |   |- core_ext.rb
|   |   |- gbp.rb
|   |- gem_best_practice.rb

All the tests should go into the test folder ( spec / feature folder respectively). Make sure the Gem is well tested, otherwise chances are nobody will use the it nor contribute to the codebase. As an awesome Ruby developer, one would add some (working) examples in the example folder to demonstrate the API of your Gem in action. Additional executables for your Gem should be put into bin and don’t forget to add a proper shebang:

#!/usr/bin/env ruby

Dependencies

There are a lot of great libraries available for Ruby nowadays and one would be a fool not to use them. That’s why most of the Gems depend on other libraries. Different Gems may share common dependencies, so it’s elementary for a good Gem to be as loose about the own dependencies as possible to reduce versioning conflicts. Tools like Bundler do a great job on resolving those conflicts and they depend on the versions that are wired in the .gemspec file of a Gem. You can easily tell Bundler to use your .gemspec the main configuration source for your gems. A tradeoff between hard wiring Gem versions and providing no version restrictions at all, is to use the ~> range operator, that will allow newer dependency versions within the same MINOR version:

  s.add_dependency("erubis", "~> 2.6.0") # >= 2.6.0 and < 2.7.0

There is still one problem left. It’s currently not possible to define optional dependencies in your .gemspec file. This is a big problem for libraries that implement some kind of adapter pattern (Have a look at HTTPI which provides an interface to different HTTP libraries) and don’t want to force the user to install all underlying implementations (This is also a problem for bundler).

Until this problem is fixed in Rubygems, a simple approach to this problem is to programmatically rescue from LoadErrors:

# require optional dependency
begin
  require 'ap'
rescue LoadError
  puts "awesome_print would be awesome, please 'gem install awesome_print'"
end

Documentation and Examples

An awesome Gem deserves a decent documentation. If you are too lazy to document your code, you should at least bundle a README file with your source. GitHub does a great job at pushing a project to provide a README (rdoc/textile/markdown/…), because this file will be prominently rendered on the projects start page. The README should at least include installation instructions and basic usage examples.

Using YARD / RDoc in the source code is another great way to help others understand the code, provide bugfixes or extend existing functionality. Even for yourself, having a good inline documentiont will save time when returning to a piece of code you did not had your hands on for month.

Generating the documentation can easily be done with Rake:

Rake::RDocTask.new(:rdoc_dev) do |rd|
  rd.rdoc_files.include("lib/**/*.rb", "README.rdoc")
  rd.options + ['-a', '--inline-source', '--charset=UTF-8']
end

If the project is hosted on GitHub, rdoc.info may generate the documentation automatically from the source-repository. GitHub also provides a service-hook so that the documentation is updated on every push! A .document file in the project will give rdoc.info the right pointers which files to include in your docs:

lib/*.rb
README.rdoc

Depending on the project, it might be a good idea to include some usage examples with the code. This might be the case if one can not put all the necessary information in the inline comments or the README file. Bye the way, (especially behavior driven) tests serve very well as executable examples.

Another thing that is overlooked quite often is the documentation of Ruby runtime compatibility. Does the Gem work with MRI 1.8 / 1.9 / JRuby / REE / … ? Since RVM is a defacto standard in the Ruby environment, there is no good reason not to test at least Ruby 1.8.7 and 1.9.2.

And then?

After finishing the work on the first version of a Gem and pushing it to Rubygems, what comes next?

If you are just interested in the acceptence of the Gem you can have a look at the Rubygems user profile or individual Gem stats.

A good way to help the Gem gain acceptance is to make it OpenSource via GitHub or RubyForge.

It’s not easy to reach a wide range of users, but if you want to promote your Gem over the interwebs there are a lot of services like DZone or Forrst that allow you to post something about the Gem or to link to articles on your blog or Twitter posts. Another great resource for Ruby developers to discover new Gems is The Ruby Toolbox.

The Gem Best Practice Gem

I am currently putting all those Guidelines above into a Gem Best Practice Gem Project on GitHub which is inspired by bundler’s bundle gem command.

More best practice, less fail!

4 thoughts on “Gem Best Practice

  1. postmodern

    There are a few mistakes in your example .gemspec.

    ‘executables’ should only contain the names of the files in the ‘bin/’ directory. ‘default_executable’ also only contains the name of the default executable file. Also, ‘files’ is suppose to contain all of the files within your project.

    Even though people claim that gemspecs are simple, it’s still very easy to make mistakes.

  2. phoet Post author

    Hi postmodern,

    thank you for the hints, I updated the example .gemspec file, since it was not the latest version within my repo (there were some copy-paste errors).

    one thing though:

    the official documentation has a “different example than your proposed solution to executables”:http://docs.rubygems.org/read/chapter/20#default_executable

      spec.executables = ['bin/foo', 'bin/bar']
      spec.default_executable = 'bin/bar'
    

Comments are closed.