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”:http://rubygems.org. The problem is, there are a lot of implicit rules that make it hard to do it right in the first place.

h2. Enabling Rubygems

“Since Ruby 1.9 Rubygems is bundled”:http://docs.rubygems.org/read/chapter/3#page70 with every Ruby distribution. If you are using “RVM”:http://rvm.beginrescueend.com/ you get Rubygems with the older versions too. “Setting the RUBYOPT environment variable”:https://gist.github.com/54177 will enforce it even for 1.8 versions:

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

h2. The Gem command

Since the “merge of Gemcutter and Rubygems”:http://ruby.dzone.com/articles/new-rubygemsorg-gemcutter-and 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”:http://rubygems.org/pages/api_docs communication, which are very well “documented on RubyGems.org”:http://rubygems.org/pages/gem_docs.
It is also worth taking some time to get an overview of the “old command reference”:http://docs.rubygems.org/read/chapter/10. Commands like _which_ or _cleanup_ come in handy if you know them!

I use “RVM”:http://rvm.beginrescueend.com/ and “Gemsets”:http://rvm.beginrescueend.com/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”:http://stackoverflow.com/questions/1381725/how-to-make-no-ri-no-rdoc-default-for-gem-install 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

h2. Building a Gem

“Jeweler”:https://github.com/technicalpickles/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…

h3. Naming and Versioning

There are “some simple rules”:http://blog.segment7.net/articles/2010/11/15/how-to-name-gems when it comes to naming:

# Use lowercase letters only! Users of case insensitive file systems will be thankfull.
# Use ASCII characters only! You know, those US guys just don’t know there is UTF-8.
# Use _ underscores for parts of the name and – dashes for extensions of existing Gems.
# 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”:http://laughingmeme.org/2005/12/23/there-are-only-two-hard-things-in-computer-science-cache-invalidation-and-naming-things/!

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”:http://en.wikipedia.org/wiki/Software_versioning 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”:https://github.com/phoet/gem_best_practice/blob/master/CHANGELOG.

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

h3. The Gemspec

One thing that is essential for creating a Gem is the “_.gemspec_ file”:http://docs.rubygems.org/read/chapter/20. Put one in the root of your project folder and name it alike. The .gemspec file “contains plain old Ruby”:http://rubygems.rubyforge.org/rubygems-update/Gem/Specification.html and simply specifies all important attributes for your Gem. For an example, have a look at the “gem_best_practice.gemspec”:https://github.com/phoet/gem_best_practice/blob/master/gbp.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 = '[email protected]'
  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

h3. 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”:http://cookingandcoding.com/2009/06/04/creating-a-ruby-gem/ so that nobody has to mess “with the _$LOAD_PATH_”:http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices 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

h3. 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”:http://gembundler.com/ 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”:http://gembundler.com/rubygems.html 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”:http://yehudakatz.com/2010/08/21/using-considered-harmful-or-whats-wrong-with/, 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":http://ku1ik.com/blog/2010/07/03/thoughts-on-ruby-gem-dependencies.html in your _.gemspec_ file. This is a big problem for libraries that implement some kind of adapter pattern (Have a look at "HTTPI":https://github.com/rubiii/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":http://yehudakatz.com/2010/04/17/ruby-require-order-problems/).

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

h3. 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":http://github.com 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":http://blog.firsthand.ca/2010/09/ruby-rdoc-example.html / "RDoc":http://rdoc.sourceforge.net/doc/index.html 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":http://rake.rubyforge.org/:

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":http://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":http://rvm.beginrescueend.com/ 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.

h2. 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":http://rubygems.org/profiles/1055 or individual "Gem stats":http://rubygems.org/gems/rails/stats.

A good way to help the Gem gain acceptance is to make it OpenSource via "GitHub":http://github.com or "RubyForge":http://rubyforge.org/.

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":http://dzone.com or "Forrst":http://forrst.com/ 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":http://ruby-toolbox.com.

h2. The Gem Best Practice Gem

I am currently putting all those Guidelines above into a "Gem Best Practice Gem Project on GitHub":https://github.com/phoet/gem_best_practice which is inspired by "bundler's _bundle gem_ command":http://gembundler.com/rubygems.html.

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.