2

I used to think that you needed to use the automatically-generated binstub for Rails in order to load the correct version, otherwise you might accidentally load the wrong version.

However, I’ve found that the correct version of Rails is loaded regardless.

Consider the following scenario:

# System context
$ ruby --version
ruby 2.4.3p205 (2017-12-14 revision 61247) [x86_64-darwin17]
$ which ruby
/Users/sean/.rbenv/shims/ruby
$ rbenv versions
  system
* 2.4.3 (set by /Users/sean/.rbenv/version)

# Install three different versions of Rails
$ gem install rails
$ gem install rails -v 4.2.10
$ gem install rails -v 3.2.22.5
$ gem list | grep rails
rails (5.1.4, 4.2.10, 3.2.22.5)

# Globally, I’m using Rails 5.1.4
$ rails --version

# Creating a new app using Rails 4.2.1
$ rails _4.2.10_ new foo_app
$ cd foo_app
$ which rails
/Users/sean/.rbenv/shims/rails
$ rails --version
Rails 4.2.1

# Creating a new app using Rails 3.2.22.5
$ rails _3.2.22.5_ new bar_app
$ cd bar_app
$ which rails
/Users/sean/.rbenv/shims/rails
$ rails --version
Rails 3.2.22.5

How is the correct version being loaded, instead of the most recent version of Rails every time? If the toolchain is smart enough to load the correct version of Rails, why does Rails generate a binstub for itself? What am I misunderstanding about the toolchain?

(I’m using the word "toolchain" here because I'm not sure where this magic is happening: Ruby, Bundler, rbenv, or Rails.)

https://github.com/rbenv/rbenv/wiki/Understanding-binstubs

For context, this is the automatically generated binstub for Rails from version 4.2.10 — it’s pretty representative of Rails 4 and 5 binstubs look like:

#!/usr/bin/env ruby
begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
Sean Moubry
  • 1,010
  • 1
  • 13
  • 22

1 Answers1

0

You’re actually using the binstubs without knowing it.

Let’s explore why this is.

First, delete the Gemfile and Gemfile.lock from the Rails 4 app, attempt to run rails again, and you’ll see this error:

$ cd foo_app
$ rails --version
Rails 4.2.1
$ rm Gemfile Gemfile.lock
/Users/sean/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bundler-1.16.0/lib/bundler/shared_helpers.rb:34:in `default_gemfile': Could not locate Gemfile (Bundler::GemfileNotFound)
    from /Users/sean/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bundler-1.16.0/lib/bundler/shared_helpers.rb:39:in `default_lockfile'
    from /Users/sean/.rbenv/versions/2.4.3/lib/ruby/gems/2.4.0/gems/bundler-1.16.0/lib/bundler.rb:318:in `default_lockfile'
    from /Users/sean/Desktop/apps/foo_app/bin/spring:10:in `<top (required)>'
    from bin/rails:3:in `load'
    from bin/rails:3:in `<main>'

Surprisingly, it looks like the binstubs are being even though you aren’t explicitly running bin/rails.

Now, start over with a clean Rails 4 app, and this time, delete the bin folder instead of the Gemfiles, and you’ll notice that you’re now running Rails 5:

$ cd .. && rm -rf foo_app && rails _4.2.10_ new foo_app && cd foo_app
$ rails --version
Rails 4.2.1
$ rm -rf bin
$ rails --version
Rails 5.1.4

I’m not sure where this magic is happening: Ruby, RubyGems, Bundler, rbenv, or Rails — but you should not need to explicitly execute Rails via its binstub, because it’s happening either way.

Sean Moubry
  • 1,010
  • 1
  • 13
  • 22