Gems & Bundler
💎 Gems & Bundler
📦 What Is a Gem?
A gem is a packaged Ruby library — code plus metadata (name, version, dependencies, author) bundled into a single distributable file — published to the central registry at rubygems.org. This is Ruby's direct equivalent of PHP's Composer packages (hosted on Packagist) or Python's pip packages (hosted on PyPI). RSpec, which Chapter 6 used freely, is itself just a gem like any other.
📋 Gemfile — Declaring a Project's Dependencies
Installing gems globally works for quick experiments, but real projects need every dependency and its version declared somewhere, so anyone (including you, six months later) can reproduce the exact same setup. That's what a Gemfile does:
~> 3.12 is a pessimistic version constraint — allow any 3.12.x patch update, but never jump to 3.13. This is directly comparable to a composer.json's version constraints or a Python requirements.txt/pyproject.toml entry.
🔒 Bundler and Gemfile.lock
Bundler reads the Gemfile, resolves a complete, consistent set of exact versions for every gem and every gem those gems themselves depend on, installs them, and writes the precise result to Gemfile.lock. That lock file — comparable to Composer's composer.lock or Poetry's poetry.lock — is what guarantees everyone working on the project, and your production server, all use the exact same gem versions, not just "whatever satisfies the constraints today."
It's tempting to .gitignore a lock file since it's "generated," but doing so defeats the entire purpose of Bundler. Without Gemfile.lock committed, two different machines running bundle install against the same Gemfile could resolve to different gem versions if anything was published to RubyGems in between — the exact "works on my machine" problem lock files exist to prevent.
▶️ bundle exec
bundle exec runs a command using the exact gem versions locked in Gemfile.lock, rather than whatever version happens to be installed globally on the machine. Python's rough equivalent is activating a virtual environment before running a command; PHP typically doesn't need an analogous step, since Composer's vendor/autoload.php is usually required directly by the application code itself rather than needing a wrapper command.
📥 require vs require_relative
Use require for gems and standard-library code; use require_relative for your own project's files, since it resolves relative to wherever the requiring file actually lives rather than the current working directory a script happens to be run from.
🏗️ Building Your Own Gem
This generates a standard structure: a lib/ directory for the actual code, a greeter.gemspec file describing the gem's metadata, plus a README, license, and test setup. The .gemspec is the gem's equivalent of a composer.json or Python pyproject.toml:
From there, publishing to rubygems.org with gem push makes it installable by gem install greeter from anywhere — the exact same distribution path every gem used throughout this entire course, including RSpec itself, went through.
📜 Package Management, Side by Side
Composer vs pip/Poetry vs RubyGems/Bundler
| Concept | PHP (Composer) | Python (pip/Poetry) | Ruby (RubyGems/Bundler) |
|---|---|---|---|
| Central registry | Packagist | PyPI | RubyGems.org |
| Declare dependencies | composer.json | requirements.txt / pyproject.toml | Gemfile |
| Exact locked versions | composer.lock | poetry.lock (or pip freeze) | Gemfile.lock |
| Run with locked versions | Usually automatic via autoload | Activate a virtual environment | bundle exec |
💻 Coding Challenges
Challenge 1: Write a Gemfile
Write a Gemfile for a small command-line project that needs rspec for testing (pinned with a pessimistic constraint to the 3.x line), rake for task running (any version), and a made-up gem my_project_utils pointed at a local path instead of RubyGems using path: "../my_project_utils".
Goal: Get comfortable writing realistic Gemfile syntax, including version constraints and local path dependencies.
Challenge 2: require vs require_relative
Sketch out two small files: lib/greeting.rb defining a Greeting module with a hello method, and main.rb in the project root that needs to use it. Write the correct require_relative line in main.rb, and explain in a comment why require (not require_relative) would be the wrong choice here.
Goal: Solidify when to reach for each of the two require variants.
Challenge 3: Sketch a Tiny Gem
Write the .gemspec and the single lib/ file for a gem called temp_converter that exposes one class method, TempConverter.celsius_to_fahrenheit(c). Include a name, version, summary, and the files list in the gemspec.
Goal: Practice the minimum shape a real, publishable gem needs.
It's worth restating: a Gemfile alone only says what's allowed — a range of acceptable versions. Gemfile.lock says what's actually installed, down to the exact version number, for every direct and indirect dependency. Reproducibility — the guarantee that "it works on my machine" also means "it works on every machine" — comes entirely from that lock file being committed and respected, not from the Gemfile by itself.
🎯 What's Next
Next chapter — the final chapter of this course: Ruby Idioms & Style — Rubocop conventions, the &. safe navigation operator, and a last look back at common habits worth unlearning from PHP and Python.