使いやすくなった Rails 3.1 の Engine

Rails エンジン (Rails Engine) は、ひとことで言うと、Rails アプリケーションの再利用を容易にする仕組みです。Rails エンジンに関することは、https://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb に全て書いてあるのですが、自分用のメモとして Rails エンジンの {良いところ,作り方,使い方} を簡単にまとめてみます。

この記事は、執筆時点の最新版 (Rails 3.1.0.rc5) をもとにしているため、8/22 リリースとアナウンスされている 3.1.0 では参考にならないかもしれません。ご注意を。

Rails エンジンとは

他の Rails アプリケーションを自分の Rails アプリケーションから利用したい、という場合、どのようにすればよいでしょうか。Rails アプリケーションは Web アプリケーションなので、2 つのアプリケーションを立ち上げて、ハイパーリンクでつないであげるだけで連携できます。

しかし、Rails プロセスを 2 つ立ち上げておくのは効率も格好も悪いです。そこで、Rails エンジンの登場です。Rails アプリケーションをエンジンとして取り込んでしまえば、Rails プロセスを 2 つ立ち上げる必要が無くなります。

このように、Rails エンジンを使うと、アプリケーションの再利用が容易になります。

以下、Rails エンジンが組み込まれるほうの Rails アプリケーション(図の上のほうの 「Rails アプリ」)を便宜上「親アプリケーション」と呼ぶことにします。

Rails エンジンの良いところ

