scramble cadenza

技術ネタのガラクタ置き場

ActiveSupport::Configurable の話

イントロ

よく設定ファイルで見るクラスを簡単に作れる module ActiveSupport::Configurable の紹介
使い方は、rails/configurable.rb at master · rails/rails のコメント文を読んだほうが早い。

よくあるやつ

具体例を幾つか紹介。
引数が違ったり、ブロック引数有り/無しだったり、メソッド名が違う等の差はあれど、どれも似ている

vagrant

Vagrantfile を拝借

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "centos7"
  config.vm.network "private_network", ip: "192.168.33.10"
  ....
end

rails

config/environments/development.rb を拝借

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false
  ...
end

tapp

esminc/tapp より

Tapp.configure do |config|
  config.default_printer = :awesome_print
  config.report_caller   = true
end

rails_config

railsconfig/rails_config より

RailsConfig.setup do |config|
  config.const_name = 'AppSettings'
  config.use_env = true
end

他にもあったけど、紙面の関係上省略。

使ってみる

インスタンスを使う

require 'active_support/configurable'

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id, :name
end

c = MyConfig.new
c.id = 'aaa'
c.id
#=> 'aaa'

c.name = 'bbb'
c.name
#=> 'bbb'

c.hoge
#=> NoMethodError: undefined method `hoge'

config_accessorメソッド名を渡すと、(内部でゴニョゴニョしてるが)アクセサが作られるというだけ。
また、config というインスタンスメソッドが定義されて、それを呼ぶと登録した設定が全て取れる。

c.config
#=> {:id=>"aaa", :name=>"bbb"}

クラスを使う

ActiveSupport::Configurable を include したクラスでは、ブロックを渡すお馴染みのインターフェイスが提供される。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id, :name
end

# みたことあるやつや!
MyConfig.configure do |config|
  config.id = 'aaa'
  config.name = 'bbb'
end

MyConfig.id
#=> 'aaa'

MyConfig.name
#=> "bbb"

MyConfig.config
#=> {:id=>"aaa", :name=>"bbb"}

とはいえ、これだとクラス自体が設定を持っていて、何か気持ち悪い。

応用

例えば以下のようにクラスを書いてみると、しっくりくる。

class MyApp
  class Config
    include ActiveSupport::Configurable
    config_accessor :id, :name
  end

  def self.configure(&block)
    yield config
  end

  def self.config
    @config ||= Config.new
  end
end

MyApp.configure do |config|
  config.id = 'aaa'
  config.name = 'bbb'
end

MyApp.config
#=> <MyApp::Config:0x007f9b7b9ab870 @_config={:id=>"aaa", :name=>"bbb"}>

config = MyApp.config
config.id
#=> 'aaa'
config.name
#=> 'bbb'
  • 設定を保持するクラス(MyApp::Config)に ActiveSupport::Configurable を include させる
    • 更に config_accessor でアクセサを宣言する

とするだけで

  • MyApp.config とすれば設定インスタンスを参照可能
    • MyApp を参照できれば、誰でも config を知ることができる
  • MyApp::Config はアクセサを定義しているだけ

みたいな事ができ、馴染みのあるコードになってきた。

けどこれだけだと、config_acessor の代わりに attr_accessor でいいんじゃね?ってなる。

オプションを渡す

ところがどっこい config_accessor にはオプションを渡すことができる。
instance_readerinstance_writer だ。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name, instance_reader: false
end

c = MyConfig.new
c.id = 'aaa'
c.id
#=> "aaa"

c.name = 'bbb'
c.name
#=> NoMethodError: undefined method `name'

instance_reader: false を与えると、MyConfig#name という reader のメソッドが提供されなくなるので、NoMethodError になる。

instance_writer: false はというと、その逆になる。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name, instance_writer: false
end

c = MyConfig.new

c.id = 'aaa'
c.id
#=> "aaa"

c.name = 'bbb'
#=> NoMethodError: undefined method `name='
c.name
#=> nil

今度は MyConfig#name= というメソッドだけ提供されていないので、writer を呼ぼうとすると NoMethodError になる。

ブロックを渡す

default 値を定義することもできて、ブロックを渡せば可能。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name do
    'bbb'
  end
end

c = MyConfig.new
c.name
#=> "bbb"

まとめ

rails/configurable.rb at master · rails/rails

デフォルト値などを考えだすまでは attr_accessor でいいんじゃないか...? って思いました。
けど使い方次第でしょうねー