Edited at

bundle install時に--path vendor/bundleを付ける必要性は本当にあるのか、もう一度よく考えてみよう


TL; DR(最初に結論)



  • bundle installをする場合は--path vendor/bundleを付けてプロジェクトごとにgemを管理しろ、という意見をよく見かける。

  • しかし、pathを指定しないと問題が起きる可能性があるのは、かなり特殊な条件下に限られる(100人いたら100人全員が遭遇するような問題ではない)。

  • よって、--path vendor/bundleのオプションは、付けたい人が付ければよいだけで、開発者全員に強制するようなルールではない、と筆者は考える。


はじめに

bundle installコマンドを実行するとき、Ruby界に大きく分けて2つの流派があります。それは「--path vendor/bundleを付ける派」と「付けない派」です。

付ける派の一部の人はときどき、「--path vendor/bundleを付けるのが正しいやり方なので、必ず付けるべき」といった旨の発言をします。

しかし、本当にそうなのでしょうか?

僕はかれこれ7〜8年Ruby(主にRails)で開発をしてきていますが、bundle install時に--path vendor/bundleを付けたことは一度もありません。

「付ける派」の人の意見に従うなら、僕は今までずっと間違った方法でBundlerを使ってきたことになります。

「付ける派」の人が書いた技術記事は定期的に上がってくるので、僕はこれまでに何度か「なぜ--path vendor/bundleを付けるのか?付けないとどういった問題が起きるのか?」ということを調べたことがあります。

ですが、調べてみても多くの場合は「オプションを付けないとgemがグローバル(system)にインストールされてしまうから」とか「gemをプロジェクトごとに管理できるから」といった、理由になっているようでなっていない説明しか見つけられませんでした。

しかし、おそらくですが、--path vendor/bundleは必ずしも付けなくて良い(付けるか付けないかは個人の自由である)と僕は考えています。

そこで、この記事ではその結論を導き出すために、実際によくある意見と、その意見に対する考察(もしくは反論)を書いていきます。


対象読者

本記事の対象読者は「--path vendor/bundleは付けなければいけないと考えているが、その理由については明確に言えない人たち」です。

明確な意図と目的をもって--path vendor/bundleを付けている方(なおかつ、むやみやたらに他人にそのスタイルを押しつけない方)は対象外になります。


前提条件

本記事は以下のような環境を想定して執筆しています。


  • rbenvでRubyのバージョンが管理されているローカルの開発環境

  • 加えて、DockerやVagrantのような、コンテナ技術・仮想化技術を使っていない実行環境

サーバー環境(本番環境)にアプリケーションをデプロイする際のbundle installは議論の対象外とします。


意見1「オプションを付けないとgemがグローバルにインストールされるから良くない」

「付ける派」の人は「オプションを付けないとgemがグローバル(system)にインストールされるからNGである」といった旨の理由をよく掲げます。

「グローバルにインストールされる」というのはすなわち、Bundlerを使ってインストールしたgemが開発マシン内のどこでも使える状態になる(bundle installしたプロジェクト以外の場所でも使える)ことを意味します。

では、gemがグローバルにインストールされると具体的にどういう問題が起きるのでしょうか?


gemのバージョン違いに起因する問題が発生する?

グローバルにgemがインストールされると、たしかに様々なgemがひとつのマシンに共存した状態になります。

同じ名前のgemでもバージョンが違えば、それぞれのバージョンがマシン内にインストールされます。

しかし、Railsアプリケーションのように、Bundlerを使ってgemを管理しているプロジェクトであれば、様々なバージョンの様々なgemがグローバルにインストールされている状況下でも、Bundlerがうまくgem同士の依存関係をハンドリングしてくれるはずです(そのために生まれたのがBundlerなのですから)。

問題が起きるとすれば、それはBundlerを使わずにサードパーティgem(rspecのように、Ruby標準ではないgem)を利用しているプロジェクトが存在している場合です。

ですが、最近のRailsプロジェクトであれば、ほぼ100% Bundlerを使っているはずです。

Rails以外のプロジェクトであっても、gemの依存関係をきれいに管理するためにBundlerを利用しているケースは多いと思います。

「いえ、私のマシンにはBundlerを使わずにサードパーティgemを利用している開発プロジェクトがあります!このプロジェクトは今後もBundlerを利用する予定はありません!」という人がいれば、--path vendor/bundleを付ける意味はありそうです。

しかし、おそらくそういう環境は最近ではレアケースだと思います。

ですので、「誰もがみな、--path vendor/bundleを付けるべき」という理由にはなりえません。


