複数環境に対応したRailsアプリケーションの設定方法

Railsには `RAILS_ENV`と呼ばれる環境を切り替えるための環境変数が用意されています.　最初はこれだけでも十分ですが開発が進むと環境毎に切り替えるプロジェクトがよくあるので備忘録として自分がよくやる対応をまとめてみました.

## config_for
`config/database.yml`の様に環境毎で切り替える設定は[config_for]というメソッドがあるのでこれを使うとスッキリ書けます.

Rails6 であれば、 `shared` でマージされるのでデフォルト値が手軽に設定できます.
Rails5を使ってるのであれば、YAMLの[Anchors and Aliases]を使ってマージすると便利です.

```yaml
# config/foo.yml
---
shared:
  key3: 'shared value'
  
base: &base
  env: <%= Rails.env %>
  key1: "<%= ENV['MY_APP_KEY1'] || 'default' %>"
  key2: 'hello %{user_cd}'

production:
  <<: *base
```

```ruby
# config/application.rb
module MyApp
  class Application < Rails::Application
    config.foo = config_for(:foo).deep_symbolize_keys
  end
end

### ruby
Rails.configuration.foo[:key1]
# => default

Rails.configuration.foo[:key2].gsub('%{user_cd}', 'dummy1')
# => hello dummy1

Rails.configuration.foo[:key3]
# => shared value # if Rails6
# => nil # if Rails5
```

## 設定より規約を活用して設定を減らす
普通に[config_for]を使ってるとYAMLの設定ファイルの運用が進むにつれて数が増えてきます.
もしくは、既存のシステムがあるとすれば既に膨大な設定になってるかと思います.
そこで[設定より規約]を使って最初から規約寄りにしておいた方が良いでしょう.

YAMLでは値に`key1: null`とあると`NULL`値(Rubyだと`nil`)になります. `key1: 'null'` の様にクォートで囲むと文字列の`"null"`になります.

これを利用して以下の様にしていきます.

```yaml
# config/foo.yml
---
production:
  key1: <%= "'#{ENV['MY_APP_KEY1']}'" || 'null' %>
```

```rb
# config/application.rb
module MyApp
  class Application < Rails::Application
    config.foo = config_for(:foo).deep_symbolize_keys
  end
end

### something ruby implements
def config_foo_key1
  "${Rails.configuration.foo[:key1] || 'default'}"
end
```

`config_foo_key1` の様に実装側にデフォルト値を用意することで、YAMLの設定ファイル自体がコンパクトになります. `base`や`shared` にデフォルト値を寄せても良いと思います.

このケースの良いところは全ての環境が規約だけで運用できる様になった時、YAMLファイルが不要になるところにあります. 既存のハードコードされた設定があるのであれば、これらの方法を使って減らしていくと良いと思います.


- `bundle exec rails runner -e development 'puts config_foo_key1`
  - `'default'`
- `MY_APP_KEY1=foo bundle exec rails runner -e production 'puts config_foo_key1`
  - `'foo'`

## 環境変数にも規約をつける

アプリで固有で使う環境変数にも `MY_APP_KEY1` の様に接頭辞`MY_APP_`の様に規約をつけると良いでしょう.
grepした時に探すのも楽になるし、直感的にアプリ固有の環境変数だということがわかります.

## 環境毎にユニークな名前空間を使う
ここでの名前空間は以下の様なものを指しています.

- `AWS S3`のバケット名
- ディレクトリのパス
- データベース名
- 全文検索のインデックス名
- キャッシュのキー名

通常、プロダクション環境だと、複数環境が相乗りになることは無いはずですが、デモ環境や開発環境は複数環境が相乗りになることがあります.

### 含める変数
Railsでは 環境変数 `RAILS_ENV` があるし、[parallel_tests] では `ENV['TEST_ENV_NUMBER']` を使って並列に動作できるようにしています. さらにアプリ固有の環境変数もある場合もあります. 

これらを含めた名前空間にすることで開発環境の様に `development`と`test`が同じPCで動かせる様になります.

例えばS3のオブジェクトのキーを考えてみましょう. 環境変数とデモ環境などの実際に動作する物理的な環境であれば `MY_APP_ENV` の値には `demo`,`prd`,`stg`, `qa` そして `dev` の様な値が入るとします.

名前空間は `"#{Rails.env}#{ENV['TEST_ENV_NUMBER']}/${ENV['MY_APP_ENV']}"` の様な規約にしておきます.

これらの変数の組み合わせが展開されると以下の様になります. 名前空間が重なってないため柔軟に同じサーバや開発環境内に同居できる様になります.

| RAILS_ENV   | TEST_ENV_NUMBER | MY_APP_ENV | name space             |
|:----------- |:--------------- |:---------- |:----------------- |
| development |                 | dev        | `development/dev` |
| production  |                 | demo       | `production/demo` |
| production  |                 | prd        | `production/prd`  |
| production  |                 | stg        | `production/stg`  |
| production  |                 | qa         | `production/qa`   |
| test        |                 | dev        | `test/dev`        |
| test        | 2               | dev        | `test2/dev`       |





###### tags: `Rails`

[設定より規約]: https://ja.wikipedia.org/wiki/%E8%A8%AD%E5%AE%9A%E3%82%88%E3%82%8A%E8%A6%8F%E7%B4%84
[config_for]: https://api.rubyonrails.org/classes/Rails/Application.html#method-i-config_for
[Anchors and Aliases]: https://yaml.org/spec/1.2/spec.html#id2765878
[parallel_tests]: https://github.com/grosser/parallel_tests