rails -v で Railsのバージョンが分かる仕組み

rails --help を見てみると、-vでバージョンを表示したり、 rails hogehogeアプリケーションを作成したり、rails -h でヘルプを表示したりしているのですが、いったいどのようなロジックになっているのか気になったので見てみました。

% rails -v
Rails 2.3.5
% rails "_1.2.3_" -v 
Rails 1.2.3

railsコマンドの場所を知る

% which rails
/opt/local/bin/rails

/opt/local/bin ディレクトリは「追加アプリケーション」を設置するディレクトリですね。

railsコマンドの内容を知る

railsコマンドはRubyファイルです。

  1 #!/opt/local/bin/ruby
  2 #
  3 # This file was generated by RubyGems.
  4 #
  5 # The application 'rails' is installed as part of a gem, and
  6 # this file is here to facilitate running it.
  7 #
  8
  9 require 'rubygems'
 10
 11 version = ">= 0"
 12
 13 if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
 14   version = $1
 15   ARGV.shift
 16 end
 17
 18 gem 'rails', version
 19 load Gem.bin_path('rails', 'rails', version)

ポイントは、Gem.bin_path('rails', 'rails', version) です。rails -v と入力したとき、最初は /opt/local/bin/rails で受けるのですが、Gem.bin_path('rails', 'rails', version) で指定のバージョンのRailsをロードして、-v の引数は /opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails に移ります。

詳しく見る

rubyloadRuby プログラム file をロードして実行します。Gem.bin_path は 以下のような動きをします。

/opt/local/bin% irb
irb(main):001:0> Gem.bin_path('rails')
=> "/opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails"
irb(main):002:0> Gem.bin_path('rails', 'rails')
=> "/opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails"
irb(main):003:0> Gem.bin_path('rails', 'rails', '1.2.3')
=> "/opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/bin/rails"

つまり、railsファイルの19行目は、

load "/opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails"

とやっていることと同じになります。
load したあと、-v が引数として評価され実行されます。

% ruby /opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails -v
Rails 2.3.5
% ruby /opt/local/lib/ruby/gems/1.8/gems/rails-1.2.3/bin/rails -v
Rails 1.2.3

railsコマンド内で require 'rubygems' していますので、正確に書くと以下になります。

% ruby -rubygems /opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails -v
Rails 2.3.5

/opt/local/lib/ruby/gems/1.8/gems/rails-2.3.5/bin/rails の中身

  1 require File.dirname(__FILE__) + '/../lib/ruby_version_check'                        
  2 Signal.trap("INT") { puts; exit }
  3 
  4 require File.dirname(__FILE__) + '/../lib/rails/version'
  5 if %w(--version -v).include? ARGV.first
  6   puts "Rails #{Rails::VERSION::STRING}"
  7   exit(0)
  8 end
  9 
 10 freeze   = ARGV.any? { |option| %w(--freeze -f).include?(option) }
 11 
 12 app_path = ARGV.first
 13 
 14 require File.dirname(__FILE__) + '/../lib/rails_generator'
 15 
 16 require 'rails_generator/scripts/generate'
 17 Rails::Generator::Base.use_application_sources!
 18 Rails::Generator::Scripts::Generate.new.run(ARGV, :generator => 'app')
 19 
 20 Dir.chdir(app_path) { `rake rails:freeze:gems`; puts "froze" } if freeze

上のファイルを見ると、

puts "Rails #{Rails::VERSION::STRING}"

でバージョンが表示されるのが分かりますね。

実験環境

OS Mac OS X 10.6.2

RubyGemsの状態は以下です。

/Users/japanrock% gem env
RubyGems Environment:
  - RUBYGEMS VERSION: 1.3.6
  - RUBY VERSION: 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin10]
  - INSTALLATION DIRECTORY: /opt/local/lib/ruby/gems/1.8
  - RUBY EXECUTABLE: /opt/local/bin/ruby
  - EXECUTABLE DIRECTORY: /opt/local/bin
  - RUBYGEMS PLATFORMS:
    - ruby
    - x86-darwin-10
  - GEM PATHS:
     - /opt/local/lib/ruby/gems/1.8
     - /Users/japanrock/.gem/ruby/1.8
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :benchmark => false
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - http://rubygems.org/

まとめ

最近、毎日Railsのコードを少しずつ読むようにしています。感じるのはプログラマの成長を支える上で、コードリーディングは非常に有益だと思うことです。
Railsのコードリーディングはrubyのことも知れますし、もちろんrailsのことも知れます。一流の人がどのようにコードを書くのか、どのような設計をするのか、 思いつかないようなrubyの使い方など、想像力が膨らみます。
私の場合、時間を決めて(例えば 10:00〜11:00)1日1時間行うようにしています。時間が取れないようであれば、30分になったりします。だらだら伸ばしたりしません。そうして毎日続ける方が私にはあっているようです。最近は何事も習慣化するように心がけています。