RailsはGemをどう扱っている?Bundlerの仕組みを徹底解説
数週間前、RubyGemsがRubyのロードパス($LOAD_PATH)をどのように管理しているかについての記事を書きました。しかし実は、RailsはRubyGemsを直接使っていません。RailsはBundlerを使ってgemを管理しています。
Bundlerの仕組みを知らないと、Gemfileに1行追加するだけでコードがアプリに組み込まれる様子は、少し「魔法のように」感じられるかもしれません。いったいどうやって、Gemfileへの記述がアプリのコードとして読み込まれるのでしょうか?Bundler、Rails、RubyGemsはどのように連携して、依存関係の管理を簡単にしているのでしょうか?
なぜBundlerが必要なのか?
私はBundlerを「厳格なgemマネージャー」と捉えています。つまり、Bundlerは必要なgemの正しいバージョンをインストールし、アプリがそのバージョンだけを使うよう強制してくれるのです。
これがどれほど便利かを理解するには、Bundlerが登場する前の世界に思いを馳せてみる必要があります。
Bundler以前は、セットアップスクリプトのようなものを使えば、正しいバージョンのgemをインストールすること自体はまだ簡単でした。
gem install rails -v 4.1.0
gem install rake -v 10.3.2
...
(ただし、Rails 4.1の依存関係とRake 10.3.2の依存関係が衝突しないことが前提です!)
ところが、それぞれ異なるバージョンのgemに依存する複数のRailsアプリを同時に開発していたらどうなるでしょうか?細心の注意を払わない限り、恐ろしいgemアクティベーションエラーに遭遇することになります。
Gem::Exception: can't activate hpricot (= 0.6.161, runtime),
already activated hpricot-0.8.3
うっ…。このメッセージは今でも悪夢に出てきそうです。このエラーが出ると、たいてい丸1日かけてgemのインストールとアンインストールを繰り返し、そのマシン上でちょうど正しいバージョンの組み合わせを見つけ出すことになります。しかも、うっかりgem install rakeをひとつ実行するだけで、慎重に築いた環境がすべて台無しになってしまうのです。
rvmのgemsetsはしばらくの間、この問題の救いになりました。しかしセットアップに手間がかかるうえ、うっかり間違ったgemsetにインストールしてしまえば、同じ問題に逆戻りです。Bundlerがあれば、依存関係についてほとんど意識する必要がなく、アプリはたいていそのまま動きます。しかも、gemsetsよりもはるかに少ないセットアップで済みます。
つまり、Bundlerは重要な役割を2つ担っています。1つ目は必要なgemをすべてインストールすること。2つ目はRubyGemsをロックダウンし、そのRailsアプリ内でrequireできるgemをそれらだけに限定することです。
RailsはどうやってBundlerを使っているのか?
中核となる機能としては、Bundlerはgemのインストールと分離を行います。しかしそれだけではありません。Gemfileにあるgemのコードは、どのようにしてRailsアプリの中に入ってくるのでしょうか?
bin/railsを見てみましょう。
#!/usr/bin/env ruby
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
Railsは../config/bootをrequireすることで読み込まれています。次はそのファイルを見てみましょう。
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' # Gemfileに記載されたgemをセットアップする。
おっと、Bundlerの登場です!(ちなみに、環境変数BUNDLE_GEMFILEを設定すれば、別のGemfileを使い分けられることもここで初めて知りました。これはかなり便利です。)
bundler/setupは主に次の2つのことを行います。
- $LOAD_PATHからすべてのgemのパスを取り除く(RubyGemsが加えたロードパスへの変更を打ち消す)。
- その後、Gemfile.lockに記載されているgemだけのロードパスを$LOAD_PATHに戻す。
こうして、ファイルをrequireできるgemは、Gemfileに書かれたものだけに限定されます。
これで必要なgemはすべてロードパス上に乗りました。しかし、RubyGems単体で使う場合は、必要なファイルを自分でrequireしなければなりません。なぜRails+Bundlerの環境では、gemを明示的にrequireしなくていいのでしょうか?
答えは、Railsの起動後に実行されるconfig/application.rbにあります。
# Gemfileに記載されたgemをrequireする。
# :test、:development、:productionなどに限定したgemも含まれる。
Bundler.require(*Rails.groups)
またしてもBundlerです!Bundler.requireは、引数として渡されたすべてのグループに属するgemをrequireします。(ここでいう「グループ」とは、Gemfile内で指定するグループのことです。)
では、Rails.groupsにはどのグループが入るのでしょうか?
# 以下に基づいて、ロード用のrailsグループをすべて返す:
#
# * Railsの環境(environment)
# * 環境変数RAILS_GROUPS
# * 引数として渡されたオプションのenvと、グループ依存を表すハッシュ
#
# groups assets: [:development, :test]
#
# # 戻り値
# # => [:default, :development, :assets] (Rails.env == "development" の場合)
# # => [:default, :production] (Rails.env == "production" の場合)
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
groups.unshift(:default, env)
groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
groups.compact!
groups.uniq!
groups
end
これですべて説明できました。Rails.groupsは、開発モードなら[:default, :development]、本番モードなら[:default, :production]といった具合に、実行環境に応じたグループの配列を返します。
そして、BundlerはGemfileからこれらのグループに属するgemを探し出し、見つかった各gemに対してrequireを呼び出します。たとえばnokogiriというgemがあれば、自動的にrequire "nokogiri"を実行してくれるのです。だからこそ、Railsでは特別なコードを書かなくても、gemがたいていそのまま動くというわけです。
使うツールを深く知ろう
日頃使っているツールをよく理解していれば、作業はぐっと楽になります。頻繁に使うものがあるなら、少し時間をかけてその仕組みを掘り下げてみる価値は十分にあります。
RubyとRailsで開発をしているなら、gemとは毎日向き合うことになります。ぜひ時間をかけて、しっかり学んでみてください!
-
【Rails】Flashメッセージの使い方を徹底解説!alertとnoticeの違いからflash.nowまで
Flashメッセージとは? Flashメッセージとは、Railsアプリケーションのユーザーに対して、自分の操作によって何が起こったのかを伝えるための仕組みです。 メッセージの例: 「パスワードが正しく変更されました」(確認メッセージ) 「ユーザーが見つかりません」(エラーメッセージ) Flashメッセージはコントローラーで設定し、ビューで表示します。ユーザーはそのメッセージを受け取って、次のアクションにつなげられます。 それでは、具体的な使い方を見ていきましょう! Flashメッセージの基本的な使い方 通知メッセージは、flashヘルパーメソッドを使って扱います。 flashオブジェクトは
-
RailsアプリでTwitterログインを実装する方法:OmniAuth-Twitter完全ガイド
このチュートリアルでは、RailsアプリケーションのユーザーがTwitterアカウントを使ってログインできるようにする方法を解説します。OAuthなどのツールのおかげで、この実装はとても簡単に行えるようになりました。 今回は、OmniAuth用のTwitterストラテジーを含む「OmniAuth-Twitter」を使用します。 それでは始めましょう! はじめに:プロジェクトの準備 まずはRailsアプリケーションを生成します。ターミナルで以下のコマンドを実行してください。 rails new Tuts-Social -T 次にGemfileを開き、bootstrap gemを追加します。