Gem Development Best Practices

Mike Perham ·

Working on a Rubygem developed by another person is almost always a struggle, you have to waste an hour or two to get an environment set up so that you can run the gem’s test suite to ensure you have a known, good environment before you start to make changes. But making your gem easy for others to hack on is not hard if you follow a few simple best practices. I recently updated the mysql2 project to use these steps.

Use RVM
RVM is a no brainer for all Rubyists these days. If you aren’t familiar with it, please give it a test drive. With RVM, we can easily create a gemset which provides you a pristine development environment. I always create an .rvmrc file in the root directory like so:

rvm use 1.9.2@ --create

With RVM, we can also easily switch rubies if you want to ensure the gem works on Ruby 1.8, Ruby 1.9 and JRuby for instance.

Use an intelligent .gemspec
Rubygems requires a gemspec to describe how to build your project’s gem but this file can be a pain to maintain; give it some smarts to minimize your manual labor. Using mysql2 as an example:

require './lib/mysql2/version'

Gem::Specification.new do |s|
  s.name = %q{mysql2}
  s.version = Mysql2::VERSION
  s.authors = ["Brian Lopez"]
  s.date = Time.now.utc.strftime("%Y-%m-%d")
  s.email = %q{seniorlopez@gmail.com}
  s.extensions = ["ext/mysql2/extconf.rb"]
  s.extra_rdoc_files = [
    "README.rdoc"
  ]
  s.files = `git ls-files`.split("\n")
  s.homepage = %q{http://github.com/brianmario/mysql2}
  s.rdoc_options = ["--charset=UTF-8"]
  s.require_paths = ["lib", "ext"]
  s.rubygems_version = %q{1.3.7}
  s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql}
  s.test_files = `git ls-files spec examples`.split("\n")
  # tests
  s.add_development_dependency 'eventmachine'
  s.add_development_dependency 'rake-compiler', "~> 0.7.1"
  s.add_development_dependency 'rspec'
  # benchmarks
  s.add_development_dependency 'activerecord'
  s.add_development_dependency 'mysql'
  s.add_development_dependency 'do_mysql'
  s.add_development_dependency 'sequel'
  s.add_development_dependency 'faker'
end

Notice a few things:

  1. Line 01 and 05 – Use a /version file which contains just the version for your project. The gemspec can just refer directly to that constant.
  2. Line 07 – Use Time.now to dynamically set the date.
  3. Line 13 – Use git ls-files to dynamically create your gem’s filelist.
  4. Line 20 – The most crucial point: make sure you list all your development dependencies necessary to run the test suite.

Use bundler
Use bundler to install development dependencies for your gem. Typically gems have runtime and development dependencies where runtime dependencies are necessary for the proper operation of your gem and development dependencies are necessary for testing, benchmarking, and other developer tasks. gem install will install just runtime dependencies. But in our case, we want a way to install all the dependencies after we’ve cloned the codebase so we can run those developer tasks. All we need is a minimal Gemfile which references our gemspec and then bundle install will install both types of project dependencies. Here’s mysql2’s Gemfile:

source :rubygems

gemspec # DRY!  No need to repeat the gem deps here

Document Anything Else!
If your gem requires any further environmental configuration, document the necessary steps. A paragraph in your README about how to set up a proper development environment can help a lot, especially to a busy developer who just wants to create a quick fix for a bug.

Conclusion
With all this done, anyone with mysql and bundler installed can run the test suite (hopefully green!) on the mysql2 project with just a single line:

git clone git://github.com/brianmario/mysql2.git && cd mysql2 && bundle install && rake

Once the test suite runs green, the hacking can begin!