45

TL;DR

  • Don't bother with gemsets; multiple versions of a gem may be installed concurrently.
  • When necessary, specify which version to execute using $ gem-based-binary _version_ args notation.
  • Use bundle exec when you have a Gemfile specifying the version.
gem install rails -v 3.2.13
rails _3.2.13_ new Project2
cd Project2
bundle exec rails server

UPDATE: 2015-06-04

I wrote this question three years ago. Partly, it was based on a false assumption, and partly the situation has changed since then. With appreciation to @indirect for his original answer, I want to call attention to @kelvin's newer (less upvoted) answer, summarized above.

My false assumption: Only a single version of a gem could be installed at a time, hence the need for gemsets to isolate the namespace. Not true. Multiple versions of a gem may be installed concurrently. The most recent one will be used when invoked from a command line, unless you have a Gemfile specifying the version constraints and invoke the command via bundle exec, or specify the version as its first argument.

See also How can I call an older version of a gem from the commandline? re: the underscore-version notation.


Original question:

I have multiple projects going on using different versions of Rails. I have a workflow (described below) for creating projects using specific versions of rails, and keeping the projects isolated from each other. I'd like to experiment with other workflows, in particular, using rbenv instead of RVM, but it's not clear how to do so.

QUESTION: What is the best current practice for creating multiple rails projects, each using a different version of rails, when making use of rbenv and bundler, as opposed to rbenv-gemset or rvm?

USE CASE: I have two rails projects, called ProjectA and ProjectB. ProjectA is developed using one version of rails ("RailsA"), whereas ProjectB uses a different version ("RailsB"). How do I manage having both versions installed?

THE GEMSETS APPROACH: When I first started with Rails development, I used RVM. In addition to supporting multiple, concurrent installations of ruby, RVM supports having multiple Named Gem Sets. Each project has its own independent collection of gems (including rails itself) called a gemset:

rvm gemset create RailsA
rvm gemset use RailsA
# RailsA.  Note: My question is not version-specific.
gem install rails --version 3.0
rails new ProjectA
cd ProjectA
rvm --rvmrc use `rvm current`
vi Gemfile
bundle install
cd ..
## Now do the same for ProjectB
rvm gemset create RailsB
rvm gemset use RailsB
gem install rails --version 3.2
rails new ProjectB
cd ProjectB
rvm --rvmrc use `rvm current`
vi Gemfile
bundle install

Note: The very creation of the project folders should be done (IMHO) by a rails new command using the desired version of rails, since the skeleton files change from version to version. (Perhaps I should revisit this premise?)

THE BUNDLER APPROACH: I've been playing with using rbenv instead of RVM, but I don't understand the workflow as clearly. In the README.md, Sam Stephenson writes that "rbenv does not ... manage gemsets. Bundler is a better way to manage application dependencies." There is a plugin (rbenv-gemset) for getting the same results as rvm's gemsets, but Sam clearly favors using Bundler instead. Unfortunately, he doesn't elaborate on what the workflow would look like. Even the Bundler website doesn't explicitly connect all the dots of how to isolate one project from another. Several blogs and gists come to the rescue, suggesting the following ~/.bundle/config file:

---
BUNDLE_PATH: vendor/bundle

(BTW, I'm not sure what the "---" is about. The docs make no mention of it and it doesn't seem to make a difference.)

This effectively gives each rails project its own gemset, storing the gems in ProjectX/vendor/bundle/. In fact, rails itself will be (re-)installed there, making the project completely independent of the rest of my environment, once I run bundle install.

But the elephant in the room is the chicken-and-egg problem of creating the rails project folder in the first place!! In order to create the ProjectA folder using RailsA, I need to install rails (and its numerous dependencies) first. But when I want to create ProjectB, I must then switch to using RailsB. Without gemsets, I must do some serious upgrading/downgrading. Not cool.

A possible solution is simply not to worry about what version of rails I use to create the ProjectX folder. If I then use rails 3.0 to create a 3.2 project, I could just manually create the app/assets tree. But that just irks me. Ain't there a better way?

Community
  • 1
  • 1
