bundle execはなぜ必要なのか

この記事は「なんか分かんないけど`bundle exec`つけないと動かないからつけてるけど、つけなきゃいけない理由が知りたい」という方向けに説明を試みるものです。

# TL;DR
一言で言うと、`bundle exec` をつけると`Gemfile.lock`に書いてあるとおりに`require`するようになります。
`Gemfile.lock`に記載されているgemは、お互いの依存関係を満たすようにバージョンが選ばれているので、`Gem:: ConflictError`を防ぐことができます。

# もうちょっと詳しく
## Bundlerがないと何が起きるのか
具体例で説明します。以下のような依存関係を持つgem, "GemA", "GemB"があるとしましょう。両方ともインストール済みとします。
![how_bundler_works_dependency.png](https://qiita-image-store.s3.amazonaws.com/0/75567/5be76859-49f2-4612-39bb-91078c25869a.png)

```gem_a.rb
module GemA
  Version = "1.0.0"
end
```

```gem_b.rb
require 'gem_a'

module GemB
  Version = '1.0.0'

  def self.say
    puts "I'm using GemA ver. #{::GemA::Version}!"
  end
end
```

```shell-session
# gem list

*** LOCAL GEMS ***

gem_a (1.0.0)
gem_b (1.0.0)
```

もちろんどちらも`require`できます。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil
```

ここで、GemAを2.0.0にアップデートします。

```shell_session
# gem update gem_a
Updating installed gems
Updating gem_a
Fetching: gem_a-2.0.0.gem (100%)
Successfully installed  gem_a-2.0.0.gem
Gems updated: gem_a
# gem list

*** LOCAL GEMS ***

gem_a (2.0.0, 1.0.0)
gem_b (1.0.0)
```

アップデートできました。irbでさっきと同じことをしてみます。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
```

ちゃんとアップデートされています。GemBも使ってみましょう。

```irb
irb(main):003:0> require 'gem_b'
Traceback (most recent call last):
        8: from /usr/local/bin/irb:11:in `<main>'
        7: from (irb):3
        6: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:39:in `require'
        5: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:128:in `rescue in require'
        4: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:217:in `try_activate'
        3: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems.rb:224:in `rescue in try_activate'
        2: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:1438:in `activate'
        1: from /usr/local/Cellar/ruby/2.5.1/lib/ruby/2.5.0/rubygems/specification.rb:2325:in `raise_if_conflicts'
Gem::ConflictError (Unable to activate gem_b-1.0.0, because gem_a-2.0.0 conflicts with gem_a (= 1.0.0))
```

おおっと…困りました。GemBが壊れてしまいました。既にGemA (2.0.0)を`require`しているので、GemBに必要なGemA (1.0.0)を読み込むことができず、エラーになってしまいました。[^1]

![how_bundler_works_conflict.png](https://qiita-image-store.s3.amazonaws.com/0/75567/0633a839-6d00-6d05-fafc-556fb1a965d8.png)

## どうなると嬉しいのか
GemBを使うプロジェクトでは、アップデートしたGemA (2.0.0)を使わず、GemA (1.0.0)がそのまま使えればよかったのです。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
```

一方で、GemBを使わないプロジェクトでは、最新のGemA (2.0.0)を使いたいですね。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
```

これを実現するのがBundlerであり、`bundle exec`というコマンドです。

## Bundlerがあるとどうなるのか

GemAとGemBを使いたいプロジェクトに、こんな感じのGemfileを用意します。

```Gemfile
gem 'gem_a'
gem 'gem_b'
```

そして、`bundle`を実行すると、`Gemfile.lock`が生成されます。

```shell-session
# bundle 
Fetching gem metadata from https://rubygems.org/.
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using bundler 1.16.2
Using gem_a 1.0.0
Using gem_b 1.0.0
Bundle complete! 2 Gemfile dependencies, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

# cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    gem_a (1.0.0)
    gem_b (1.0.0)
      gem_a (= 1.0.0)

PLATFORMS
  ruby

DEPENDENCIES
  gem_a
  gem_b

BUNDLED WITH
   1.16.2
```

この状態で`bundle exec irb`してみましょう。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "1.0.0"
irb(main):003:0> require 'gem_b'
=> true
irb(main):004:0> GemB::Version
=> "1.0.0"
irb(main):005:0> GemB::say
I'm using GemA ver. 1.0.0!
=> nil
```

ちゃんと動きます！

普通にirbを起動すると、新しいバージョンのGemA (2.0.0)もちゃんと使えます。

```irb
irb(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"
```

`Gemfile`から`gem_b`を取り除いて`bundle`すれば、GemA (2.0.0)が使えます。

```Gemfile
gem 'gem_a'
```

```shell-session
# bundle
Using bundler 1.16.2
Using gem_a 2.0.0
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

# cat Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    gem_a (2.0.0)

PLATFORMS
  ruby

DEPENDENCIES
  gem_a

BUNDLED WITH
   1.16.2
```

Bundlerってすばらしいですね。

# まとめ
Bundlerがあれば、いろんなバージョンのgemをたくさんインストールしても、コンフリクトしないように`require`することができます。

(余談ですが、上記のような動きなので、基本的には`bundle --path=vendor/bundle`とかしないでグローバルインストールしてもちゃんとプロジェクトごとの独立性は保たれるはずです)

[^1]: 「なんでGemBはGemA (2.0.0)を使わずにGemA (1.0.0)を読み込もうとすることができるの？それってBundlerの仕事なんじゃないの？」と思う方は、おそらくGemとBundlerの機能を混同しています。gemの依存関係はgemごとに定義してあります (gemの中に.gemspecという定義ファイルがあります)。このため、gemを1つ使うだけなら普通に`require`すれば、依存しているgemも一緒に適切なバージョンが読み込まれます。一方で、Bundlerは「複数のgemを同時に使うとき、どう依存関係を解決すれば`Gem::ConflictError`を避けられるか？」という問題を解決するためのものです。