25

My latest Rails project is more or less and experiment for me to break lots of things and learn in the process. I have the latest version of Ruby specified in my gemfile:

ruby '2.2.3'

And I also have a .ruby-version dotfile in the project, with the following contents:

2.2.3

Other than the obvious duplication, what is wrong with this? What is the purpose of both conventions? If I should only have one convention for listing my Ruby version, why should I have one (Gemfile) over the other (dotfile)?

Is it perfectly fine to have both conventions in a project?

I am going to be the only maintainer of this experimental project, and dont feel it will be an issue having to maintain this one subtle duplication. I dont intend to upgrade Ruby for this project, and if I do, I wont have an issue remembering to do it in both places. Other than this detail, I certainly avoid any such duplication in my applications' codebases.

Todd
  • 2,824
  • 2
  • 29
  • 39
  • 1
    I think you don't need to specify ruby version in your Gemfile at all. – Dalibor Filus Oct 04 '15 at 14:40
  • 4
    Afaik `.ruby-version` sets the Ruby version for RVM or rbenv, but Heroku for example takes the Ruby version from the Gemfile ([source](https://devcenter.heroku.com/articles/ruby-versions)). – spickermann Oct 04 '15 at 15:02
  • Thanks @spickermann, that sounds like it is a good idea to have both. – Todd Oct 04 '15 at 15:08

4 Answers4

32

They were each developed by different teams at different times, and are used by different software.

Listing a ruby version in the Gemfile is a feature in bundler.

As the Gemfile is mostly only used by bundler, it will mainly only effect things when you run the with bundler -- using bundle exec, or software (like Rails) that automatically triggers bundler for you. It's effect is simply to error out and refuse to run if you aren't using the version of ruby specified. It's a requirement -- run under this ruby, or I'll throw an error warning you you're running under the wrong ruby.

However, heroku also pays attention to the version specified in the Gemfile, and will run under that version. Heroku decided to use the feature in bundler too. But most other software, on your workstation, or even travis, does not use that convention. (Travis makes you edit your .travis.yml ENV to specify ruby version to use).

The feature in bundler was introduced in Bundler 1.2 in August 2012.

The .ruby-version file was first introduced by rvm, the first ruby version manager. If you are using rvm, and you switch into a project direcotry with a .ruby-version file, rvm will automatically switch your shell to using the ruby version specified.

I'm not sure when rvm introduced this feature, but I think before the Gemfile "ruby" feature.

Since rvm introduced it, other ruby version switching software like rbenv and chruby have adopted it too to do the same thing -- automatically switch to the ruby version specified when you cd into the directory. Although I think with rbenv and chruby both it may be an optional feature.

So they were different features introduced into and supported by different software packages at different times, doing somewhat different things.

I agree it would be annoying to maintain both, and keep them in sync.

They are both actually optional, you don't need to use either one. Except that you might need to use the Gemfile ruby spec for heroku, to tell it which ruby you want it to run.

I don't personally use either. But if you need to work in different ruby versions in different projects, and find it convenient to have your ruby version manager (rvm, rbenv, or chruby) automatically switch to the right project-specific ruby version, .ruby-version might be useful.

Except for heroku purposes, listing ruby in the Gemfile is mostly just to keep yourself from making a mistake, for instance on deployment. Or perhaps an in-house automated deployment or CI environment could use them somewhat like heroku does, or perhaps other cloud deployment stacks will or have adopted it. I think many have found it not too useful though -- this one too, I wouldn't use until you run into or see a problem that it's solving. One inconvenience some people have with listing ruby versions in Gemfile is that with new rubies always coming out, you've got to be updating all your Gemfiles all the time.

In general, the last couple years of ruby releases have all been very backwards compatible, limiting the need to be sure you're using an exact version of ruby, most recent code will run on the most recent ruby, even if it was originally written for an older one.

I don't believe either feature lets you specify a range of ruby versions, like 2.2.* or what have you.

With either/both features, use them only if you need them or find them useful, you don't have to use either, and it's fine (if annoying) to use both, if you need what both do.

Since a Gemfile is live ruby code, you could theoretically have your Gemfile read your .ruby-version file and automatically use that value as the Gemfile ruby value. If you wanted to use both, and "don't repeat yourself" with it. I don't know if that's a common thing to do, I just thought of it. But it should work fine.

jrochkind
  • 22,799
  • 12
  • 59
  • 74
  • Thank you for the thorough answer. – Todd Oct 05 '15 at 20:23
  • Also, I have a potentially related question here: http://stackoverflow.com/questions/34905560/is-it-a-bad-practice-to-have-both-a-rvmrc-and-a-ruby-version-in-a-ruby-project, should you care to provide such wisdom again :) – Todd Jan 20 '16 at 16:47
  • 1
    Maybe worth an edit to mention that RVM actually supports the Gemfile method, too. I'm not sure why anyone uses `.ruby-version` – Kingdon Feb 25 '19 at 14:58
  • Aha, I think RVM didn't yet support using the ruby version in the Gemfile when I wrote this, I wasn't aware it did (I am not currently an RVM user). – jrochkind Feb 27 '19 at 01:04
19

I think it's better to avoid listing the same information twice unless there's a good reason - i.e., keep it DRY.

You can store the ruby version in ".ruby-version", and then in the Gemfile do something like this:

ruby File.read('.ruby-version').strip

I originally posted the following, which collimarco then shortened into the above:

ruby File.open('.ruby-version', 'rb') { |f| f.read.chomp }

If you do this, then whenever you change .ruby-version the Gemfile will magically do the same.

  • 2
    This is so DRY it chafes. You'll need to manually change the version in `.ruby-version` anyway, replacing multiple instances is trivial with your favourite editor. – Dennis Mar 25 '16 at 17:08
  • 3
    @Dennis that's the whole point of being DRY. Someone might forget to do a search replace. This code tells you that I want to use the same version as in .ruby-version – PhilT Dec 01 '16 at 13:54
  • 4
    This is a bit more succinct and equivalent: ruby File.read('.ruby-version', mode: 'rb').chomp – Empact Jul 24 '17 at 20:11
  • Check out the section "The Fallacy of DRY" in [this article](https://medium.com/@copyconstruct/small-functions-considered-harmful-91035d316c29). I agree, this is so DRY, it chafes! – Todd Aug 30 '17 at 20:14
  • This is more DRY and reliable for whitespace: `ruby File.read('.ruby-version').strip` – collimarco Oct 01 '19 at 14:26
5

Use this in your Gemfile:

ruby File.read('.ruby-version').strip

Pros:

  • DRY
  • you don't forget to update the version in one of the files
  • it ensures that you always run the correct version.
collimarco
  • 34,231
  • 36
  • 108
  • 142
1

While ruby File.read(".ruby-version").strip work on most cases, it does prevent the user from running bundle on a project's subdirectory.

If you're in a React Native app then you would need to do cd ios; bundle exec pod install but this will raise the following error:

[!] There was an error parsing `Gemfile`: No such file or directory @ rb_sysopen - .ruby-version. Bundler cannot continue.

Since .ruby-version is not present in that subdirectory.

To fix you could do:

ruby File.read(File.join(__dir__, ".ruby-version")).strip

This allows you to run bundle on any subdirectory.

If you're on Rails, you can now do cd app; bundle which is not possible with the previously suggested.

See: https://www.davidangulo.xyz/posts/dry-by-reading-ruby-version-in-your-gemfile/

dcangulo
  • 1,888
  • 1
  • 16
  • 48