Railtie を使った Rails3の拡張 (翻訳版)

この記事はコミュニティ ゲスト投稿者兼 Engine Yard OB の Andre Arko 氏から英語版ブログで2010年10月3日に寄せられたものです。Arko 氏は 5 年前から Ruby and Rails を使ってウェブ アプリケーションを作成されており、Bundler コア チームのメンバーでもあります。同氏は Plex に勤務するかたわら、@indirect としてのツイートや、andre.arko.net でのブログも公開しています。

Rails 3.0 の gem プラグイン

ついに Rails 3.0 がリリースされました。このバージョンには Railtie という、Rails を拡張するための素晴らしい新機能が追加されています。Railtie は Rails 3 のコア コンポーネントの基盤であり、これには Carlhuda さんが何か月もかけて丁寧にリファクタリングを行った成果が活かされています。Rails の機能は、alias_method_chain をまったく使用せずに、従来よりずっと簡単に拡張できるようになりました。

ただ、Rails を拡張するシステムは徹底点検したものの、ドキュメントの方はまだ更新されていません。Rails プラグイン ガイドには、古い Rails 2 スタイルによるプラグインの書き方が説明されているだけです。Ilya Grigorik 氏が Railtie & Creating Plugins (Railtie とプラグインの作成) のブログ記事を書かれていますが、これは Railtie プラグインで可能な機能のごく一部に触れているだけです。この投稿では、Railtie プラグインの作成、Rails 初期化プロセスへのフック処理、Railtie プラグインの gem としてのパッケージ化、そして Rails 3 アプリケーションで gem プラグインを使用する方法についてそれぞれ説明したいと思います。

Railtie プラグインの作成

Railtie を作成するのは簡単です。単に ::Rails::Railtie から継承するクラスを作成するだけです。Railtie のすべてのサブクラスを使用して、Rails アプリケーションが初期化されます。ActionController、ActionView、およびその他の Rails コンポーネントも Railtie なので、作成したプラグインは Rails アプリケーションの第一級メンバーとして動作することができます。正式な Rails コンポーネントで使用されるものと同じメソッドとコンテキストにアクセスできます。次に、Rails アプリケーションの起動時に読み込まれる最小の Railtie のサンプルを挙げます。
require 'rails'
class MyCoolRailtie < Rails::Railtie
  # railtie code goes here
end
Railtie ドキュメントには各 Railtie クラス内で利用できるすべてのメソッドが一覧されていますが、Railtie で何が可能かについては詳しく説明されていません。そこで、Railtie のメソッドを使用して Rails のカスタマイズと拡張を行う方法を示すサンプルの Railtie を、いくつかアルファベット順にご紹介します。

console

console メソッドを使用すると、Rails コンソールの起動時に実行されるコードを Railtie によって追加できます。
console do
  Foo.console_mode!
end

generators

