Polyglot
Languages Ruby modules
Ruby § modules

Gems and packaging

The Ruby ecosystem revolves around gems — packaged, versioned libraries — and the conventional tooling: RubyGems (the package registry and command-line tool), Bundler (project-level dependency management), RubyGems.org (the public registry), and version managers (rbenv, rvm, chruby, asdf) for managing multiple Ruby versions per project. The principal project files: Gemfile (declares dependencies), Gemfile.lock (records resolved versions), .gemspec (gem metadata for libraries being published), .ruby-version (specifies the Ruby version). The combination — gems as the unit of distribution, Bundler for dependency management, version managers for toolchain isolation, the conventional project layout — is the substance of Ruby’s package ecosystem.

require and require_relative

The principal loading mechanisms:

require "json"                                    # standard library or gem
require "active_support/all"                      # gem (with subpath)

require_relative "lib/helpers"                    # relative to current file
require_relative "../config"

The require:

  • Searches $LOAD_PATH.
  • Loads the file once (idempotent — subsequent require is a no-op).
  • Returns true on first load, false on subsequent.

The require_relative (Ruby 1.9+):

  • Resolves relative to the current file (not relative to the working directory).
  • The conventional form for project-internal loading.

For dynamic loading (re-evaluating each call), load:

load "config.rb"                                  # always re-loads

The conventional discipline uses require and require_relative; load is reserved for development and tests.

Gems

A gem is a packaged Ruby library; the conventional file layout:

my_gem/
├── my_gem.gemspec
├── Gemfile
├── Gemfile.lock
├── lib/
│   ├── my_gem.rb                                 # main entry point
│   └── my_gem/
│       ├── version.rb
│       ├── client.rb
│       └── helpers.rb
├── bin/
│   └── my_gem                                    # executable
├── test/ or spec/
└── README.md

The principal entry point is lib/<gem_name>.rb; the conventional contents:

# lib/my_gem.rb
require "my_gem/version"
require "my_gem/client"
require "my_gem/helpers"

module MyGem
  # public API
end

Gemfile

The Gemfile declares project dependencies:

source "https://rubygems.org"

ruby "3.4.0"                                      # required Ruby version

gem "rails", "~> 8.0"
gem "puma", ">= 6.0"
gem "redis", "~> 5.0"

group :development do
  gem "rubocop"
  gem "byebug"
end

group :test do
  gem "rspec", "~> 3.13"
  gem "factory_bot", "~> 6.5"
end

group :development, :test do
  gem "pry-byebug"
end

# Gem from git:
gem "my_lib", git: "https://github.com/user/my_lib", branch: "main"
gem "my_lib", git: "https://github.com/user/my_lib", tag: "v1.2.3"

# Local path (development):
gem "my_other", path: "../my_other"

# Specific version:
gem "exact", "1.2.3"
gem "min", ">= 1.0"
gem "twiddle", "~> 1.2"                          # 1.2.x but < 2.0
gem "twiddle", "~> 1.2.3"                        # 1.2.x but < 1.3

Version specifiers

SpecMeaning
"1.2.3"exact
"~> 1.2.3">= 1.2.3, < 1.3
"~> 1.2">= 1.2, < 2.0
">= 1.0"minimum
"< 2.0"maximum
">= 1.0", "< 2.0"both

The ~> (twiddle-wakka) is the conventional choice for bounded version compatibility.

Bundler

Bundler manages dependencies declared in Gemfile:

bundle install                                    # install deps; updates Gemfile.lock
bundle install --path vendor                      # install to local vendor directory
bundle update                                     # update all to latest matching constraints
bundle update rails                               # update specific gem

bundle exec rails server                          # run with the bundle's gems
bundle exec rspec
bundle exec ruby my_script.rb

bundle outdated                                   # show outdated gems
bundle list                                       # list installed
bundle show rails                                 # show install path
bundle clean                                      # remove unused

bundle init                                       # create empty Gemfile
bundle add some_gem                               # add to Gemfile and install

The bundle exec ensures the right gem versions are loaded; without it, the system Ruby’s gems are used.

Gemfile.lock

The lockfile records exactly which versions were installed:

