はじめに:Springが起動するコマンドはいったいどれだ!?
Rails 4.1からはアプリケーションpreloaderとしてSpringが標準で組みこまれています。
Springを使うとバックグラウンドでRailsサーバーが動いている状態になるので、rails g
やrakeコマンドが素早く起動します。
一般に、Springを使う場合は bin/rails g
や bin/rake -T
のように、bin/
を付けるように言われていますが、これは必須なんでしょうか?
bin/
を付けずに実行すると、何が起きるんでしょうか?
というわけでちょっと調べてみました。
対象バージョン
- Rails 4.1または4.2
railsコマンドの場合:bin/ を付けなくても大丈夫!
まず最初に、bin/rails c
と rails c
を実行して、Springのstatusがどう変わるか確認してみました。
$ bin/spring status
Spring is not running.
$ bin/rails c
Loading development environment (Rails 4.2.0)
irb(main):001:0> exit
$ bin/spring status
Spring is running:
37179 spring server | cb | started 6 secs ago
37180 spring app | cb | started 6 secs ago | development mode
$ bin/spring stop
Spring stopped.
$ rails c
Loading development environment (Rails 4.2.0)
irb(main):001:0> exit
$ bin/spring status
Spring is running:
37318 spring server | cb | started 12 secs ago
37319 spring app | cb | started 12 secs ago | development mode
上記の実行結果を見てもらうとわかりますが、なんと rails c
は bin/
を付けなくてもSpringが起動します。
これは rails g
や rails r
(rails runner
)でも同様です。
ただし、rails s
や rails new
では常にSpringは起動しません。
なんで bin/ がいらないの?
Railsはプロジェクトルートの bin
ディレクトリにある rails
ファイル(実行可能ファイル)を優先的に起動する仕様になっているからです。
よって、bin/rails
(実行可能ファイル)がSpringを使うようになっていれば、bin/
を付けなくてもSpringが起動することになります。
このあたりの挙動を詳しく知りたい方は、以下のコードを読んでみてください。
rakeコマンドの場合:bin/ が必要
同様に、今度は bin/rake -T
と rake -T
、さらに bundle exec rake -T
を比較してみました。
$ bin/spring status
Spring is not running.
$ bin/rake -T
rake about # List versions of all Rails frameworks and the environment
# 省略
$ bin/spring status
Spring is running:
37693 spring server | cb | started 5 secs ago
37694 spring app | cb | started 5 secs ago | development mode
$ bin/spring stop
Spring stopped.
$ rake -T
rake about # List versions of all Rails frameworks and the environment
# 省略
$ bin/spring status
Spring is not running.
$ bundle exec rake -T
rake about # List versions of all Rails frameworks and the environment
# 省略
$ bin/spring status
Spring is not running.
はい、上の実行例の通り、 rakeコマンドは bin/
を付けないとSpringが起動しません。
Railsのように bin
ディレクトリの下を見にいこうとしないので、bin/
を付けない限り、素の rake
コマンドが実行されます。
つまり、rake -T
や bundle exec rake -T
ではSpringを使わないので、Railsが起動するまで待つ必要があります。
rspec コマンドも bin/ が必要
同じ理由でRSpecをSpring経由で実行する場合は bin/rspec
にする必要があります。
なお、RSpecをSpring経由で実行したい場合は、spring-commands-rspecを使って事前にセットアップしておく必要があります。
# Gemfile
group :development do
gem 'spring-commands-rspec'
end
$ bundle install
$ bundle exec spring binstub rspec
余談:"rake -T" と "bundle exec rake -T" は同じ?
上のrakeコマンドの例では bin/rake -T
と rake -T
、bundle exec rake -T
の3種類を比較しました。
bin/
を付けないとSpringが起動しないことはわかりましたが、rake -T
と bundle exec rake -T
には何か違いがあるのでしょうか?
はい、違いはあります。
bundle exec
を付けないと、通常、マシンにインストールされている最新バージョンのgemが実行されます。
なので、場合によっては意図しないバージョンが起動したり、gem同士の依存関係を正しく解決できなかったりする場合があります。
なので、 RailsのようにBundlerを使うプロジェクト(Gemfile
が存在するプロジェクト)では、bundle exec
または bin/
を付けて実行するのが望ましいです。
これはrakeコマンドに限らず、rspecコマンドやspringコマンドにも同じことが言えます。
spring と bin/spring の挙動の違いにちょっと注意
上のセクションでは「bundle exec
を付けないと意図しないバージョンが起動することがある」と書きましたが、Springにおいてはそれが原因でわかりづらい挙動の違いが出る場合があります。
以下のコマンドの実行例を見てください。
$ spring -v
Spring version 1.2.0
$ bin/spring -v
Spring version 1.1.2
$ spring status
Spring is not running.
$ bin/spring status
Spring is not running.
$ bin/rake -T
rake about # List versions of all Rails frameworks and the environment
# 省略
$ spring status
Spring is not running.
$ bin/spring status
Spring is running:
38771 spring server | cb | started 11 secs ago
38772 spring app | cb | started 11 secs ago | development mode
上の例では、マシンにインストールされている最新のSpringのバージョンは1.2.0で、プロジェクトのBundlerが使っているバージョンが1.1.2になっています。
Springはサーバーとコマンドのバージョンが異なると、spring status
や spring stop
を実行したときに、実行中のSpringサーバーをちゃんと認識してくれない場合があります。
上の例では spring status
は "Spring is not running." を返していますが、bin/spring status
は "Spring is running:" を返しています。
こうなると、「あれ、Spring動いてないの?」という勘違いが発生したり、Springをちゃんと停止できなかったりする問題が出てくるので、注意が必要です。
まとめ:Rails 4.1以降は bin/ を付けて実行する癖を付けよう
というわけで、結論としては Rails 4.1以降でSpringを使いたい場合は、とりあえず bin/
を付けておくのがベスト だと思います。
railsコマンドだけは bin/
が不要ですが、付けていても実害はないので、細かい違いを覚えるのが面倒であれば「とりあえず bin/
を付ける」と考えておけばOKでしょう。
bin/
を付けておけば、Spring経由で高速にコマンドが実行されますし、Bundlerがgemのバージョンや依存関係も正しく処理してくれるので、通常の開発ワークフローであればベストな選択になるはずです。
補足1. 既存のRailsプロジェクトをアップグレードした場合はSpringのセットアップが必要
Rails 4.1以降でrails new
した場合は最初からSpringが有効になっていますが、Rails 4.0以前のプロジェクトからアップグレードした場合は別途セットアップが必要です。
# Gemfile
group :development do
gem 'spring'
end
$ bundle install
$ bundle exec spring binstub --all
また、SpringはRails 3.2や4.0でも使えるみたいです。
詳しくはSpringのREADMEを参照してください。
補足2. direnvを使って bin/ の入力を省略する
direnvというツールを使うと、自動的にプロジェクトルートの bin
の下にPATHを通すことができます。
つまり、bin/
を入力しなくても優先的に bin
の下にある実行可能ファイルを呼び出してくれるようになります。
direnvの使い方は@kompiroさんのこちらの記事が参考になります。
なお、Powを使っている場合はエラーを避けるため、.envrc
をこんな感じで書く必要があります。
# .envrc
# See https://github.com/basecamp/pow/issues/73#issuecomment-48735353
if [ "$(type -t direnv_load)" = 'function' ]; then
PATH_add bin
fi
補足3. もっと詳しく理解したい場合は binstub の仕組みを勉強しよう
今回説明した話はRubyやRailsの binstub の仕組みに関連しています。
「なんでこうなるのか、もっと詳しい理由が知りたい」という人は、binstubの仕組みを勉強するのが良いでしょう。
そうすればどういう理屈で各コマンドが動作しているのか理解できるはずです。
binstubについては、@tanaka51さんのこちらの記事が大変参考になります。
僕も「binstubって名前は知ってるけど、仕組みはよくわかってない」という状態でしたが、今回の調査でかなり詳しくなることができました。