What is inside a gem
The layout
A gem is a folder with a gemspec and the files it lists. The convention is older than Bundler and every tool assumes it: the code in lib/, the command in bin/, one file in lib/ named after the gem:
demo ├── demo.gemspec ├── bin │ └── demo └── lib ├── demo.rb └── demo └── version.rb
require "demo" finds lib/demo.rb because lib is the require path. A second file lives in lib/demo/ and is loaded with require_relative from the first, which keeps the gem working when it is installed anywhere.
The gemspec
The gemspec is Ruby: a block that fills a Gem::Specification. Six fields are required and a few more make the gem publishable:
require_relative "lib/demo/version"
Gem::Specification.new do |spec|
spec.name = "demo"
spec.version = Demo::VERSION
spec.summary = "A demo gem"
spec.authors = ["Ada"]
spec.license = "MIT"
spec.homepage = "https://example.com/demo"
spec.files = ["lib/demo.rb", "lib/demo/version.rb", "bin/demo"]
spec.bindir = "bin"
spec.executables = ["demo"]
spec.require_paths = ["lib"]
spec.required_ruby_version = ">= 3.2"
end
files is the package contents; a file that is not listed is not in the gem, which is the most common "works here, missing there" bug. executables and bindir are what put the command on PATH after install. license and homepage are not required, and gem build warns without them.
gem build
The build reads the gemspec and writes a .gem file named from the name and the version:
gem build demo.gemspec
Successfully built RubyGem Name: demo Version: 0.1.0 File: demo-0.1.0.gem
The version comes from the constant the gemspec required, so a release is a change to lib/demo/version.rb and a rebuild. A tag that does not match the version is the next thing a release pipeline checks.
Install and run
Installing the file is the same as installing a published gem: RubyGems unpacks it, links the executable and records it in the gem list.
gem install ./demo-0.1.0.gem
Successfully installed demo-0.1.0 1 gem installed
demo
demo 0.1.0
The command works from any folder because the binstub is on PATH; the code is loaded from the installed gem, not from the working directory. That is the difference between ruby bin/demo and demo, and the reason a packaging bug shows up only after install.
What to watch for
files written by hand drifts from the folder. Use
Dir.glob in the gemspec, or a tool such as
gem files in CI, so a new file is packaged
without an edit.
A gem that depends on another gem declares it in
add_dependency, not in the README. A missing
dependency installs cleanly and fails at the first
require, which is the hardest failure to trace
back to packaging.
Next: Bundler groups and platforms
Article author: Arthur Isaev