We tend to have both .ruby-version
file and a ruby
directive in your Gemfile
, and when you might choose one over the other, or even use both. This gets to the heart of how Ruby version management works in a Rails project, and it involves understanding the roles of different tools.
tl;dr: Between Ruby on Rails 5.2 (2017) and 7.1 (2024), we had both .ruby-version
and ruby
directive in Gemfile
, but since 7.2 (2024) we should no more have ruby
directive in Gemfile
. We should manage the Ruby version only in .ruby-version
for your Rails applications even if you want to add contstraints.
1. The Role of .ruby-version
(and version managers)
-
Interpreter Selection: The primary purpose of
.ruby-version
is to tell Ruby version managers (likerbenv
,chruby
,asdf
) which Ruby installation to use when you're in a project directory. It's the mechanism that activates a specific Ruby.- When you
cd
into a directory containing a.ruby-version
file, and you're using a version manager, the version manager reads the file. - It then modifies your shell's
PATH
environment variable (and potentially other environment variables) to point to the binaries (likeruby
,gem
,bundle
,rails
) of the specified Ruby version. - This ensures that when you run
ruby -v
, you see the version specified in.ruby-version
, and that any commands you execute (likebundle install
) will use that specific Ruby installation.
- When you
System-Wide vs. Project-Specific: Version managers are designed to allow you to have multiple Ruby versions installed on your system simultaneously.
.ruby-version
provides a project-specific override. You might have a system-wide default Ruby, but each project can specify its own requirement.Outside of Bundler's Scope: Crucially,
.ruby-version
is primarily handled by external tools (the version managers). Bundler doesn't directly interpret or enforce.ruby-version
in the same way it enforces theruby
directive in theGemfile
. Bundler sees the result (the active Ruby version), but it doesn't set it.
2. The Role of the ruby
Directive in Gemfile
-
Dependency Constraint and Compatibility: The
ruby
directive inGemfile
serves a different purpose. It's a constraint that Bundler uses during dependency resolution. It tells Bundler:- "This project requires at least this version of Ruby."
- "When resolving gem dependencies, consider only gem versions that are compatible with this specified Ruby version."
-
Bundler's Responsibility: Bundler enforces this constraint. If you try to
bundle install
with an incompatible Ruby version (one that doesn't meet the requirement in theGemfile
), Bundler will raise an error and refuse to proceed.
# Gemfile ruby "3.4.2"
If you were to run
bundle install
with Ruby 3.4.1 active, Bundler would error out. -
Platform-Specific Rubies (Optional): The
ruby
directive can also be used to specify platform-specific Ruby implementations:
# Gemfile ruby "3.1.6", engine: "jruby", engine_version: "9.4.12.0"
This tells Bundler that the project requires JRuby 9.4.12.0, which is compatible with Ruby 3.1.6 (actually Ruby 3.1).
Doesn't Activate a Ruby: The
ruby
directive inGemfile
doesn't change your active Ruby version. It's a declaration of a requirement, not a command to switch interpreters. This is the key difference from.ruby-version
.
3. Why Both Might Be Used (and When)
Here's a breakdown of common scenarios:
-
Scenario 1:
.ruby-version
Only (Common in older projects or simpler setups)- How it works: You rely solely on the version manager and
.ruby-version
to set the correct Ruby. You don't explicitly state the Ruby version in theGemfile
. - Pros: Simpler setup, especially if you consistently use a version manager.
- Cons: Less explicit. Someone working on the project without a version manager might accidentally use the wrong Ruby and not realize it until runtime errors occur. Bundler won't warn them. Less portable to environments without the same version manager setup.
- How it works: You rely solely on the version manager and
-
Scenario 2:
ruby
inGemfile
Only (Less Common)- How it works: You rely on the
ruby
directive inGemfile
to enforce the Ruby version. You don't use a.ruby-version
file. You might manually switch Ruby versions (perhaps with your OS's package manager) or rely on a system-wide default Ruby that happens to match theGemfile
requirement. - Pros: Bundler will always enforce the Ruby version.
- Cons: Very inconvenient. You have to manually ensure the correct Ruby is active before running any commands. Prone to errors if you forget. Not suitable for projects requiring different Ruby versions.
- How it works: You rely on the
-
Scenario 3: Both
.ruby-version
andruby
inGemfile
(Best Practice, and now the Rails default)- How it works: This was the recommended approach, and it's how Rails 5.2 through 7.1 generates new projects. You used to use both files.
- Pros:
- Best of both worlds:
.ruby-version
handles the automatic switching of Ruby versions, making development convenient. Theruby
directive inGemfile
acts as a safety net and ensures Bundler only resolves compatible dependencies. - Explicit and Enforced: The required Ruby version is clearly stated in two places, reducing ambiguity.
- Portable: The
Gemfile
constraint works regardless of whether a version manager is used. - Consistency: This combination helps ensure consistency between development, testing, and production environments.
- Best of both worlds:
- Cons: Slightly more complex setup (but the complexity is justified by the benefits). You need to keep the versions in both files in sync. (Rails 7.2+'s
--skip-ruby-version
option forrails new
can help in specific cases like using Devcontainers, where.ruby-version
might be redundant).
-
Scenario 4: .ruby-version file is present, and the ruby directive within the Gemfile is not used. However, a .gemspec file exists, specifying the required_ruby_version.
-
How it Works:
-
.ruby-version
: Manages the active Ruby version as described before. -
.gemspec
: If your project is also intended to be packaged as a gem (even if it's primarily an application), the.gemspec
file can include arequired_ruby_version
setting. This is similar to theruby
directive in theGemfile
, but it applies when your project is installed as a gem into another project. - Bundler respects
required_ruby_version
from a.gemspec
: When you runbundle install
, Bundler will check therequired_ruby_version
in your.gemspec
(if it exists) and ensure the currently active Ruby version satisfies that requirement. It acts like aruby
directive in theGemfile
.
-
- Pros:
- Good practice if your project could potentially be used as a gem by others. Ensures compatibility when installed as a dependency.
- Avoids redundancy: If the version is already accurately specified in the
.gemspec
, there's less need to duplicate it in theGemfile
.
- Cons:
- Slightly less explicit within the Gemfile itself. A developer looking only at the
Gemfile
might not immediately see the Ruby version constraint. -
.gemspec
is primarily for gem packaging; if your project is only an application and never intended to be a gem, it might feel a bit out of place to use.gemspec
for this. - This scenario is acceptable, though generally scenario 3 is preferred.
- Slightly less explicit within the Gemfile itself. A developer looking only at the
-
How it Works:
Set Ruby version in Gemfile and .ruby-version by default rails/rails#30016
rails/rails#30016 introduced ruby "3.4.2"
in Gemfile
file in 2017 targeting for Ruby on Rails 5.2.0. This is applied only when new Rails applications are created through rails new
command.
https://github.com/rails/rails/pull/30016
Permanently remove ruby from Gemfile rails/rails#50914
7 years after its inception in 2017, rails/rails#50914 permanently removed the ruby "3.4.2"
directive from Gemfile
file in 2024 targeting for Ruby on Rails 7.2.0. This change looks like a so minor problem
The original commit message was misleading but @rafaelfranca explained correctly as follows:
https://github.com/rails/rails/pull/50914#issuecomment-2529739641
It is not temporary anymore. We decided to not generate ruby version in the gemfile
Personally with this change in Feb 2024 I am so glad to get rid of ruby
DSL from Gemfile
, which bothers me and our developers. 🎉🎉🎉
In summary, we had to use both .ruby-version
and the ruby
directive in Gemfile
for the best combination of convenience, explicitness, and safety according to rails new
by default. This had ensured that your project uses the correct Ruby interpreter and that Bundler enforces compatibility during dependency resolution. If you are using a devcontainer, you should manage the Ruby version in .ruby-version
and .devcontainer/Dockerfile
files by your own self, by once removing ruby
directive in Gemfile
and the Dockerfile to manage the Ruby version. Since then, you are free from these constraints with the balanced configurations. Thanks Rails team for these long-term improvements ❤️❤️❤️
Top comments (0)