Railsの設定定数をYAMLで一元管理する(EasySettings)

# はじめに
Railsの設定定数をYAMLファイルで管理するのに[EasySettings](https://github.com/nownabe/easy_settings)というGemを使う方法です。

# 使い方
`Gemfile`に

```rb
gem "easy_settings"
```

を追加して、`config/settings.yml`に次のようなYAMLファイルを作ります。

```yaml
default: &default
  key1: value1

development:
  <<: *default
  key2: value2.d

test:
  <<: *default
  key2: value2.t

production:
  <<: *default
  key2: value2.p
```

あとはアプリのどこからでも次のようにして値を参照できます。

```rb
EasySettings.key2 #=> "value2.d"
```

# 他の定数管理
他にもいろいろあります。

* [Settingslogic](https://github.com/binarylogic/settingslogic)
* [Config](https://github.com/railsconfig/config)
* [Chamber](https://github.com/thekompanee/chamber)

## Settingslogic
Settingslogicはこの中でも最もシンプルで1つのYAMLで定数を管理します。
機能といえばERBが処理できることとファイル内で名前空間が設定できることぐらいです。

`Settingslogic`クラスを継承したクラスを定義する必要があります。

EasySettingsは元々Settingslogicの代替として作られたためSettingslogicに似ています。

## Config
元々はrails_configというGemだったようです。

複数のYAMLファイルに継承関係を持たせて定数を定義できます。

## Chamber
Chamber is the auto-encrypting, extremely organizable, Heroku-loving, CLI-having, non-extra-repo-needing, non-Rails-specific-ing, CI-serving configuration management library.

だそうです。ドキュメントも多くてやりたいことに対して大げさすぎる気がするのであんまり調べてません。

# 特徴
といっても機能はそんなにありません。EasySettingsはただのYAMLを読み込む[Hashie::Mash](https://github.com/intridea/hashie)です。
Settingslogicの不満点を解消するために作ったのでSettingslogicと比較しながら説明します。

## メソッド形式、ハッシュ形式(String、Symbol)で参照できる
これはSettingslogicでもできますね。

例えば設定ファイルがこんな感じだと、

```yaml
development:
  key1:
    subkey1: value1
    subkey2: value2
```

こんな感じで参照できます。

```rb
>> EasySettings.key1
=> #<EasySettings subkey1="value1" subkey2="value2">
>> EasySettings[:key1]
=> #<EasySettings subkey1="value1" subkey2="value2">
>> EasySettings["key1"]
=> #<EasySettings subkey1="value1" subkey2="value2">
>> EasySettings.key1.to_h
=> {"subkey1"=>"value1", "subkey2"=>"value2"}

>> EasySettings.key1.subkey1
=> "value1"
>> EasySettings.key1[:subkey1]
=> "value1"
>> EasySettings.key1["subkey1"]
=> "value1"
```

ハッシュは`EasySettings`のオブジェクトになりますが`to_h`で`Hash`オブジェクトに変換できます。

## 初期化いらず
Settingslogicはどこかで次のようにクラスを定義する必要があります。

```rb
class Settings < Settingslogic
  source Rails.root.join("config/settings.yml").to_s
  namespace Rails.env
end
```

EasySettingsは不要です。あえていうなら`Gemfile`に1行必要なぐらい。

## 未定義のメソッド形式で例外が発生しない
Settingslogicでは定義されていないキーをメソッド形式で参照しようとすると例外が発生します。

```rb
# Settingslogicは例外発生
>> Settings.undefined_key
Settingslogic::MissingSetting: Missing setting 'undefined_key' in /tmp/app/config/settings.yml

# EasySettingsはnilになる
>> EasySettings.undefined_key
=> nil
```

EasySettingsの場合メソッド形式で代入もできます。

```rb
# Settingslogicだとエラー
>> Settings.existed_key = "new value"
Settingslogic::MissingSetting: Missing setting 'existed_key=' in /tmp/app/config/settings.yml

# EasySettingsはOK
>> EasySettings.existed_key = "new value"
=> "hoge"
```

## デフォルト値が設定できる
デフォルト値が設定できます。機能的には一番強力かもしれません。

例えばこういうときに、

```rb
class User < ActiveRecord::Base
  paginates_per(Settings[:per_page] || 5)
end
```

EasySettingsではこのように書けます。

```rb

class User < ActiveRecord::Base
  paginates_per EasySettings.per_page(5)
end
```

さらに、EasySettingsでは末尾に`!`をつけるとキーが定義されていない場合だけハッシュとして初期化します。

```rb
>> EasySettings.new_key
=> nil
>> EasySettings.has_key?(:new_key)
=> false
>> EasySettings.new_key!
=> #<EasySettings>
>> EasySettings.has_key.to_h
=> {}
>> EasySettings.has_key?(:new_key)
=> true
```

`EasySettings.new_key({})`と`EasySettings.new_key!`は同じ意味です。

これは設定に入れ子ハッシュがある場合に威力を発揮します。
例えば`paginates_per`をYAMLでは次のように書きたい時、

```yaml
development:
  pagination:
    per_page: 5
```

これを次のように書いてしまうと、`pagination`がnilの場合エラーになります。

```rb
pagenates_per(Settings[:pagination][:per_page] || 5)
```

なので、最低でもYAMLファイルに空ハッシュを書くようにするか、

```yaml
development:
  pagination: {}
```

アプリ側をこのようにする必要があります。

```rb
paginates_per((Settings[:pagination] || {})[:per_page] || 5)
```

やってられませんね。EasySettingsならこうです。

```rb
pagenates_per EasySettings.pagination!.per_page(5)
```

## 別名が使える
`EasySettings`という名前は長いです。短くしたいという時はこうします。

```rb
Config = EasySettings
```

`config/initializers/easy_settings.rb`とかに書いておけばいいと思います。

## config/settings.yml以外
`config/settings.yml`以外を使いたい場合、`EasySettings.source_file=`メソッドで設定ファイルを指定します。
ついでに紹介しておくと、`EasySettings.namespace=`とか`EasySettings.reload!`とかいうのもあります。

`config/initializers/easy_settings.rb`に次のように書くと環境ごとにYAMLファイルを切り替えられます。

```rb:config/initializers/easy_settings.rb
EasySettings.source_file = Rails.root.join("config/settings/#{Rails.env}.yml")
EasySettings.namespace = nil
EasySettings.reload!

Config = EasySettings
```

## ERBが使える
ERBを処理できます。

```yaml
default: &default
  aws_id: <%= ENV["AWS_ID"] %>
  aws_secret: <%= ENV["AWS_SECRET"] %>
```

みたいな感じです。[dotenv](https://github.com/bkeepers/dotenv)と組み合わせられます。

## Rails以外でも使える
元々はRails以外で使うために作りました。
Rails以外で使うとき、`Dir.pwd`からみて`settings.yml`か`config/settings.yml`にファイルが存在すればそれを読むようになっています。
それ以外に設定ファイルがあるときは`EasySettings.source_file=`を使って下さい。

# その他Settingslogicのおかしな挙動
ついでなので、他にもレアケースですがSettingslogicにおかしな挙動があってハマりました。

例えばデフォルト値を設定してその後も使い回す例です。

```rb
>> (Settings[:pagination] ||= {})[:per_page] ||= 5
=> 5
>> Settings[:pagination][:per_page]
=> nil
```

あと入れ子になるハッシュで同じキーを突っ込んだ場合。

```rb
>> Settings[:foo] = {foo: "bar"}
=> {:foo=>"bar"}
>> Settings.foo
=> {:foo=>"bar"}
>> Settings.foo.foo
Settingslogic::MissingSetting: Missing setting 'foo' in /tmp/app/config/settings.yml
```

# おわりに
最後はSettingslogicディスみたいになってしまいましたが、基本はSettingslogicのシンプルなところをリスペクトして作ったGemです。
設定管理のお供にいかがでしょうか:smile::sparkles: