Ruby 歴 10 年の私が【絶対に】 gem install rails コマンドを実行しない理由

タイトルは釣りです。

主語が大きすぎてちょっとドキドキしてます。では、タイトル通りの話をしていきます。

# 2022/09/05 追記

- 私は 2022/09 時点で Visual Studio Code の devcontainer に環境を移行しました
    - devcontainer 環境で開発をすることで「環境が汚れる」という点においてはとてもクリーンになりました
    - とはいえ、この記事に記載していることは開発環境に限らず通用することもあると思いますので恥ずかしいですが、残しておきます
- 今見返すと歴も書いてたりして、赤面モノですが、2022 現在でもチョコチョコと「いいね」や「ストック」をしていただけてるようで補足を入れたくなったので追記しました

# 結論

* 結論から言うと、 Ruby の環境が **汚れる** からです
* `gem install xxx` コマンドは基本的に Ruby の環境にインストールされることになります
    * ですので、Ruby が状態を持つことになり、冪等性（べきとうせい）に欠けてしまいます
* `bundler` という便利な仕組みがある上では、 `gem install xxx` を実行するシチュエーションは限定的になると思います
* `gem install`、`bundle install` コマンドを使った時に何が起きているかを意識しましょう

# 開発環境構築のジレンマ

開発環境を構築するにあたって、いろんな方法があります。

画一された手段がないというのはメリットでもあり、デメリットでもあります。 (自由性という意味で)

ですが「よくわからないけど、Qiita に載っていて、それが上手くいったからその手順でいいや」というのは問題です。

OSS で公開されているソフトウェアは用途に特化した形で公開されています。それを上手く活用する方法を自分なりに示したいと思います。

# 具体例

例えば、こういった記事があります

