Crafting Rubies: Best Practices While Cutting Gems

To some, cutting gems is considered an art for the minerally inclined. In the Ruby world, however, gem cutting is a matter of life. Creating RubyGems can be easy, but also a trap for a novice. By following some general community best practices, you can create gems that can live in harmony within the Ruby ecosystem, and be enjoyed by consumers and contributors alike for years to come.

Make use of gem authoring tools

With the exception of experienced cutters, or those who want or need to have complete control over every line of code, there are gem authoring tools that can automate the majority of the manual work required, and avoid amateur mistakes, when publishing gems for the first time.

The biggest of the gem authoring tools at the moment is Jeweler (RubyGems, GitHub), which aims to provide a set of wizard based gem automation tools. It has tasks to manage releasing, versioning, dependencies, executables and Rake tasks all out of the box; it can be a real time saver in the long run for frequent or beginner gem publishers.

RubyGems (and most other package managers) add the lib/ directory of your gem to the load path; in some cases this is prepended to the load path. If your lib/ directory contains any files named the same as any core Ruby files (eg. string.rb), your gems’ file will take precedence. As such, follow the convention to keep all your files under a directory named the same as your gem within your lib/ folder, eg. lib/gem_name/something.rb.

Most package managers, and testing frameworks, will automatically add your lib/ and test folders (spec/ and features/ being two examples) to the load path. If providing an executable, or a Rakefile, you can add the lib/ directory to the path there ($:.unshift(path_to_lib)). As a result, remove code using things like File.dirname(__FILE__) within any requires, as you can safely assume that you can always require relative to your lib/ directory.

Don’t depend on anything outside of your project’s lib/ folder (with the exception of other gems); some package managers only copy across your lib/ directory.

Don’t require rubygems inside your gem

When you include require 'rubygems' in your gem, you take away the user’s choice to use a package manager other than RubyGems. There are many powerful and full featured alternatives to RubyGems, including Rip (RubyGems, GitHub), and Dpkg (used mainly by sysadmins who need to manage gems using Debian packages).

Use a meaningful versioning pattern

Versioning gems can be a painful experience, but it’s a mountain that has been conquered by very intelligent people (several times at that). The Semantic Versioning system aims to make versioning more predictable, and therefore safer, and it can be applied to gems with minimal effort. The semver.org website includes a detailed specification, as well as best practices when versioning software.

Some important takeaway points from the specification are:

Versions should be in the format X.Y.Z (Major.Minor.Patch).

You can use special version numbers when pushing out test release versions, eg. 1.0.0beta1, 1.0.0alpha3, 1.0.0rc1.

You can make feature changes and bug fixes in Major versions.

You can make backwards compatible feature changes and bug fixes in Minor versions.

You can only make backwards compatible bug fixes in Patch versions.

The API should be considered public and stable from 1.0.0 and onwards.

Harness the power of Bundler for testing

A little known secret of Bundler is that it can automatically use the dependencies within your .gemspec file, whether manually provided or generated by a gem tool, to create a Gemfile.

source :rubygems
gemspec

With the simplest of Gemfiles above, you can distribute an easily maintained Gemfile.lock, and use bundle exec to wrap your test runs and prevent gem version collisions.

Have a decent way to track issues publicly

Creating a gem without any issues is a myth. However, remembering that most of the users of any gems you create will also be technically adept, it’s a matter of course that you will have people who are ready and willing with the required details for providing a fix.

Providing a decent way of tracking issues seems obvious, but you’d be surprised at how difficult it can be to find a place to submit bugs, feature requests, or other issues to some gem maintainers.

If you’re hosting your gem on GitHub, you get a fantastic issue tracker for free. Some people prefer hosting on things like Lighthouse; whatever you choose, just make sure it’s linked to within the README in an obvious way.

Make contributing easy

Tangential to the last point is the idea that contributing to a gem should be easy. Make sure that you have a clear license that explains what and how people are allowed to contribute. Also try to make it worth their while to contribute back to the original code by offering clear instructions and restrictions that you apply to any incoming source code.

Hosting your code on GitHub is a fantastic way to nurture this practice, as Git makes it dead simple to work in a fork-alter-merge pattern, and GitHub wraps this in Pull Request goodness.

Once public, it stays public

