Railsの設定ファイルはYamlよりRubyの方が良いと思う

Railsアプリケーションで設定値をまとめるためのgemとして次のようなものがよく使われています。

- [Setting Logic](https://github.com/binarylogic/settingslogic)
- [Rails Config](https://github.com/railsconfig/config)
- [Figaro](https://github.com/laserlemon/figaro)

上記3つのgemは細かい違いはあれど、基本的に Yaml ファイルに設定値を記述しておけば Ruby のコード内でグローバル定数のように扱えるというものです。
これらの gem はよく出来ていて、私も何度か使ってきたのですが、だんだんなぜYamlで頑張っているのだろうかという気持ちになりました。

例えば、 次の Yaml を見てください。

```yaml
user:
  password_expiry_period: <%= 6.months %>
```

これは、ユーザーのパスワードの有効期限を設定する項目です。この値はコード中では次のようにして使います。

```ruby
class User < ApplicationRecord
  def expired?
    # password_changed_at カラムに前回パスワードを変更した日時が記録してある
    password_changed_at < Time.current.ago(Settings.user.password_expiry_period)
  end
end
```

このコードでも特に不自由なく動くと思いますが、もし `password_expiry_period` の部分を設定値にせずにべた書きするとしたら次のように書くのが自然ではないでしょうか？

```ruby
password_changed_at < 6.months.ago
```

そのため、私としては設定値を使う場合でも次のように書けると嬉しいです。

```ruby
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](http://dry-rb.org/gems/dry-configurable/) という gem が便利なのでこれを使います。

[dry-configurable](http://dry-rb.org/gems/dry-configurable/) の基本的な使い方はリンク先のドキュメントをご覧ください。ここでは Rails アプリケーションにおける設定ファイルとしての活用方法を説明します。

まず、 `app/initializers/settings.rb` を次の内容で作成します。

```ruby
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
```

```ruby
# 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` モデルのコードは次のようになります。

```ruby
class User < ApplicationRecord
  def expired?
    # password_changed_at カラムに前回パスワードを変更した日時が記録してある
    password_changed_at < Settings.config.user.password_expiry_period.ago
  end
end
```

考えていたことができるようになりました！ :tada: 

今回作成した `Settings` クラスはご覧の通りただの Ruby のクラスなので、 Yaml + ERB では定義できなかった設定も定義できるようになります。
例えば、 lambda です。

```yaml
user:
  after_signed_in_logic: <%= -> { redirect_to root_path } %>
```

```ruby
# これは動かない
Settings.user.after_signed_in_logic.call
```

```ruby
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の表現力に不満を覚えている方は、以上の方法をお試しください。