Railsアプリケーションで設定値をまとめるためのgemとして次のようなものがよく使われています。
上記3つのgemは細かい違いはあれど、基本的に Yaml ファイルに設定値を記述しておけば Ruby のコード内でグローバル定数のように扱えるというものです。
これらの gem はよく出来ていて、私も何度か使ってきたのですが、だんだんなぜYamlで頑張っているのだろうかという気持ちになりました。
例えば、 次の Yaml を見てください。
user:
password_expiry_period: <%= 6.months %>
これは、ユーザーのパスワードの有効期限を設定する項目です。この値はコード中では次のようにして使います。
class User < ApplicationRecord
def expired?
# password_changed_at カラムに前回パスワードを変更した日時が記録してある
password_changed_at < Time.current.ago(Settings.user.password_expiry_period)
end
end
このコードでも特に不自由なく動くと思いますが、もし password_expiry_period
の部分を設定値にせずにべた書きするとしたら次のように書くのが自然ではないでしょうか?
password_changed_at < 6.months.ago
そのため、私としては設定値を使う場合でも次のように書けると嬉しいです。
password_changed_at < Settings.user.password_expiry_period.ago
しかし、このような書き方はエラーになります。 Yaml の password_expiry_period: <%= 6.months %>
の ERB 部分は先に評価されて Fixnum
の 15552000
として扱われるため、 ActiveSupport::Duration
ではないからです。
つまり、次のようになります。
> 6.months
6 months
> 6.months.class
=> ActiveSupport::Duration
> 6.months.to_i
=> 15552000
> Settings.user.password_expiry_period
=> 15552000
> Settings.user.password_expiry_period.class
=> Fixnum
> Settings.user.password_expiry_period.ago # 本当はこう書きたいがエラーになる
NoMethodError: undefined method `ago' for 15552000:Fixnum
> Time.current.ago(Settings.user.password_expiry_period) # 仕方なくこう書く
=> Mon, 04 Jul 2016 17:24:16 JST +09:00
そもそも設定ファイルでは ERB を使わずに Yaml の機能だけで書くべきという考えもあると思いますが、今回のように設定ファイル内でRubyの便利な機能を使いたいケースはありますし、どうせ設定ファイルの内容は Ruby のコード内で使うものなので、設定ファイルもRubyのコードとして定義した方が素直なのではないかと思います。
dry-configurable で設定ファイルを作成する
前置きが長くなりましたが、私の意見としては、 gem
の内部設定を記述する時等によく見かける configure
を使って Rails アプリケーションの設定も定義してしまおうということです。
このロジックは自前で作ってもいいですが、 dry-configurable という gem が便利なのでこれを使います。
dry-configurable の基本的な使い方はリンク先のドキュメントをご覧ください。ここでは Rails アプリケーションにおける設定ファイルとしての活用方法を説明します。
まず、 app/initializers/settings.rb
を次の内容で作成します。
require_dependency Rails.root.join('config/settings/default.rb')
require_dependency Rails.root.join("config/settings/#{Rails.env}.rb")
config/settings
内に次のファイルを作成します。
config/settings
├── default.rb
├── development.rb
├── production.rb
└── test.rb
# config/settings/default.rb
class Settings
extend Dry::Configurable
setting :user do
setting :password_expiry_period, 6.months
end
end
# config/settings/development.rb
Settings.configure do |config|
# 環境毎に値を変えたい場合は次のように定義します
# config.user.password_expiry_period = 6.months
end
これを用いると、 User
モデルのコードは次のようになります。
class User < ApplicationRecord
def expired?
# password_changed_at カラムに前回パスワードを変更した日時が記録してある
password_changed_at < Settings.config.user.password_expiry_period.ago
end
end
考えていたことができるようになりました!
今回作成した Settings
クラスはご覧の通りただの Ruby のクラスなので、 Yaml + ERB では定義できなかった設定も定義できるようになります。
例えば、 lambda です。
user:
after_signed_in_logic: <%= -> { redirect_to root_path } %>
# これは動かない
Settings.user.after_signed_in_logic.call
class Settings
extend Dry::Configurable
setting :user do
setting :after_signed_in_logic, -> { redirect_to root_path }
end
end
# 動く!
Settings.config.user.after_signed_in_logic.call
(サインイン後の処理をこのように記述するのは適切ではないかもしれませんが) Yaml + ERB よりも Ruby の方が表現力が高いことは明らかです。
Yamlの表現力に不満を覚えている方は、以上の方法をお試しください。