Rails 4.1以降のコンソールコマンドは必ず bin/ を付けなきゃいけないの?

  • 184
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに:Springが起動するコマンドはいったいどれだ!?

Rails 4.1からはアプリケーションpreloaderとしてSpringが標準で組みこまれています。
Springを使うとバックグラウンドでRailsサーバーが動いている状態になるので、rails g やrakeコマンドが素早く起動します。

一般に、Springを使う場合は bin/rails gbin/rake -T のように、bin/ を付けるように言われていますが、これは必須なんでしょうか?
bin/ を付けずに実行すると、何が起きるんでしょうか?

というわけでちょっと調べてみました。

対象バージョン

  • Rails 4.1または4.2

railsコマンドの場合:bin/ を付けなくても大丈夫!

まず最初に、bin/rails crails 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 cbin/ を付けなくてもSpringが起動します。
これは rails grails r (rails runner)でも同様です。

ただし、rails srails new では常にSpringは起動しません。

なんで bin/ がいらないの?

Railsはプロジェクトルートの bin ディレクトリにある rails ファイル(実行可能ファイル)を優先的に起動する仕様になっているからです。

よって、bin/rails(実行可能ファイル)がSpringを使うようになっていれば、bin/ を付けなくてもSpringが起動することになります。

このあたりの挙動を詳しく知りたい方は、以下のコードを読んでみてください。

https://github.com/rails/rails/blob/4-2-stable/railties/lib/rails/app_rails_loader.rb

rakeコマンドの場合:bin/ が必要

同様に、今度は bin/rake -Trake -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 -Tbundle 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 -Trake -Tbundle exec rake -T の3種類を比較しました。
bin/ を付けないとSpringが起動しないことはわかりましたが、rake -Tbundle 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 statusspring 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を参照してください。

https://github.com/rails/spring

補足2. direnvを使って bin/ の入力を省略する

direnvというツールを使うと、自動的にプロジェクトルートの bin の下にPATHを通すことができます。
つまり、bin/ を入力しなくても優先的に bin の下にある実行可能ファイルを呼び出してくれるようになります。

direnvの使い方は@kompiroさんのこちらの記事が参考になります。

direnvを使おう

なお、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 について翻訳してみた

僕も「binstubって名前は知ってるけど、仕組みはよくわかってない」という状態でしたが、今回の調査でかなり詳しくなることができました。