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.rbnyan-cat->lib/nyan/cat.rbnyan_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->NyanCatnyan-cat->Nyan::Catnyan_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.rbnyan-cat->lib/nyan/cat/version.rbnyan_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 fooor “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.