ここまで読んでいて、Railsプラグインを作ったことのある人なら、「それ、プラグインでできるよ」と思ったに違いありません。私も最初そう思いました。しかし、エンジンにはプラグインには無い良さがあります。たくさんあるので一部を列挙すると:

  • app ディレクトリの models/controllers/helpers を自動的にロード
  • エンジンが独自の config/routes.rb のルーティング設定、config/locales/* のロケール、lib/tasks/* のタスクをもつことができ、しかも、自動的にロード
  • エンジン独自の Rack ミドルウェア
  • (などなど)

があります。

Rails エンジンの作り方

「でも、Rails エンジンって作るの難しいんでしょう?」

いえいえ、rails new ではなく rails plugin new するだけで、あとは、Rails アプリケーションと同じです。ただし、rails plugin new に --mountable オプションをつけるのをお忘れなく。

$ rails plugin new engine_name --mountable

例として、blog エンジンを作成するつもりになって、rails plugins コマンドを実行してみます。

$ rails plugin new blog --mountable

すると、blog ディレクトリにエンジンの雛形ができます。あとは、雛形を参考に、普通の Rails アプリケーションと同じように開発するだけです。

Rails エンジンのディレクトリ構成

ちょっと寄り道をして blog ディレクトリをのぞいてみましょう。

blog ディレクトリを上から見ていくと、Rails アプリケーションでも毎度おなじみ app フォルダの下に blog.gemspec ファイルが見つかります。名前のとおり、gemspec の雛形になっています。gem にしておくと、アプリケーションへのエンジン組み込みが簡単になるので、エンジンを作るときには gem にするのがお勧めです。

config ディレクトリを見ると、中に routes.rb が見えます。この config/routes.rb にエンジン独自のルートを記述します。もう、親アプリケーションの config/routes.rb にルーティング設定をコピーするプラグイン時代は過去のものになりました。その次に見える lib フォルダの話は後回しにして、Rails アプリケーションでおなじみの db ディレクトリが見つかりません。これは、Rails エンジンが親アプリケーションと同じデータベースを利用するためです。

しかし、データベース無しでは各種テストを実行できません。そこで、test/dummy にテスト用のダミー Rails アプリケーションが用意されています。エンジンをテストするときには、この test/dummy アプリケーションにエンジンを組み込んでテストすることになります。

最後に、後回しにした lib ディレクトリについて説明します。この lib ディレクトリには、このアプリケーションがエンジンであることを証明する大切なコードが含まれています。それが、lib/blog/engine.rb です。

# lib/blog/engine.rb
module Blog
  class Engine < Rails::Engine
    isolate_namespace Blog
  end
end

isolate_namespace() は普通の Rails アプリケーションでは見ないメソッドです。このメソッドは、ヘルパーメソッドと名前つきルートについて、エンジンと親アプリケーションの名前空間を分離するものです。

エンジンが独自のルーティング設定 (config/routes.rb) をもつことを前に述べました。たとえば、エンジンに独自の root ルートを設定し、親アプリケーションにも root ルートが設定されていたとしましょう。この場合、エンジンのコントローラから root_path を参照するとどうなるのでしょう? エンジンも親アプリケーションの名前空間を共有するので困ったことになってしまいます。

ここで isolate_namespace() の登場です。isolate_namespace() で指定されたエンジンのモジュールからは、親アプリケーションのヘルパーメソッドと名前つきルートにアクセスできなくなります。isolate_namespace() した場合、エンジンのコントローラで root_path を呼び出すと、エンジンの root_path を参照することになります。

もしエンジンから親アプリケーションの root_path にアクセスしたくなったときでも大丈夫です。main_app をつけて、

module Blog
  class CommentController < ActionController::Base
    def index
      main_app.root_path
    end
  end
end

などとすれば、アクセスできます。

さて、話を lib/blog/engine.rb に戻します。このファイルには Blog::Engine クラスが含まれているのですが、この Blog::Engine クラスを親アプリケーションがロードすることによって、親アプリケーションがエンジンを組み込みます。lib ディレクトリのもう一つのファイル、lib/blog.rb を見てみると、

# lib/blog.rb
require "blog/engine"

module Blog
end

となっているので、親アプリケーションでは、

require 'blog'

するだけで、blog エンジンを組み込めます。

エンジンの使い方

最後に、親アプリケーションからエンジンを利用する方法についてです。親アプリケーションからエンジンを利用するには、

  1. エンジンのロード
  2. エンジンのマウント
  3. マイグレーションのコピー

を行ないます。

(1) エンジンのロード

Rails エンジンを親アプリケーションから使うには、エンジンの MyEngine::Engine クラスを親アプリケーションの config/application.rb か、Gemfile でロードします。例で挙げた blog エンジンであれば、Blog::Engine クラスをロードします。blog エンジンが gem になっていれば、親アプリケーションの Gemfile で

gem 'blog', :git => 'https://example.com/path/to/blog.git'

とするだけで、コードのコピーも Blog::Engine クラスのロードもできるので簡単です。もし、gem になっていなくても、config/application.rb で

require 'blog'

すればエンジンを組み込めます。$: にエンジンの lib を追加するのをお忘れなく。

(2) エンジンのマウント

親アプリケーションの config/routes.rb でエンジンをマウントします。次の例では、MyApplication が親アプリケーションです:

# my_application/config/routes.rb
MyApplication::Application.routes.draw do
  mount Blog::Engine => '/blog'
end
(3) マイグレーションのコピー

今のところ、エンジンのマイグレーションを親アプリケーションでそのまま実行することはできないようです。代わりに、エンジンのマイグレーションを親アプリケーションのマイグレーションディレクトリにコピーする方法が用意されています。Rake タスク

$ rake railties:install:migrations

を実行します。

おわりに

Rails エンジンの {良いところ,作り方,使い方} を簡単に説明してきたのですが、まだまだ、エンジン名の話やら、ヘルパーの話やら、endpoint の話やら、Rails エンジンについて話していないことがたくさんあります。実際に Rails エンジンを作るとなると、この記事の情報だけでは不十分なので、この記事を読んで Rails エンジンに興味をもったら、ぜひ、https://github.com/rails/rails/blob/master/railties/lib/rails/engine.rb を読んでみてください。Rails エンジンは、ここで書いた以上に便利でかつパワフルです。

一部の情報については古くなっているのですが、http://piotrsarnacki.com/2010/09/14/mountable-engines/ も有用です(Migration と Asset に関する記述は参考にならないかも)などは参考になります。

Rails 3.0 以前からエンジンの仕組み自体は存在しており、RailsAdminTolk といったエンジンが既にあります。Rails 3.1 からは、独自の routes.rb をもてたり、マウントできたりするようになり、さらに使いやすくなりました。Rails 3.1 のエンジンとして公開されるアプリケーションが増え、アプリケーション単位の再利用で Rails 開発がもっと楽しくなることを願っています。