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