「[(Ubuntu)Ruby on rails 6.0 環境構築](https://qiita.com/Gushi_maru/items/f3b5cc43e135e678085f)」

注意点ですが、 **この記事が悪いということではなく、こういった手順を私は踏まない** という話なだけです。

# じゃぁ、貴方はどうやるの？

私は Rails プロジェクトを構築する際には以下のような手順を踏みます。まずは概要を示してから、具体的なコマンドを書いていきます

1. Ruby をビルドするのに必要なパッケージ(ライブラリなど)を事前にインストールする
2. `rbenv` を利用して Ruby をインストールする
3. Rails 6 以降で必要になった Node.js をインストールする
4. プロジェクトのためのディレクトリを適当な名前で作成する (`example_project`)
5. プロジェクトディレクトリ配下で `bundle init` コマンドを実行し、 `Gemfile` ファイルを作成する
6. 作成した `Gemfile` ファイルを編集して `gem 'rails'` となっている行を有効に(コメントイン)する
7. `bundle install --path=vendor/bundle` を実行して、特定のパス配下に gem をインストールする
8. `rails s` コマンドで立ち上げる

では、具体的なコマンドを書いていきます。
環境は Ubuntu Linux を前提とします。

Ruby が無い状態から開始します。ほぼほぼ「[(Ubuntu)Ruby on rails 6.0 環境構築](https://qiita.com/Gushi_maru/items/f3b5cc43e135e678085f)」と同一ですが、順番が異なります。

## 前準備

```shell
# apt パッケージの情報を最新にする
sudo apt update -y
# Ubuntu にインストールされているソフトウェアを最新にする
sudo apt upgrade -y
# Ruby のビルドに必要なパッケージを apt 経由でインストールする
sudo apt install build-essential -y
sudo apt install -y libssl-dev libreadline-dev zlib1g-dev

# sqlite3 を利用する場合、sqlite3 に関するライブラリをインストールしておく
sudo apt install libsqlite3-dev

# PostgreSQL の場合には下記コマンドを実行
sudo apt-get install postgresql-common
sudo apt-get install libpq-dev

# MySQL or MariaDB の場合には下記コマンドを実行
sudo apt-get install libmysqlclient-dev
```

## Rubyをインストール

```shell
# rbenv（パッケージ管理ツール）をインストール
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
# 環境変数にPathを設定
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
# シェルを再起動
exec $SHELL -l

# ruby-buildをインストール
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build

# Rubyをインストール
rbenv install 2.6.2
rbenv global 2.6.2

# bundler コマンドを gem としてインストールする
# Ruby 2.6 からは bundler が内包されるようになったので、下記の手順は不要です。 2.5 以前では必要です
gem install bundler
```

ここまでで Ubuntu Linux に `ruby` コマンドと `gem` コマンド、 `bundler` コマンドがインストールされた状態になりました。

Ruby on Rails で開発を始めるにはまだかかります。

ですが、この状態でしたら、 Ruby のプログラムを書き始める状態までは完了しています。

**ここから手順が異なります**

次に Rails 6 以降で必要になった Node.js をインストールします。

```shell
# node.js, npmをインストール
sudo apt install -y nodejs npm
# n packageをインストール
sudo npm install n -g
# n packageを使ってnodeをインストール
sudo n stable
# 最初に入れた古いnode.js, npmを削除
sudo apt purge -y nodejs npm
# 再ログイン
exec $SHELL -l
# yarn packageをインストール
sudo npm install yarn -g
# node.js のバージョンが最新か確認
node -v
```

この手順を経ることで、Ubuntu Linux 上に `ruby` (2.6.2) と `gem` コマンド、 `node`、 `yarn` コマンドがインストールされました。

ここで一息入れます。

これらの準備が整うことで Rails 6 を動かす前準備ができたことになります。

次に自分が作りたいプロジェクトのディレクトリを作成し、Rails のセットアップを行います

```shell
# 自分が作成するプロジェクトのディレクトリを作成
mkdir example_project
# 作成したディレクトリに移動
cd example_project

# bundle init を実行して Gemfile ファイルを作成
bundle init

# 作成された Gemfile を編集して「# gem 'rails'」の部分をコメントインする
# 「# gem "rails"」となっている箇所を#(シャープ)を消して「gem 'rails'」という形にする
gedit Gemfile

# rails をインストールするために bundle install
bundle install --path vendor/bundle

# rails new コマンドを実行して、rails に関する gem をまとめてインストールする
bundle exec rails new .
(ここで、「上書きしますか？」的な英文メッセージが表示されるので「Y」を入力)
```

これらの手順を踏むことで、 Rails が利用できるようになります。

```
bundle exec rails s
```

**2020/07/18 追記**

コメントでご指摘いただき、`bundle install` する際に `--path vendor/bundle` に関することについて述べておきます。
Bundler 2.1 以降、 `--path` オプションは deprecated(非推奨) となりました。この点に関しての対処はこちらに記載があるので参照をお願いします。

[bundle install時に"[DEPRECATED] The --path flag is deprecated"という警告が発生した場合の対応手順](https://qiita.com/jnchito/items/62adbea043abf72fa7cc)

# 解説

どういう状態になったかを適宜解説していきます

まず、 「前準備」であるライブラリを入れた状態を図として示します

<img width="463" alt="スクリーンショット 2020-07-17 22.48.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/ca585c32-405b-d875-f21b-08ed44722de4.png">

これらは Linux 上にライブラリがインストールされた状態です。これだけでは、 Ruby コマンドの実行はできません (Ruby がインストールされていないので当たり前ではありますが)

「Rubyをインストール」の状態では、以下の状態になります

<img width="525" alt="スクリーンショット 2020-07-17 23.01.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/c449f919-b1ee-f782-ff87-fbd09567184c.png">

「Ubuntu Linux にインストールされたパッケージ」のライブラリを元に ruby と gem コマンドがインストールされます。bundler コマンドがある理由は `gem install bundler` をコマンドで `bundle` コマンドをインストールしているからです。

次に「Node.js」をインストールした状態を示します。

<img width="524" alt="スクリーンショット 2020-07-17 23.24.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/97a2a66f-2f68-a208-7461-7c738f417f95.png">

点線で示したのは `sudo apt purge -y nodejs npm` コマンドで削除しているためです。

この状態で `ruby`, `gem`, `bundler`, `node`, `npm`, `yarn` コマンドが実行できる状態です。

意識したいポイントとしては、Ruby に関して言えば `ruby`, `gem`, `bundler` コマンドのみ実行可能で、 JavaScript においては `node`, `npm`, `yarn` コマンドが実行できる状態である、というだけです。

次に「自分が作りたいプロジェクトのディレクトリを作成し、Rails のセットアップを行う」という状態を示します。

![スクリーンショット 2020-07-17 23.36.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/adece39e-e988-64e9-13bd-06b80acc2c38.png)

はい、これで vendor/bundle に **プロジェクトに必要なライブラリ** がインストールされたことになります。

これだけだと対比が分かりづらいと思うので、`gem install rails` を実行したケースを示します。

![スクリーンショット 2020-07-17 23.39.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/135f440a-d64f-c908-f037-d114f8744020.png)

強調したいため赤字で示しました。

ここで言いたいこととしては、 <font color="red">**`rails` コマンドを実行する rails の gem と 「vendor/bundle ディレクトリ以下 rails 関連の gem が格納される」のバージョンが異なる可能性がある**</font> ということです。

こういった状態は「ruby にインストールされている特定のバージョンの gem と `vendor/bundle` 以下にインストールされている gem のバージョンが一致しないことには、完全な再現はできない (冪等性（べきとうせい）がない)」という状況を生み出し得るということです。

一方、先述した方法(`gem install rails` を用いない方法) では、「Ubuntu Linux の HOME ディレクトリ以下に設置されたプログラム類」には依存しません。

ですので、 `bundler` コマンドを有効活用して、特定のディレクトリ配下にプログラムに必要なライブラリをインストールするようにし、「できる限り Ruby 標準の gem の環境はシンプルに保つ」ということをすると、快適な Ruby の生活ができると思います。

(とはいえ、このお話は恐らく、 Python などパッケージ管理システムを持っているエコシステムでは同じ話が適用できますが)

ですので、きちんと **各コマンドが何の役割を担当していて、どういったことに適するかの意味を理解して実行することが大切** だと思います。

勢いで書いているため、乱筆乱文お許しください。

# 蛇足

* Ruby on Rails の環境を構築するのに、なんで Node.js をインストールしなきゃいけないんだ！という話
    * Rails 6 から webpacker というものが標準になりました
    * webpacker は node.js のエコシステムである yarn を利用することが必須になります
    * 一つの言語で完結しないのは微妙感がありますが、結局 JS 関連で悩むことになるよりも、Node.js のエコシステムに乗っかってしまった方が長期的に考えると幸せになる、ということだと個人的には解釈しています。