GEM
  remote: https://rubygems.org/
  specs:
    rails (8.0.1)
      actioncable (= 8.0.1)
      actionmailbox (= 8.0.1)
      ...
    puma (6.5.0)
      nio4r (~> 2.0)
    nio4r (2.7.4)

PLATFORMS
  ruby
  x86_64-linux

DEPENDENCIES
  puma (>= 6.0)
  rails (~> 8.0)

BUNDLED WITH
   2.6.1

The Gemfile.lock is conventionally committed to version control — admits reproducible builds across machines.

.gemspec for libraries

For libraries being published as gems:

# my_gem.gemspec
require_relative "lib/my_gem/version"

Gem::Specification.new do |spec|
  spec.name        = "my_gem"
  spec.version     = MyGem::VERSION
  spec.authors     = ["Author Name"]
  spec.email       = ["author@example.com"]
  spec.summary     = "Short description"
  spec.description = "Longer description"
  spec.homepage    = "https://github.com/user/my_gem"
  spec.license     = "MIT"

  spec.required_ruby_version = ">= 3.0"

  spec.files = Dir.glob("lib/**/*") + ["README.md", "LICENSE"]
  spec.require_paths = ["lib"]
  spec.executables = ["my_gem"]                   # files in bin/

  spec.add_dependency "json", "~> 2.0"

  spec.add_development_dependency "rspec", "~> 3.0"
  spec.add_development_dependency "rubocop", "~> 1.0"
end

The .gemspec is the metadata for gem build and gem push.

RubyGems CLI

gem search rails                                  # search registry
gem install rails                                 # install globally
gem install rails -v 8.0.0                        # specific version
gem uninstall rails                               # remove
gem list                                          # list installed
gem outdated
gem cleanup
gem environment                                   # show paths and config

gem build my_gem.gemspec                          # build the .gem file
gem push my_gem-1.0.0.gem                         # publish to RubyGems.org

The conventional contemporary discipline uses Bundler for project gems; gem install is conventional for system-wide tools (e.g., bundler itself, rubocop, pry).

Version managers

Multiple Ruby versions admit isolation per project; the conventional managers:

  • rbenv — minimal, uses shims.
  • rvm — older, more features, more invasive.
  • chruby — minimal, simpler than rbenv.
  • asdf — language-agnostic version manager.
  • mise — modern asdf alternative.
# rbenv:
rbenv install 3.4.0
rbenv local 3.4.0                                 # writes .ruby-version

# Then in the project:
echo "3.4.0" > .ruby-version

The .ruby-version file admits automatic version selection; conventional in modern projects.

Standard library vs gems

The Ruby standard library admits substantial functionality without gems:

require "json"
require "yaml"
require "csv"
require "uri"
require "net/http"
require "fileutils"
require "set"
require "date"
require "time"
require "logger"
require "ostruct"
require "pathname"
require "open3"
require "tempfile"
require "tmpdir"

The standard library admits substantial breadth; the conventional Ruby program leans on it for routine functionality and reaches for gems for substantial third-party functionality.

Common gems

The conventional contemporary gems:

Web frameworks

  • Rails — full-featured web framework.
  • Sinatra — minimal microframework.
  • Hanami — modern alternative to Rails.
  • Roda — routing tree DSL.

Testing

  • RSpec — BDD-style testing.
  • Minitest — built-in standard library testing.
  • Capybara — browser-driver testing.
  • FactoryBot — test data factories.
  • VCR — record HTTP interactions.

Utility

  • Active Support — substantial Ruby extensions (part of Rails).
  • Pry — REPL replacement for irb.
  • Faker — fake data generation.
  • DotEnv — environment variable loading.

Database

  • Active Record — ORM (part of Rails).
  • Sequel — alternative ORM.
  • Sqlite3, pg, mysql2 — database adapters.
  • Redis — Redis client.

Background work

  • Sidekiq — background job processor.
  • Active Job — job queue abstraction (part of Rails).

HTTP

  • Faraday — HTTP client with middleware.
  • HTTParty — simple HTTP client.

Linting/formatting

  • RuboCop — linting and formatting.
  • Standard — alternative ruleset.

Documentation

  • YARD — documentation generator.
  • RDoc — older standard.