Rails は lib/generators/*.rb で定義されたすべてのジェネレーターを自動的に要求します。Rails::Generators を、別のディレクトリに入っている Railtie と併せて配布する場合は、このメソッドを使って要求することができます。
generators do
  require 'path/to/generator'
end

rake_tasks

アプリケーション用の rake タスクを Railtie と併せて配布する場合、このメソッドを使って読み込みます。
rake_tasks do
  require 'path/to/railtie.tasks'
end

initializer

initializer メソッドは Railtie にさまざまな機能を提供します。アプリケーション ディレクトリの config/initializers に配置されるファイルなど、Rails のブート プロセス中に実行される initializer を作成します。initializer メソッドは、自分の initializer の前か後に特定の initializer を実行したい場合に、:after:before の 2 つのオプションを指定できます。
initializer "my_cool_railtie.boot_foo" do
  Foo.boot(Bar)
end

initializer "my_cool_railtie.boot_bar", :before => "my_cool_railtie.boot_foo" do Bar.boot! end

Rails の設定フック

Railties で提供される最大の拡張フックには config メソッドという何気ない名前が付いています。このメソッドは、ブートしているアプリケーションに属する Railtie::Configuration のインスタンスを返します。config オブジェクトは Rails アプリケーションの environment.rb ファイル内で提供されるものと同じなので、これによっていろいろな面白い操作が可能になります。以下に、config を使って Rails アプリケーションの初期化と設定の方法に変更を加える例をコメント付きでいくつか示します。

after_initialize

このメソッドは、Rails が完全に初期化され、アプリケーションの initializer がすべて実行された後で実行されるブロックを受け入れます。

app_middleware

このメソッドは、Rails アプリケーションへの要求の処理に使用される MiddlewareStack を公開します。useswap など、MiddlewareStack で定義されている任意のメソッドを使用して、Rails アプリケーションの Rack ミドルウェアを管理できます。たとえば、Railtie に Rack ミドルウェア MyRailtie::Middleware が含まれている場合、これを Rails アプリケーションのミドルウェア スタックに次のように追加することができます。
config.middlewares.use MyRailtie::Middleware

before_configuration

このメソッドにブロックとして渡されるコードは、application.rb 内にあるアプリケーション設定ブロックが実行される直前に実行されます。これは通常、下記の jquery-rails の例のように、プラグインのユーザーが自分で上書きできる既定のオプションを設定する最適の場所です。

before_eager_load

before_eager_load に渡されたブロックは、Rails がアプリケーションのクラスを要求する前に実行されます。eager load は開発モードで実行されることはありません。しかし、Rails を読み込んだ後、アプリケーション コードが読み込まれる前にコードを実行する必要がある場合には、ここに配置します。
config.before_eager_load do
  SomeClass.set_important_value = "RoboCop"
end

before_initialize

このメソッドは、Rails の初期化プロセスが開始する前に実行されるブロックを受け入れます。これは基本的には initializer を作成してアプリケーションの最初の initializer の前に実行するよう設定するのと同じ効果があります。

generators

このオブジェクトには rails generate コマンドを実行すると呼び出されるジェネレーターの設定が格納されます。
config.generators do |g|
  g.orm             :datamapper, :migration => true
  g.template_engine :haml
  g.test_framework  :rspec
end
これを使ってコンソールで色付きのログを無効にすることもできます。
config.generators.colorize_logging = false

to_prepare

最後に、to_prepare は重要なメソッドで、1 回限りのセットアップを行うことができます。このメソッドに渡されたブロックは、開発モードでは各要求ごとに実行されますが、本番では 1 度だけ実行されます。これは、アプリケーションが要求の処理を始める前に 1 度だけ何かをセットアップする必要がある場合に使用します。

サンプル コード

ここまで読んで、こんなものを使う機会が本当にあるのかと疑っている方も多いと思います。そこで、以下に gem にパッケージ化された Railtie プラグインの例をいくつか挙げることにします。

rspec-rails

rspec-rails プラグインは、RSpec gem を Rails と統合する一連の rake タスクおよびジェネレーターと併せて配布されます。
module RSpec
  module Rails
    class Railtie < ::Rails::Railtie
      config.generators.integration_tool :rspec
      config.generators.test_framework   :rspec

  rake_tasks do
    load "rspec/rails/tasks/rspec.rake"
  end
end

end end

この Railtie は 3 つの処理を行います。まず、integration_tool メソッドを使って、統合テストに使用されるジェネレーターを設定します。次に、モデル、コントローラー、およびビュー テストの生成に使用されるジェネレーターを設定します (test_framework メソッドを使用) 。最後に、RSpec rake タスクを読み込んで、test-unit テストの代わりに Spec テストを実行します。

jquery-rails

jquery-rails プラグインはジェネレーターとともに配布されますが、このジェネレーターは jQuery (Rails ヘルパーを jQuery で有効にする jquery-ujs スクリプト) のダウンロードとインストールを行い、オプションで jQueryUI をインストールします。
module Jquery
  module Rails
    class Railtie < ::Rails::Railtie
      config.before_configuration do
        if ::Rails.root.join('public/javascripts/jquery-ui.min.js').exist?
          config.action_view.javascript_expansions[:defaults] =
            %w(jquery.min jquery-ui.min rails)
        else
          config.action_view.javascript_expansions[:defaults] =
            %w(jquery.min rails)
        end
      end
    end
  end
end
この Railtie が行う設定は 1 つだけですが、jQueryUI ライブラリをチェックして設定する値を決定します。config.before_configuration フックの使用により、application.rb 設定ブロックが実行される直前に実行されます。したがって、jQueryUI のチェックに必要な Rails.root にアクセスでき、またプラグインの提示する新たな既定値以外の値を使用したいユーザーは、各自の application.rbjavascript_expansion[:defaults] を上書きすることが可能です。

haml-rails

haml-rails gem は、ERB で書かれている既定の生成済みビューの代わりに、Haml で書かれたビュー用のジェネレーターを提供します。
module Haml
  module Rails
    class Railtie < ::Rails::Railtie
      config.generators.template_engine :haml

  config.before_initialize do
    Haml.init_rails(binding)
    Haml::Template.options[:format] = :html5
  end
end

end end

この Railtie は、rails generate の実行時に Rails によって呼び出されるテンプレート エンジンを変更してから、Haml を Rails 用に初期化して、Haml の出力形式を HTML5 に設定します。

gem プラグインのパッケージ化

Railtie プラグインを Rails 用の gem プラグインにするのは簡単です。そのため、配布や管理、アップグレードも容易に行えます。まず必要なのは gem です。gem がまだない場合は、Bundler を使って新しい gem を簡単に作成できます。bundle gem my_new_gem を実行するだけで、Bundler がスケルトン gem と、ベスト プラクティスに沿った gemspec を生成してくれます。gem ができたら、lib/my_new_gem.rb の読み込み時に Railtie サブクラスが定義されていることを確認してください。Railtie を個別のファイルに定義してそのファイルを要求するか、あるいは直接定義することもできます。最後に、Rails gem への依存関係 (~>3.0) を gemspec に追加します。

作成した gem が標準の Ruby ライブラリでもあり、Rails gem に依存したくない場合は、Railtie を別個のファイルに配置して、メイン ライブラリ ファイル内でそのファイルを条件付きで要求することができます。

# lib/my_new_gem/my_cool_railtie.rb
module MyNewGem
  class MyCoolRailtie < ::Rails::Railtie
    # Railtie code here
  end
end
# lib/my_new_gem.rb
require 'my_new_gem/my_cool_railtie.rb' if defined?(Rails)
これにより、gem が Rails アプリケーションのコンテキスト外で読み込まれる場合に、Railtie なしでの読み込み可能になります。

これで gem に Railtie を追加できました。次は、これを構築して Gemcutter にリリースします。gem が Gemcutter にリリースされたら、Rails 3 アプリケーションで使用するのはごく簡単です。gem を Gemfile に追加するだけです。bundle install を実行すると、Bundler がその gem をダウンロードしてインストールし、Rails がこれを読み込んで、あとは Rails::Railtie クラスがすべて処理してくれます。