Designing and Publishing a Gem
Prerequisites: Gems and Bundler, Code organization and RuboCop
Packaging code as a gem lets you reuse it across projects — or share it with the whole community on rubygems.org.
Generate the skeleton
bundle gem slugify_plus --test=minitest --linter=rubocop --ci=github
slugify_plus/
├── slugify_plus.gemspec # name, version, summary, dependencies
├── Gemfile # development dependencies
├── lib/
│ ├── slugify_plus.rb # entry point: require "slugify_plus"
│ └── slugify_plus/
│ └── version.rb # VERSION = "0.1.0"
├── test/
├── README.md
└── CHANGELOG.md
Write the library
# lib/slugify_plus.rb
# frozen_string_literal: true
require_relative "slugify_plus/version"
module SlugifyPlus
class Error < StandardError; end
def self.call(text, separator: "-")
raise Error, "text must be a String" unless text.is_a?(String)
text.unicode_normalize(:nfkd)
.gsub(/[^\x00-\x7F]/, "")
.downcase
.gsub(/[^a-z0-9]+/, separator)
.delete_prefix(separator)
.delete_suffix(separator)
end
end
p SlugifyPlus.call("Héllo, Wörld! Ruby 3.4")
"hello-world-ruby-3-4"
Library design guidelines
- Namespace everything under one module named after the gem, and
define a base
Errorclass so users can rescue all your errors at once. - Keep the public API small. Everything public is a promise; mark
helpers
private(orprivate_constantfor internal classes). - Don't monkey patch core classes, and don't print to stdout — return values, raise errors, and accept a logger if logging is needed.
- Minimize runtime dependencies. Each one becomes a dependency of every app that uses your gem.
- Use semantic versioning:
MAJOR.MINOR.PATCH. Bump MAJOR for breaking changes, MINOR for new features, PATCH for fixes. Record every change inCHANGELOG.md.
Dependencies in the gemspec
spec.required_ruby_version = ">= 3.2"
spec.add_dependency "zeitwerk", "~> 2.6" # needed at runtime
Development-only gems (test framework, linter) go in the Gemfile.
For a library, the Gemfile.lock is not what users get — they
resolve versions from your gemspec constraints — so keep those
constraints reasonably loose.
Release
bundle exec rake test rubocop
gem build slugify_plus.gemspec
gem push slugify_plus-0.1.0.gem
Or bundle exec rake release, which also tags the version in git.
Enable multi-factor authentication on your rubygems.org account —
published gems are a popular target for supply-chain attacks.