Common patterns

Project initialisation

mkdir my_app && cd my_app
bundle init
echo "3.4.0" > .ruby-version
echo "*.log" >> .gitignore
echo ".bundle/" >> .gitignore
echo "vendor/bundle/" >> .gitignore
git init

Then edit Gemfile:

source "https://rubygems.org"

ruby "3.4.0"

gem "puma", "~> 6.5"
gem "redis", "~> 5.0"

group :development, :test do
  gem "rspec", "~> 3.13"
  gem "rubocop", "~> 1.71"
end

Then:

bundle install

Module organisation

my_app/
├── Gemfile
├── Gemfile.lock
├── README.md
├── .ruby-version
├── .rubocop.yml
├── lib/
│   ├── my_app.rb                                 # entry point
│   └── my_app/
│       ├── version.rb
│       ├── client.rb
│       └── helpers/
│           ├── string.rb
│           └── time.rb
├── bin/
├── spec/                                          # or test/
└── config/
# lib/my_app.rb
require "my_app/version"
require "my_app/client"

module MyApp
  # ...
end

Loading from lib

For libraries with a lib/ directory, the lib_paths is conventionally added to $LOAD_PATH:

# in bin/my_app or test setup:
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "my_app"

In gemified projects, bundle exec and the .gemspec handle this automatically.

Constants in modules

module MyApp
  VERSION = "1.0.0"
  DEFAULT_TIMEOUT = 30

  module Config
    DEBUG = ENV["DEBUG"] == "1"
  end
end

# Access:
MyApp::VERSION
MyApp::Config::DEBUG

Conditional gem loading

require "rails"

# Optional gem:
begin
  require "redis"
  REDIS_AVAILABLE = true
rescue LoadError
  REDIS_AVAILABLE = false
end

The pattern admits optional dependencies in libraries.

Gem with executable

# bin/my_app
#!/usr/bin/env ruby
require "my_app"
MyApp::CLI.start(ARGV)
# In .gemspec:
spec.executables = ["my_app"]

After gem install, my_app is admitted on the command line.

Releasing a gem

# Build:
gem build my_gem.gemspec                          # produces my_gem-1.0.0.gem

# Test locally:
gem install ./my_gem-1.0.0.gem

# Publish:
gem push my_gem-1.0.0.gem                         # uploads to RubyGems.org

# Or via Rake (with bundler/gem_tasks):
rake build
rake release                                      # tag, build, push

Bundler conventions

# Install:
bundle install

# Run with the bundle:
bundle exec ruby my_script.rb
bundle exec rspec
bundle exec rails server

# Or use binstubs:
bundle binstubs rspec-core
./bin/rspec                                       # without bundle exec

The binstubs admit running commands without bundle exec prefix.

Version constants

module MyApp
  VERSION = "1.0.0"
end
# .gemspec:
require_relative "lib/my_app/version"
spec.version = MyApp::VERSION

The pattern admits a single source of truth.

Local gem development

# Gemfile (in consuming project):
gem "my_other", path: "../my_other"

# Or for git development:
gem "my_other", git: "/Users/me/code/my_other"

The conventional discipline removes path:/git: references before publishing or pushing the consuming project.

A note on the conventional discipline

The contemporary Ruby gems-and-Bundler advice:

  • Use Bundler for project dependency management.
  • Commit Gemfile.lock for applications; do not commit for libraries.
  • Use ~> (twiddle-wakka) for version constraints.
  • Use .ruby-version — admits version-manager auto-selection.
  • Use bundle exec (or binstubs) for running.
  • Use the lib/ layout for gem-style libraries.
  • Use module-namespaced constantsMyApp::VERSION, etc.
  • Use require_relative for project-internal files.
  • Use the standard library before reaching for gems.
  • Group dev/test gems in Gemfile.
  • Document the Gemfile with comments where conventions are non-obvious.

The combination — Gemfile and Gemfile.lock for declarative dependency management, the lib/ convention for gem libraries, the .gemspec metadata, the version managers for toolchain isolation, the substantial standard library plus the rich gem ecosystem — is the substance of Ruby’s package management. The discipline produces reproducible, version-pinned, well-organised projects with substantial automation.