Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby on Rails: Gem等の外部から`config.x`を使わずにネストしたカスタム設定を追加する方法MEMO📝

Ruby on Railsを拡張するGemを作成する等、デフォルトの振る舞いといったものRails.application.configureで設定できるようにしたいなぁと思ったのですが、config.xは使わずにネストしたカスタム設定をいい感じに追加する方法をメモ🗒

Railsガイドを見ると以下のような記載があり、基本的にはconfig.fooの形で直接記載すればいいのですがネストする場合にはhashのkeyとしてアクセスしないといけないので、そういう場合にはconfig.x.fooみたいな形で定義してあげるのが推奨なのですが、Gem側等の外部からの設定でconfig.xを使うのはちょっとイマイチかなとも思ったのでいい感じに実装できないのかなぁと思ったのですが、

Railsの設定オブジェクトに独自のコードを設定するには、config.x名前空間またはconfigに直接コードを書きます。両者の重要な違いは、ネストした設定(config.x.nested.nested.hiなど)の場合はconfig.xを使うべきで、単一レベルの設定(config.helloなど)ではconfigだけを使うべきであるという点です。 Rails アプリケーションを設定する - Railsガイド

以下のようなコードを用意して、initializer等々で初期化時に読み込んであげればconfig.fooの形式でネストしたカスタム設定をいい感じに追加できそうでした🙆‍♂️

# frozen_string_literal: true

require 'active_support'
require 'active_support/ordered_options'
require 'rails/railtie'

module Foo
  class Configration < ActiveSupport::OrderedOptions
    def custom_payload(&block)
      self.custom_payload_method = block
    end
  end

  class Railtie < Rails::Railtie
    config.foo = Configration.new

    config.after_initialize do |_app|
      # NOTE: ユーザーが書き換えた設定値に依存した処理はinitializerによる設定が行われた後に実行するためにここで読み込むなり実行するなりする。
    end
  end
end

Hashを継承した動的にaccessor メソッドが定義されるActiveSupport::OrderedOptionsインスタンスをtopレベルの名前空間config.fooに渡すことでネストした値でもいい感じにkey形式じゃなくても取得できるようになるということですね✨

ActiveSupport::OrderedOptions < Hash OrderedOptions inherits from Hash and provides dynamic accessor methods. ActiveSupport::OrderedOptions

config.xの実態であるRails::Application::Configuration::Custommethod_missingActiveSupport::OrderedOptions.newが設定されるようになってるんですね。

module Rails
  class Application
    class Configuration
      class Custom # :nodoc:
        def initialize
          @configurations = Hash.new
        end

        def method_missing(method, *args)
          if method.end_with?("=")
            @configurations[:"#{method[0..-2]}"] = args.first
          else
            @configurations.fetch(method) {
              @configurations[method] = ActiveSupport::OrderedOptions.new
            }
          end
        end

        def respond_to_missing?(symbol, *)
          true
        end
      end

rails/configuration.rb at v7.0.4.3 · rails/rails · GitHub

便利!

参考

github.com

railsguides.jp