Once you’ve published a gem, even if you’ve made a catastrophic mistake, it should be your absolutely last resort to yank the gem. Even more important is that, if you do finding yourself having to yank a version, under no circumstances should you reuse the version number of the copy you yanked.

System administrators, and developers for that matter, rely heavily on gem numbers being immutable and ever available. Even if you have added a password or other piece of important security information that you need removed, you’re likely better just to change the password (you’ll need to do this anyway), and publish a follow-up release.

Naming and casing

Naming is probably the first thing you will find yourself doing, and perhaps one of the most important steps you need to get right.

Before getting your heart set on a particular name, be sure to check RubyGems.org and GitHub and make sure your name isn’t taken.

There are some commonly adhered to rules about casing, which you shouldn’t break unless you really have a good reason. The following rules apply to the name of the gem, as well as any file names within the gem:

No upper-case letters. This will inevitably cause problems for users on Windows or OS X, which both use case insensitive file systems by default.

Use underscores for spaces. An example, “Test Class” should become “test_class”.

Try to keep one module or class per file, and name the file after that module or class. An example, if a file contains a module called “EventGenerator”, the file should be called “event_generator”.

If you follow these pretty simple rules, all of a sudden it becomes a lot easier for people to make assumptions about how to gem install, require, and contribute to your code; a little effort here goes a long way.

Wrapping up

So you want to create your own gems? By following the above best practices, you’re likely to avoid treading on the toes of your forebearers. A lot of very smart, very intelligent people are responsible for making gem cutting as easy as it is today, and in the process they offer valuable tools and techniques that require some level of mastery. Learn these tools, follow the best practices above, and you’ll be on your way to helping create a safer, cleaner and more efficient Ruby code community.

I’m Nathan Kleyn, and I am a web developer hailing from the heart of London, UK.
I currently work for UK based web-startup Intent HQ, and spend my days spreading the Ruby and Haskell love. I also work with JavaScript, C, Bash, IO, HTML, and CSS.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

Scott Gonyea

A few other things, though: Always assume the user has their shit together. By this, I mean:

If you want to require the file located at “./lib/your_gem/sub_dir/file.rb”, you do:

require “your_gem/sub_dir/file”

and NOT:

require File.expand_path(File.join(__FILE__, “../../sub_dir/file”))

STOP DOING THIS! RIGHT NOW! Muscling around the file system is inappropriate and out of place. It muddies your code.

ALSO:

DO NOT EVER make Bundler a runtime dependency, unless you are writing a Bundler plugin.

DO NOT EVER reference the “Bundler” object directly in your library. Bundler is not Hoe — it is not a virus. Don’t make it one. You use Bundler for testing, and you use Bundler to make it easy to install development dependencies and get contributors up to speed early and easily.

Peter Mounce

Scott: “Do not ever reference Bundler directly in your library”

Does that apply to executables too? I thought it was practise to do
“`
#!/usr/bin/env ruby
require ‘bundler’
Bundler.require
….
“`

in executables within a gem where dependencies are being managed by bundler…?

http://practicingruby.com Gregory Brown

Excellent article Nathan. You’ve provided a really good outline of the important things to remember in gem development. Thanks for writing it up.

So should we be distributing our Gemfile.lock or not? Yehuda Katz says “Do not check your Gemfile.lock into version control, since it enforces precision that does not exist in the gem command, which is used to install gems in practice”* I don’t have a preference one way or the other, but want to make sure I’m being consistent with the rest of the community.

For applications it makes sense to check it in just to keep your environment in sync. Even if in theory the precision used by Gemfile.lock is more aggressive than it needs to be, in practice it will make dependency related bugs easier to spot.

For libraries, you definitely should not have Bundler as a runtime dependency. With that in mind, the Gemfile.lock will not affect those who install your gem via RubyGems, but will serve the same benefits that a Gemfile.lock does for your applications when it comes to setting up a development environment.

So I’d say: Always check it in, but make sure that it is ignored at runtime in libraries.

http://twittter.com/mehowte Michal Taszycki

Hi Nathan,

I like the article. Personally I would not recommend Jeveler, as “bundle gem” provides reasonable gem skeleton and rake tasks, but that’s a matter of taste.

http://www.hackerinspiration.com Patrick

Also, in Ruby 1.9, require “rubygems” is automatic, so you wouldn’t need to require it anywhere in your source code if you’re not worried about being backwards compatible.

But I guess not being backwards compatible is bad practice… or is it not?