Gem Development Best Practices

Posted on by in Web

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!


Feedback

  Comments: 6


  1. Great suggestions. Having a standard way of doing gem development really helps cut down on time and makes contributing a lot easier.

    The bundler gem command bundler help gem is also useful for creating a standard directory structure when starting a new gem.

    I also use bundler’s :path attribute in my Gemfile to test out gems locally without releasing them gem 'my_gem', :path => '~/Projects/my_gem'.


  2. Using bundler to generate your gem will create a smart gemspec file so you don’t have to write it manually.

  3. Michael Wynholds


    I have had trouble with the `git ls-files` lines in the gemspec within RubyMine. It craps out if it tries to run “bundle install”. That’s not an overly big deal tho, since I tend to run that from the command line, and I have *heard* from colleagues that I can wrangle RubyMine to get that to work… but I just haven’t done it yet.

    Good post, Mike.


  4. No, don’t set the date. RubyGems does this for you.


  5. FYI, your code has HTML entities in it, likely due to an issue with your CMS configuration.

Your feedback