Noach Magedman
  • 2,313
  • 1
  • 23
  • 18
  • 2
    Sounds like you REALLY want to use RVM and gemsets. Any particular reason you are choosing NOT to use the tool that solves your problem most elegantly? – Brett Bender Mar 19 '12 at 15:51
  • 4
    I'm not committed to any particular tool (and I absolutely don't want to get into a "RVM vs rbenv" discussion). I'm more familiar with RVM and I understand a workflow for using it. I'd like to understand the equivalent workflow for rbenv, if nothing else just to have fluency with a variety of tools. – Noach Magedman Mar 19 '12 at 21:11
  • 1
    You say Bundler's website doesn't explain how to "isolate one project from another". If "isolate" means each project having a separate copy of the exact same gem & version (e.g. rack-1.4.0), Bundler doesn't do that. It maintains a single copy of each gem on disk - and any app that needs that gem & version can use it without interfering with other apps. It's like running 2 instances of `bash`; you don't have multiple copies of `bash` on disk, but multiple copies in memory. Gemsets let you have multiple copies on disk (which may be easier to delete when no longer needed) – Kelvin Sep 24 '13 at 16:23
  • Related: [How do you use multiple rails versions with rbenv?](http://stackoverflow.com/q/8877772/456814). –  May 14 '14 at 23:45
  • "---" is a YAML artifact. Output generated with the YAML format will have this, but it is not required to parse the file. – Steven Soroka May 04 '15 at 17:26

3 Answers3

42

Most people solve this by installing the rails gem first via gem install rails. If you refuse to do that for some reason, you can opt out of the automatic bundling that Rails attempts to do for you. This will work completely regardless of your ruby management system.

mkdir myapp
cd myapp
echo "source :rubygems" > Gemfile
echo "gem 'rails', '3.2.2'" >> Gemfile
bundle install --path vendor/bundle
bundle exec rails new . --skip-bundle

When prompted, type "y" to replace your Gemfile with the default Rails one (or not, as you prefer). Then, once it's done:

bundle install

You're done, and you have boostrapped a new rails app with the version of your choice without installing the rails gem into rubygems.

indirect
  • 3,470
  • 2
  • 25
  • 13
  • 3
    Unrelated to this solution, but never edit your .bundle/config file. If you want to isolate the gems for a project, use `bundle install --path vendor/bundle`. – indirect Mar 20 '12 at 16:41
  • Thanks, indirect! Could you please elaborate on your comment? What's wrong with the ~/.bundle/config approach? Why is better to use --path? – Noach Magedman Mar 20 '12 at 21:43
  • And while we're on the topic of ~/.bundle/config, what does the triple-dash do? – Noach Magedman Mar 20 '12 at 22:45
  • Just an incremental improvement on your commands: bundle exec rails new . **-f** --skip-bundle Since this is a newly created folder, it's safe to automatically accept the prompt and overwrite the Gemfile. – Noach Magedman Mar 21 '12 at 16:35
  • 1
    For the record, more recent versions of rails automatically run `bundle install` at the completion of `rails new` (3.2.2 does this; 3.0.12 does not. Not sure exactly when it was added.) However the bundle install fails when run automatically. --skip-bundle skips this step. It works when you then run it yourself manually. – Noach Magedman Mar 21 '12 at 16:42
  • 2
    `~/.bundle/config` is an internal implementation detail of Bundler. We use it to remember flags passed to install, like `--path vendor/bundle`. You shouldn't edit it yourself because unsupported things could happen if you do that. :) The triple dash is because it's stored as a YAML file. I specifically suggested --skip-bundle so that the failing autorun of `bundle install` would be skipped. I believe it fails because of how these commands bootstrap the rails gem. – indirect Mar 21 '12 at 17:00
  • 3
    Now you can call `rails _3.1.2_ new ...blablabla...`. – Hauleth Apr 08 '13 at 22:51
  • I found that running `bundle install` after the above steps can lead to dependency issues where `bundle update` does not. I've outlined the process [on a blog post](http://www.relativkreativ.at/articles/managing-multiple-rails-versions-with-rbenv) - I actually made a small shell script out of it so I don't have to type it again all the time. – Michael Trojanek May 02 '14 at 16:14
12

Suppose you have rails 3.1.0 installed, but you want to create a new project using rails 3.2.13 which is not installed.

Let's say you want the new project to be in ~/projects/Project2.

gem install rails -v 3.2.13
cd ~/projects
rails _3.2.13_ new Project2

This will create the Gemfile for you, locked to the version of rails you specified on the command-line.

I deliberately omitted the idea of keeping a separate copy of gems for the new project, because that goes against the Bundler philosophy, which is to have all gems installed in one place. When you run rails, Bundler will pick the correct gem versions automatically from that central location. That means a project can share gems instead of installing a fresh copy for itself. (Note, however that each version of ruby you install will have its own gems. This is a good thing because native extensions likely won't work across ruby versions.)

You do have to be a bit more aware, because most commands, like rake, will load the newest version of rake that you have installed. You'll need to run bundle exec rake ... to make sure the correct version is loaded. Usually I'll run bundle exec for all commands except rails. You can create an alias to make it shorter (I use bex). To automate this with gem executables, you can use rbenv-binstubs, but you still have to be aware that running non-gem executables like ruby and irb won't automatically use the Gemfile.

Sidenote: rails new will run bundle install, which will check for the newest version of the dependencies. If you want bundler to try to use currently installed gems that satisfy the dependency requirements, you can skip the bundle install with rails new --skip-bundle, then run bundle check in the app dir.

Sidenote 2: suppose you want to use a ruby version for Project2 (e.g. 2.1.8) that's different from the default (e.g. 2.3.0). In that case, running gem install as specified above will install the gems under 2.3.0, which is a waste of time because you'll need to install the gems again under 2.1.8. To solve that problem, you can force the commands to use the preferred version via environment variable:

RBENV_VERSION=2.1.8  gem install rails -v 3.2.13
cd ~/projects
RBENV_VERSION=2.1.8  rails _3.2.13_ new Project2
echo 2.1.8 > Project2/.ruby-version

You could use rbenv shell to set the variable, but I only recommend that if you don't want rbenv to auto-switch based on .ruby-version files for the duration of that shell. It's very easy to forget that you have the variable set, and when you cd to a different project, it won't be using the version you expect.

Kelvin
  • 20,119
  • 3
  • 60
  • 68
  • re: Sidenote 2: I think I'd be apt to forget the variable name and have to look it up. Not worth the time. Better to open a new window just for a quick `rbenv shell` session, closing immediately after. Personally, that would be easier for me to remember. YMMV. Further, if you use `bundle exec` for _all_ commands, _including_ `rails`, Bundler would catch you if you forgot that you had run `rbenv shell` and went off into another project folder. – Noach Magedman Jun 07 '16 at 14:23
  • What is your rationale for not starting Rails with Bundler. (re: "Usually I'll run bundle exec for all commands except rails") – Noach Magedman Jun 07 '16 at 14:23
  • n.b. some aliases I am addicted to: alias be='bundle exec' ; alias brs='be rails server' ; alias brc='be rails console' ; – Noach Magedman Jun 07 '16 at 14:24
  • 1
    @NoachMagedman re: not starting rails with bundler - I don't because the `rails` executable automatically uses the `Gemfile` (of course it wouldn't hurt to prepend `bundle exec` anyway, but personally, I'm too lazy to type it, but apparently not too lazy to remember when to use it). Btw, Rails isn't using black magic; you can load the `Gemfile` explicitly in your own apps by using `Bundler.setup` or `Bundler.require`. – Kelvin Jun 08 '16 at 15:36
8

There's a good recent post on exactly the topic of gemsets / bundler here http://rakeroutes.com/blog/how-to-use-bundler-instead-of-rvm-gemsets/ Good background you can apply to your rbenv setup.

Max
  • 136
  • 1
  • 4
  • Very good writeup. If I had known that one, I would not have had to publish [mine](http://www.relativkreativ.at/articles/managing-multiple-rails-versions-with-rbenv) :-) – Michael Trojanek May 02 '14 at 21:36