Rails の config で設定できる Rails 向け gem を作る

Rails アプリを開発していくと,プラグインやらエンジンやらで何やかんやと gem を作っていくことになります.このようにして作った gem は当然 Rails 向けなので,Rails アプリケーションの config で設定できるようにしたいところです.たとえば,passingloop という gem を作ったとして,この gem で token と email を設定できるとすれば,

Depot::Application.configure do
  config.passingloop.token = '15x3oegi'
  config.passingloop.email = 'passingloop@example.com'
end

のように書けるようにしたいところです.しかし,このように書くだけでは,

[...]/railties-3.1.1/lib/rails/railtie/configuration.rb:78:in `method_missing':
 undefined method `passingloop' for
 #<Rails::Application::Configuration:0x000001017db250> (NoMethodError)

と,config.passingloop が見つからずにエラーになります.そこで,泣く泣く

Depot::Application.configure do
  config.passingloop_token = '15x3oegi'
  config.passingloop_email = 'passingloop@example.com'
end

と "." を "_" に書き直して逃げたくなるのですが,格好悪いことこの上ありません.そこで,本エントリでは,gem ごとに config が名前空間をつくれるように,すなわち,はじめに紹介した

Depot::Application.configure do
  config.passingloop.token = '15x3oegi'
  config.passingloop.email = 'passingloop@example.com'
end

のように設定できる gem の作り方を解説します.

答は簡単 Rails::Railtie と ActiveSupport::OrderedOptions

先のエラーメッセージを見れば分かるように,

undefined method `passingloop' for
 #<Rails::Application::Configuration:0x000001017db250>
 (NoMethodError)

と,Rails::Application::Configuration なオブジェクトに passingloop が無いことが問題なので追加してあげます.ここでエラーメッセージにある Rails::Application::Configuration なオブジェクトの正体は,Rails::Railtie の config なので,gem で config にキー passingloop を追加してあげます.config.passingloop に追加するオブジェクトに Hash を使ってもいいのですが,

  • .name で name の値を取得できる
  • .name = value で name に値 value を設定できる

ようにしたいので,ActiveSupport::OrderedOptions を使います.この ActiveSupport::OrderedOptions は,Hash のように使えて,

hash['name'] = 'value'
hash['name']            #=> 'value'

と書く代わりに,

ordered_hash.name = 'value'
ordered_hash.name           #=> 'value'

と書ける優れものです.早速,gem に lib/passingloop/railtie.rb を作成し,

module Passingloop
  class Railtie < Rails::Railtie
    config.passingloop = ActiveSupport::OrderedOptions.new
  end
end

lib/passingloop.rb で忘れないように require して,

require "passingloop/version"
require "passingloop/railtie"

module Passingloop
...

rails c すると,

~/work/depot % rails c
Loading development environment (Rails 3.1.1)
irb(main):001:0> Depot::Application.configure do
irb(main):002:1*   puts config.passingloop.email
irb(main):003:1>   puts config.passingloop.token
irb(main):004:1> end
passingloop@example.com
15x3oegi
=> nil

今度はうまくいきました.

Engine と Plugin ならもっと簡単

Rails のエンジンとプラグインは,実は,両方とも Railtie です.ですので,たとえば,Rails エンジンを config で設定可能にするには,Railtie クラスをわざわざ作る必要はなく,Engine クラスの中で config に ActiveSupport::OrderedOptions を設定するだけで ok です.

module Blog
  class Engine < Rails::Engine
    isolate_namespace Blog

    config.blog = ActiveSupport::OrderedOptions.new
  end
end

これは,Blog エンジンの設定を Rails アプリケーションからできるようにする例です.

Railtie を使えば,config 以外にも Rails 的に設定できる.

本エントリでは,gem 独自の設定項目を Rails の config から設定できるようにする方法を説明しました.Railtie を使えば,config 以外にも initializer や generetor, middleware, Rake タスクなども gem から設定できるようになります.Gem を使わない Rails 開発ではあまり気にかけない Rails::Railtie ですが,一歩進んで gem 中心の開発を目指すのなら,一度ソースを読んでみることをお勧めします.