If you are coming from JavaScript (npm/yarn) or Python (pip/virtualenv), the Ruby way of doing things can feel slightly different.
New developers often run into errors like "Bundler::GemNotFound" or "Your Ruby version is 3.1.2, but your Gemfile specified 3.2.0."
To survive as a Rails developer, you need to understand the three distinct layers that keep your application from falling apart.
LEVEL 1: The Foundation (.ruby-version)
Before you install any libraries, you need the language itself.
The problem is that different projects need different versions of Ruby. Old projects might need Ruby 2.7, while your new Rails 8 app needs Ruby 3.3.
We solve this with a Version Manager (like rbenv, asdf, mise, or rvm) and a file called .ruby-version.
The File: .ruby-version
This file lives in the root of your project and contains a single line:
3.3.0
How it works:
When you cd into your project directory, your Version Manager reads this file and silently switches your active Ruby executable to version 3.3.0.
If you don't have it installed, it will yell at you to install it. This ensures everyone on the team is speaking the exact same language.
LEVEL 2: The Menu (Gemfile)
Now that we have Ruby, we need libraries (Gems).
The Gemfile is your Wishlist. It tells the world what your application needs to run, but not necessarily the exact version.
source "https://rubygems.org"
gem "rails", "~> 8.0.0"
gem "pg"
gem "puma", ">= 5.0"
The Confusing Part: The Squiggly Arrow (~>)
You will see ~> everywhere. This is the Pessimistic Operator. It means "I want the latest version, but don't break my app."
-
gem "rails", "~> 8.0"means: "Install version 8.0, 8.1, 8.9... but DO NOT install 9.0." -
gem "rails", "~> 8.0.1"means: "Install 8.0.2, 8.0.5... but DO NOT install 8.1."
It protects you from major breaking changes while allowing security patches.
LEVEL 3: The Receipt (Gemfile.lock)
This is the most misunderstood file.
The Gemfile is the menu. The Gemfile.lock is the Receipt.
When you run bundle install, Bundler looks at your Gemfile, goes to the internet, resolves all the weird dependencies (e.g., "Gem A needs Gem B version 2.0, but Gem C needs Gem B version 1.0"), and finds a configuration that works.
It writes the exact result to Gemfile.lock.
GEM
remote: https://rubygems.org/
specs:
actioncable (8.0.0)
actionpack (= 8.0.0)
activesupport (= 8.0.0)
CRITICAL RULE:
You must commit Gemfile.lock to Git.
This file guarantees that your server, your co-worker, and your laptop are running the exact same code down to the last decimal point.
LEVEL 4: The Manager (Bundler)
Bundler is the tool that orchestrates all of this. It is actually a Gem itself (gem install bundler).
1. bundle install
This command reads the Gemfile, resolves dependencies, creates (or updates) the Gemfile.lock, and installs the gems into your system.
2. bundle exec (The most important command)
If you just run rails server, your computer might try to use a version of Rails installed on your System, not the one in your Gemfile.
bundle exec means:
"Run this command using only the gem versions specified in my Gemfile.lock."
If you ever see weird errors about missing gems or wrong versions, put bundle exec in front of your command.
-
bundle exec rails server -
bundle exec rspec -
bundle exec sidekiq
(Note: Modern Rails "binstubs" like bin/rails do this for you automatically, which is why we usually type bin/rails instead of bundle exec rails in newer apps.)
Summary
-
rbenv/.ruby-version: Ensures we are using the right Ruby. -
Gemfile: A list of what libraries we want (with loose version constraints). -
Bundler: The tool that calculates valid versions for everyone. -
Gemfile.lock: The frozen snapshot of what we actually got. -
bundle exec: Running our code inside that snapshot.
Understanding this flow is the first step to mastering the Rails ecosystem.
Have you ever deleted your Gemfile.lock to fix a bug? (Don't do it!) Share your dependency horror stories below! 👇
Top comments (0)