FIVETEESIXONE

Rails エンジンの設定を外からできるようにしたい


作っている Rails エンジンの設定を外からやりたくて、どうやるのがいいのかな?と調べてみました。要は、エンジンの挙動をコントロールするための変数なりをエンジンに用意しておいて、マウントする側の親アプリケーションから値を設定したいということですね。

RailsGuides に書いてある方法

RailsGuides のエンジンチュートリアルで紹介されているのは、エンジンのモジュールに mattr_accessor を設定しておいて、親アプリからそのアクセサを介してモジュール変数 (モジュールのクラス変数というべき?) にその値を設定するという方法です。

lib/myengine.rb (エンジン側)

mattr_accessor :something

config/initializers/myengine.rb (親アプリ側)

Myengine.something = "Something Cool"

簡単だし、これでやりたいことは実現可能なのですが、モジュールの名前空間の直下に各設定がそのまま置かれるので、数が増えてくるとゴチャゴチャしそうです。できれば Myengine.config.something のような形にしたい。もちろん config というを何か専用のクラスのインスタンスにしたり、ハッシュにしたりすればすぐにできるんですが、どんどんオレオレ設定になっていくのが嫌なのでもっと標準的な方法にしたいところ。

def configure; yield self; end ってカッコいいな!

たまたま、Refile という gem のコードを眺めていたらこんなのがありました。

module Refile
...
  class << self
...
    # Yield the Refile module as a convenience for configuring multiple
    # config options at once.
    #
    # @yield Refile
    def configure
      yield self
    end
...
end

Refile.configure do |config|
  config.direct_upload = ["cache"]
  config.allow_origin = "*"
  config.logger = Logger.new(STDOUT)
  config.mount_point = "attachments"
  config.automount = true
  config.content_max_age = 60 * 60 * 24 * 365
  config.types[:image] = Refile::Type.new(:image, content_type: %w[image/jpeg image/gif image/png])
end

おぉ、これ何かカッコいい!

やっていること自体は上記した RailsGuides に書かれている方法と同じなのですが、単純に self を引数にしてブロックを実行する configure メソッドを定義することで、Refile.configure ブロック経由でアクセスできるようになってるですね。ちょっと感動しました。この記事を書いたのはこれを記録しておきたかったためです。ぶっちゃけ。

ActiveSupport::Configurable

ActiveSupport::Configurable というモジュールがあります。エンジンの設定の場合も、このモジュールが提供する機能を利用するのが一番わかりやすいんじゃないかと思います。

lib/myengine/config.rb (エンジン側)

module Bigmouth
  class Config
    include ActiveSupport::Configurable

    config_accessor :something

    configure do |config|
      config.something = "Something Special"
    end
  end
end

config/initializers/myengine.rb (親アプリ側)

Myengine.configure do |config|
  config.something = "Something Cool"
end if defined?(Myengine)

結局は Config 専用のクラスを作る必要があるので、コード量は少し多くなってしまいます。しかし、Rails の ActiveSupport が標準で提供するインターフェースを使えるのは大きなメリットだと思います。また、ちゃんと確認してはいませんが、コードを少し読んでみたところでは、親クラスの設定を継承することもできるような感じに見えました。必要になることがあれば確認してみたいと思います。