How I Structure RubyGems

December 13, 2011 · 3 min read

I haven’t been consistent in how I structure my RubyGems, and I want to be. Consistency means I know what to provide, and people who use my code know what to expect.

These are guidelines for my future self.

The require statement follows the gem name

You should be able to figure out the require path just by looking at the gem name:

  • A gem called nyan_cat: require "nyan_cat"
  • A gem called nyan-cat: require "nyan/cat"
  • A gem called nyan_cat-moar_cats: require "nyan_cat/moar_cats"

File structure

A basic project layout should look like this:

your-rubygem/
            |- bin/
            |- lib/
            |- tests/
            |- Gemfile
            |- Rakefile
            |- README
            |- LICENCE
            \- your-rubygem.gemspec

The code that provides your gem’s functionality lives under lib/ in a directory named according to these rules:

  • A gem called nyan_cat: lib/
  • A gem called nyan-cat: lib/nyan/
  • A gem called nyan_cat-moar_cats: lib/nyan_cat/

The file required by the gem name rule above should sit directly under lib/:

  • nyan_cat -> lib/nyan_cat.rb
  • nyan-cat -> lib/nyan/cat.rb
  • nyan_cat-moar_cats -> lib/nyan_cat/moar_cats.rb

This file should require everything needed for the gem to work.

The Gemfile should contain just gemspec, and Gemfile.lock should not be checked in for gems. Yehuda Katz has a good write-up on the roles of the gemspec and Gemfile.

Code structure and namespace

Your gem should have a namespace that matches the directory structure:

  • nyan_cat -> NyanCat
  • nyan-cat -> Nyan::Cat
  • nyan_cat-moar_cats -> NyanCat::MoarCats

Everything should live under this namespace.

Versioning

Provide a version.rb file containing the current version and nothing else. Be kind to the people who depend on your gem — stick to the Semantic Versioning scheme.

  • nyan_cat -> lib/nyan_cat/version.rb
  • nyan-cat -> lib/nyan/cat/version.rb
  • nyan_cat-moar_cats -> lib/nyan_cat/moar_cats/version.rb

An example version.rb for nyan_cat-moar_cats:

module NyanCat
  module MoarCats
    VERSION = "0.0.1"
  end
end

Tests

Your code should be tested. You don’t need to distribute the tests in the gem file itself, though.

Logging

Unless you’re providing a logger implementation, it’s not your job to configure logging. Logging is good and incredibly useful for debugging, so the answer isn’t to avoid it. What I want is to give your code a logger that I’ve configured to my liking. It will support the standard Logger interface. Please make the logger an option — let me pass mine to you — and stop worrying about logging configuration.

You can do this easily by defaulting to NullLogger when no logger is provided:

require "null_logger"

class Foo
  attr_accessor :logger
  private :logger=, :logger

  def initialize bar, options = {}
    self.logger = options[:logger] || NullLogger.instance
  end

  def quux
    logger.info "Called #quux"
  end
end

Find out more about NullLogger at http://github.com/craigw/null_logger.

Dependencies

Read about your dependencies’ versioning schemes. If they use Semantic Versioning (and hopefully they do), depend on the appropriate version. Read about using the pessimistic version constraint operator to depend on major, minor, or exact versions as appropriate.

Rake tasks

Provide tasks to run your tests. The default rake task should run all tests.

README

Include at minimum:

  • A brief description of the gem, ideally with an example of the problem it solves
  • Installation instructions, even if they’re just gem install foo or “add this to your Gemfile”
  • A brief usage example, possibly with a link to more detailed documentation
  • Licensing info, even if it’s just “see the LICENCE file”
  • A “how to contribute” section explaining how to submit patches
  • A list of authors (it’s nice to see your name there)

Licensing

If you don’t provide a licence, I can’t use your project, because I don’t know the terms under which it’s available. I really want to use your project. Please provide a licence.

These posts are LLM-aided. Backbone, original writing, and structure by Craig. Research and editing by Craig + LLM. Proof-reading by Craig.