コラム:「付けるのが常識」という考え方はRails 2とRails 3の過渡期に生まれた?

なぜこんなに--path vendor/bundleを付けろ、という意見をたくさん見かけるのか、僕にはその根拠がいまだによくわからないのですが、もしかするとそれはRails 2とRails 3の過渡期に生まれたのかもしれません。

僕がネットを検索していると以下のような記事を見かけました。


Rails3からBundlerが導入されgemの管理がしやすくなりましたが、色々なRailsアプリでほいほいbundle installを実行するとシステムにインストールされるgemが結構カオスになってきます。また、Rails2とRails3が同居する開発環境だとかなり面倒くさくなります。精神衛生上あまり宜しくありません。

そこでbundlerにオプションを渡してgemを任意のディレクトリにインストールし、gemをRailsプロジェクト毎に管理することをオススメします。

出典 bundle installするときはpathを指定しよう | dakatsuka's blog


上の記事の公開日は2010年11月です。この頃は僕もまだRubyを使っていなかったので、当時の状況はよく知りません。

しかし、引用文の中にも書いてあるとおり、BundlerはRails 3から導入されたので、Rails 2の頃はBundlerなしでgemを管理していたことになります。

ということは、この当時であれば、前述したような「私のマシンにはBundlerを使わずにサードパーティgemを利用している開発プロジェクトがあります」という状況が起こりえます。

なので、「Rails 2のプロジェクトがマシン内に存在しているなら、Rails 3では--path vendor/bundleを付けてbundle installしよう」という考え方は理にかなっています。

もしかすると、この当時(2010年頃)に生まれた「お作法」が、Bundlerが十分に普及した現在でも「おまじない」として生き伸びているのかもしれません。


意見2「gemがグローバルにインストールされると、他のシステムコマンドと衝突することがある」

gemの中にはときどき「マシン全体で使えるシステムコマンド」を提供するものがあります。

当然のように使っているのであまり意識することはないかもしれませんが、たとえばbundleコマンドはbundler gemが提供しているものですし、railsコマンドはrails gemが提供しているものです。

これは僕も体験したことがあるのですが、まれにgemが提供しているシステムコマンドと同じ名前のコマンドが別にあって、PATHの関係から片方は実行できて、もう片方はうまく実行できない、という問題が起きることがあります。

具体例を挙げると、heroku gemのherokuコマンドや、chromedriver-helper gemのchromedriverコマンドです。

herokuコマンドはもともとgemで提供されていたのですが、その後Homebrewでインストール可能な新バージョン(node.js版)に書き換えられました。

しかし、僕の環境ではPATHの関係でHomebrewでインストールした新しいherokuコマンドよりも、gemが提供していた古いherokuコマンドが優先されて困った経験があります。

説明が長くなるので詳細は割愛しますが、chromedriver-helperでも同様に、別の方法でセットアップした同名ツールとコマンド名が衝突して困ったことがあります。

ただ、これはどちらも「gemが提供していた既存のコマンドが、別の形で置き換えられた(またはその逆)」という特殊なケースです。

様々なプロジェクトに携わる中で、僕はこれまで何百というgemをインストールしてきたはずですが、僕が記憶している限り、問題が起きたのはこの2件だけです。

つまり、これはかなりのレアケースですので、個人的にはこれだけの理由で--path vendor/bundleを強制する根拠にはならないんじゃないかと考えています。

ちなみにここで挙げたheroku gemやchromedriver-helper gemは、いずれもメンテナンスが終了した過去のgemになっています。

ですので、ここで述べた問題が引き続き発生する可能性はあまり高くありません。


意見3「pathを指定した方がctags等を使ってgemのコードを検索するときに便利」

僕は利用したことがないので詳しいことはよくわからないのですが、ctagsのようなツールを使ってgemのコードを検索するときは、グローバルよりもプロジェクトごとにgemを管理した方が便利なことがあるようです。


他にも、ctagsなどを利用する際にgemがプロジェクト下にインストールされていると便利である可能性があります。

出典 bundle install --path vendor/bundleと、bundle listとgem listの違いについて - Qiita


ですが、これも「ctagsのようなツールを使う人なら」という前提条件が付きます。

なので、この条件に該当しない人は無理にpathを指定する必要はない、と言えるはずです。

ちなみに僕は普段、gemのコードはRubyMineを使って検索するので、グローバルにgemがインストールされていても特に困ったことはありません。


意見4「グローバルにgemがインストールされて開発マシンが汚染されるのは気持ち悪い」

