Gem Patterns

I’ve created a few Ruby gems over the years, and there are a number of patterns I’ve found myself repeating that I wanted to share. I didn’t invent them, but have long forgotten where I first saw them. They are:

Let’s dig into each of them. In the examples, the gem is called hello.

Rails Migrations

Create a template in lib/generators/hello/templates/migration.rb.tt:

class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
  def change
    # your migration
  end
end

The .tt extension denotes Thor template. Thor is what Rails uses under the hood.

Add lib/generators/hello/install_generator.rb

require "rails/generators/active_record"

module Hello
  module Generators
    class InstallGenerator < Rails::Generators::Base
      include ActiveRecord::Generators::Migration
      source_root File.join(__dir__, "templates")

      def copy_migration
        migration_template "migration.rb", "db/migrate/install_hello.rb", migration_version: migration_version
      end

      def migration_version
        "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
      end
    end
  end
end

This lets you run:

rails generate hello:install

Change the generator path and class name to match your gem. They must match exactly what Rails expects to work.

Example

Rails Dependencies

If your gem depends on Rails, add railties and any other Rails libraries it needs.

spec.add_dependency "railties", ">= 5"
spec.add_dependency "activerecord", ">= 5"

I typically require a supported version of Rails.

In code, don’t require Rails gems directly, as this can cause them to load early and introduce issues.

require "active_record" # bad!!

Instead, do:

require "active_support"

ActiveSupport.on_load(:active_record) do
  include Hello::Model
end

Example

Testing Against Multiple Dependency Versions

If your gem has dependencies, you may want to test against multiple versions of a dependency. For instance, you may want to test against multiple versions of Active Record.

To do this, create a test/gemfiles directory (or spec/gemfiles if you use RSpec).

Create test/gemfiles/activerecord50.gemfile with:

source "https://rubygems.org"

gemspec path: "../../"

gem "activerecord", "~> 5.0.0"

Install with:

BUNDLE_GEMFILE=test/gemfiles/activerecord50.gemfile bundle install

And run with:

BUNDLE_GEMFILE=test/gemfiles/activerecord50.gemfile bundle exec rake

Example

On Travis CI, you can add to .travis.yml:

gemfile:
  - Gemfile
  - test/gemfiles/activerecord50.gemfile

You can also use a library like Appraisal to help generate and run these files.

Testing Against Rails

To test against Rails, use a library like Combustion. It’s designed to be used with RSpec, but I haven’t had any issues with Minitest. Combustion generates some files that aren’t needed, so I just delete them.

Combustion.initialize! :all

Example

Coding Your Gemspec

There are a variety of ways to code your gemspec. Here’s the one I like to use:

require_relative "lib/hello/version"

Gem::Specification.new do |spec|
  spec.name          = "hello"
  spec.version       = Hello::VERSION
  spec.summary       = "Hello world"
  spec.homepage      = "https://github.com/you/hello"
  spec.license       = "MIT"

  spec.author        = "Your Name"
  spec.email         = "you@example.com"

  spec.files         = Dir["*.{md,txt}", "{lib}/**/*"]
  spec.require_path  = "lib"

  spec.required_ruby_version = ">= 2.4"

  spec.add_dependency "activesupport", ">= 5"

  spec.add_development_dependency "bundler"
  spec.add_development_dependency "rake"
end

Change files if your gem has app, config, or vendor directories. I typically use the last supported version for the minimum Ruby version.

If your gem has an executable file, add:

spec.bindir        = "exe"
spec.executables   = ["hello"]

Don’t check in Gemfile.lock.

Some gems have moved development dependencies entirely out of the gemspec and into the Gemfile, which is another option.

Summary

You’ve now seen five patterns that can be useful for Ruby gems. Now go build something awesome!

Published August 2, 2019 · Tweet


You might also enjoy

Scaling the Monolith

New Rails App Checklist

Modern Encryption for Rails


All code examples are public domain.
Use them however you’d like (licensed under CC0).