LoginSignup
155
131

More than 5 years have passed since last update.

bundle execはなぜ必要なのか

Last updated at Posted at 2018-07-04

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

TL;DR

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

もうちょっと詳しく

Bundlerがないと何が起きるのか

具体例で説明します。以下のような依存関係を持つgem, "GemA", "GemB"があるとしましょう。両方ともインストール済みとします。
how_bundler_works_dependency.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
# gem list

*** LOCAL GEMS ***

gem_a (1.0.0)
gem_b (1.0.0)

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

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にアップデートします。

# 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(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"

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

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

どうなると嬉しいのか

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

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(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"

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

Bundlerがあるとどうなるのか

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

gem 'gem_a'
gem 'gem_b'

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

# 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(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(main):001:0> require 'gem_a'
=> true
irb(main):002:0> GemA::Version
=> "2.0.0"

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

gem 'gem_a'
# 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を避けられるか?」という問題を解決するためのものです。 

155
131
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
155
131