漠然と「グローバルにgemがインストールされて開発マシンが汚染されるのは気持ち悪い。プロジェクト内に閉じている方がなんとなく安心する」という人も中にはいるかもしれません。

ですが、これだと単に「個人の好み」ですので、他人に強制する理由にはなり得ません。

この意見を突き詰めていくと最後は宗教論争になってしまうので、この意見は理由として適切ではないと僕は考えています。


その他:インストールの手間やストレージの消費量について

グローバルにインストールしたときと、pathを指定したときのインストールの手間やストレージの消費量については、一長一短があるので優劣が付けにくい気がします。

グローバルにインストールした場合


  • 他のプロジェクトで、すでに同一のRubyバージョンかつ、同一のバージョンのgemがインストールされているときは、そのgemのインストールがスキップされる

  • プロジェクトのRubyバージョンを変更すると、再度bundle installを実行してgemを再インストールする必要がある

pathを指定した場合


  • プロジェクトで使用しているRubyのteenyバージョンが上がったとき(例 2.5.0→2.5.1)は、gemの再インストールが不要(ただし、マイナーバージョンが上がったときは再インストールが必要)

  • プロジェクトごとにgemをインストールするので、マシン内に多数のプロジェクトを抱えているとインストールの手間やストレージの消費量が増える恐れがある

ただし、bundle installはそこまで頻繁に実施する作業でもないですし、ストレージの消費量に関しても、最近のストレージ容量を考えればインストールされるgemの個数が多少変わったところで、大きな問題にはなりにくいと思います。

ですので、インストールの手間やストレージの消費量は、--path vendor/bundleを付けるかどうかの大きな理由にならないと考えています。


2019.6.7追記 「逆にpathを付けないメリットって何かあるの?」

この記事は基本的に「付けない派」の僕が、「付ける派」の意見を考察する、というスタイルで書いています。

しかし、逆に「付ける派」の人たちから見ると、「なんで付けないの?付けると困ることがあるの?どっちでもいいなら、付ければええやん」と思われるかもしれません。

僕がどっちでもいいなら付けない方を好む理由は以下のとおりです。


  • systemにインストールするのがBundlerのデフォルトで、pathを指定するのがオプションだから(rails newしたときもデフォルトでsystemにbundle installされるはず)

  • デフォルトのまま使った方が、余計な手順が増えず、シンプルだから

  • 「デフォルトに従う」の方が、チームでも統一がしやすいから(「忘れずにオプションを付けるべし」をチーム内で統一するのはコストが大きいし、本来の目的がなおざりにされて形骸化しやすい)

  • 「しまった!pathを付け忘れてうっかりsystemにインストールしちゃった!」みたいなミスをしたときに、「このまま放置するか、それともsystemから全部削除するか」「削除するならどういう手順でやればいいのか」みたいなことで悩む時間がもったいないから(systemにインストールするのがデフォルトの挙動なので、そういうミスも起きやすいのでは)

もしかすると、習慣として長年pathを付け続けている人にとっては上のような理由を見ても「何をそんなことぐらいで」と思うかもしれません。

しかし、今からRubyを始める人や、新しくチームに参加してきた人に対しては、「(致命的な問題が出ない限り)デフォルトのまま使う」とした方が習得のコストが少ないはずです。

ですので、bundle installのpath指定は「オプトイン方式(デフォルトはオフで、利用する意思がある人だけがオンにする)」の方が良いと僕は考えています。


まとめ

というわけで、この記事ではなぜ--path vendor/bundleを付けないと具体的にどんな問題が起きるのかを検証し、このオプションを付ける必要性が本当にあるのかどうかについて考察してみました。

--path vendor/bundleを付けずにbundle installした場合は、たしかに特定の条件下においてはいくつかの問題が発生します。

ですが、あくまで「特定の条件」に該当した場合だけであり、100人いれば100人が遭遇するような問題ではありません。

むしろ、割合的にはかなり少ないんじゃないかと想像します。

このように考えると、「--path vendor/bundleを付けるのが正解で、付けないのは間違いだ」と断言するのは少し乱暴なのではないでしょうか。

僕個人の感覚でいえば、「付けたい人が付ければ良い。ただし、意味もなく他人に強制するのはやりすぎ」というのが結論になります。

もし、「ネットに書いてあったから」とか「先輩プログラマがそうしてたから」という理由だけで--path vendor/bundleを付けてきた人がいたら、あらためて自分がなぜ--path vendor/bundleを付けているのかを自問自答してみてください。