In Ruby, how can one laptop keep rack 2.2 and rack 3.2 installed for two projects, and how does Kernel#gem choose one per process?
answer
- name-version directories side by side
- one activated version per gem per process
- require picks the highest installed
- call gem before require
- rake _13.3.1_
basics
~20 sRubyGems installs each version in its own name-version directory, so both racks coexist. A process activates one version per gem: Kernel#gem("rack", "~> 2.2") called before require pins the newest matching version; otherwise require activates the highest installed.
solid answer
~40 sInstalled versions sit side by side as `gems/rack-2.2.x` and `gems/rack-3.2.7` with a gemspec each, so installing one never replaces the other. **Activation** is per process: RubyGems adds the chosen version's `require_paths` to the load path and records it in `Gem.loaded_specs`, and only one version of a gem can be active. A bare `require "rack"` activates the **highest** installed version. To get 2.2, the older project calls `gem "rack", "~> 2.2"` **before** any require; `Kernel#gem` returns `true` when it activates, `false` if a matching version is already active, and raises `Gem::LoadError` or one of its subclasses if nothing matches or another version is active. Wrapper executables accept `_VERSION_`, so `rake _13.3.1_` runs that rake. For whole projects, a Gemfile and lockfile are the normal answer.
code
ruby · 11 lines# legacy_tool.rb, run with plain `ruby legacy_tool.rb` (no Bundler)
gem "rack", "~> 2.2" # must come before any require of rack
require "rack"
puts Gem.loaded_specs["rack"].version # a 2.2.x version, not 3.2.7
begin
gem "rack", "3.2.7"
rescue Gem::LoadError => e
puts e.message # can't activate rack-3.2.7, already activated rack-2.2.x
endgo deeper
Recall that several versions can be installed at once and that a Ruby process uses only one version of each gem.
Explain activation, why a bare require takes the highest version, and how Kernel#gem before require pins an older one, including its true/false return.
Diagnose already activated errors by finding what activated the gem first, and move scripts to a Gemfile and lockfile rather than stacking gem pins.
Weigh per-project isolation, via lockfiles, per-project gem homes or containers, against shared machine-wide gems for a team's laptops and build agents.
## Installing: versions never overwrite each other RubyGems stores every installed version in its own directory under the gem home: - `gems/rack-2.2.<patch>/` and `gems/rack-3.2.7/` hold the code; - `specifications/rack-2.2.<patch>.gemspec` and `specifications/rack-3.2.7.gemspec` describe them. So `gem install rack -v '~> 2.2'` followed by `gem install rack -v 3.2.7` leaves **both** on disk. Nothing is shared between versions, and `gem list rack` shows both. ## Activating: one version per gem per process Having two versions installed is fine; having two **active** in one Ruby process is not. **Activation** is what turns an installed gem into loadable code: 1. RubyGems picks a specification. 2. It checks that the gem's own runtime dependencies are compatible with what is already active. 3. It activates those dependencies, adds the gem's `require_paths` (usually `lib`) to the load path, and records the gem in `Gem.loaded_specs`. After that, asking for a different version of the same gem fails: `Specification#activate` raises a `Gem::LoadError` such as `can't activate rack-3.2.7, already activated rack-2.2.x`. ## Which version a plain require gets When code calls `require "rack"` and no rack is active yet, RubyGems finds the installed gems containing `rack.rb` and activates the **highest version**, here 3.2.7. The directory a version lives in only matters as a tie-breaker between identical versions. This is why the older project breaks on a machine where the newer rack is also installed. ## Kernel#gem: pinning before require `Kernel#gem(name, *requirements)` activates a specific version explicitly: - `gem "rack", "~> 2.2"` activates the newest installed version at or above 2.2 and below 3.0. - It returns `true` when it activates the gem and `false` when a matching version is already active. - It raises `Gem::MissingSpecError` (no rack installed) or `Gem::MissingSpecVersionError` (rack installed, but no matching version), both subclasses of `Gem::LoadError`, which itself inherits from Ruby's `LoadError`. - It must run **before** the first `require` of that gem, directly or through another gem. Once 3.2.7 is active, the 2.2 request fails with `can't activate`. - It is a private `Kernel` method, called without a receiver like `require`. ## Other ways to pick a version | Need | Mechanism | |---|---| | A script needs rack 2.2 | `gem "rack", "~> 2.2"` before `require "rack"` | | Run an older executable once | wrapper `_VERSION_` argument: `rake _13.3.1_ --version` | | Run a gem's executable at a version, installing if needed | `gem exec -v 13.3.1 rake` | | Keep projects fully separate | a per-project `GEM_HOME`, or a Gemfile and lockfile | The wrapper scripts RubyGems writes into the bin directory check whether the first argument looks like `_1.2.3_` and, if so, activate that exact version before loading the executable. ## Inspecting what is active A few calls make the per-process state visible while debugging: - `Gem.loaded_specs` is a Hash from gem name to the active specification; `Gem.loaded_specs["rack"].version` shows which rack this process uses. - `Gem.loaded_specs["rack"].full_gem_path` shows the directory the code is loaded from. - `gem list rack` in the shell shows every installed version the process could have chosen. Comparing the two answers the usual question "why is this script running the wrong rack?": it is almost always a bare `require` taking the highest version, or another gem's activation pulling in a version before the script's own pin ran. ## Where RubyGems stops `Kernel#gem` pins one gem. It does not resolve a project's whole dependency graph, so a two-project laptop with dozens of shared gems quickly outgrows hand-placed `gem` calls. The standard answer is a Gemfile and `Gemfile.lock` per project, with every command run through Bundler, which activates exactly the locked versions. RubyGems' own role is to hold every version side by side and enforce the one-version-per-process rule that Bundler builds on.
- What does Kernel#gem return when the requested rack version is already active?`false`. It first looks up `Gem.loaded_specs["rack"]`; if that spec satisfies the requirement, it returns `false` without doing anything. It returns `true` only when it activates a gem, and it raises `Gem::LoadError` or a subclass when nothing installed matches or a different version is already active.
- Why must the gem call come before require, not after?Because `require "rack"` with nothing active activates the highest installed rack, 3.2.7 here. After that, `gem "rack", "~> 2.2"` asks for a version that conflicts with the active one and raises `can't activate`. The same happens if another gem's activation pulled rack 3 in first, so the pin has to be the first thing that touches rack.
Installed versions are books on a library shelf, several editions each; activation is checking one edition out to your desk. Your desk holds one edition of each title at a time, and Kernel#gem is asking for a specific edition before anyone hands you the newest.
saying these in an interview costs you the question
- Believes installing rack 3.2 replaces the installed rack 2.2
- Thinks one Ruby process can load two versions of rack at once
- Expects a bare require to pick the oldest or first-installed version
- Calls gem after require and expects it to switch versions
- Thinks Kernel#gem downloads the requested version when missing