Rails6 のちょい足しな新機能を試す112（database.yml warning編）

# はじめに

Rails 6 に追加された新機能を試す第112段。 今回は、`database.yml warning` 編です。
Rails 6 では、複数データベースに対応していますが、 `database.yml` ファイルで複雑なERBが使われていると警告が出るようになっています。

Ruby 2.6.5, Rails 6.0.1 で確認しました。 (Rails 6.0.0 でこの修正が入ったようです。)

```shell
$ rails --version
Rails 6.0.1
```

今回は、警告を出すために、 環境変数 DB によって、 MySQLデータベースかPostgreSQLデータベースにアクセスを切り変えるということで試してみたいと思います。
複数データベースの機能は使いません。

# Rails プロジェクトを作成する

```shell
$ rails new rails_sandbox
$ cd rails_sandbox
```

# Gemfile に mysql2 と pg を追加する。

MySQL と PostgreSQL どちらでも接続できるように、 Gemfile に mysql2 と pg を追加します。

```ruby:Gemfile
gem 'pg', '>= 0.18', '< 2.0'
gem 'mysql2'
```
bundle install を実行します。

```shell
$ bundle install
```

# database.yml ファイルを編集する

`database.yml` を変更します。

MySQL 用の接続設定と PostgreSQL 用の接続設定を追加します。

環境変数によって、接続を切り変えられるようにします。

ここで、ポイントは、 `<<: *<%= ENV.fetch("DB") { 'postgresql' } %>` となっている部分です。
この記述によって警告が出るようになります。

```yaml:config/database.yml
mysql: &mysql
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  host: db_mysql

postgresql: &postgresql
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db_postgresql
  user: <%= ENV.fetch("POSTGRES_USER") %>
  password: <%= ENV.fetch("POSTGRES_PASSWORD") %>

development:
  <<: *<%= ENV.fetch("DB") { 'postgresql' } %>
  database: app_development
  ...

test:
  <<: *<%= ENV.fetch("DB") { 'postgresql' } %>
  database: app_test
  ...

production:
  <<: *<%= ENV.fetch("DB") { 'postgresql' } %>
  database: app_production
  ...
```

# スクリプトを作成する

確認のためだけなのですが、 database.yml を ERB でパースして結果を出力するスクリプトを書いておきます。

```ruby:scripts/parse_database_yml.rb
yml = ERB.new(File.read(Rails.root.join('config/database.yml')))
print yml.result
```

# スクリプトを実行する

スクリプトを実行します。特にエラーもなくパースできています。

`development:` の次の行が、 `<<: *postgresql` と処理できていることに注意してください。

```shell
$ bin/rails runner scripts/parse_database_yml.rb
Running via Spring preloader in process 104
mysql: &mysql
  adapter: mysql2
  ...

postgresql: &postgresql
  adapter: postgresql
  ...

development:
  <<: *postgresql
  database: app_development
  ...
```

# db:create を実行する

`db:create` を実行します。
`Rails couldn't infer whether you are using multiple databases from your database.yml ... please simplify your ERB.` と警告が出ます

```shell
$ bin/rails db:create
Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB.
Created database 'app_development'
Created database 'app_test'
```

# なぜ警告が出るのか

複数データベースに対応するために、 Rails は、 boot 完了する前に、 `database.yml` を読んで、task を生成しているそうです。task 生成する時点では、 ERB を使っておらず DummyCompiler を使って解析しており、この DummyCompiler では処理できない場合に警告を出すようにしたということらしいです。

# どうすれば良いのか

結論としては、ERB の部分を警告が出ないように書き変えれば良いということになります。
では、一般的にどう書き変えれば良いのかは、筆者にもわかっていません。

# ワーニングを出ないように修正する

今回の場合は、以下のように書き変えることで警告が出ないようにすることができました。
`default` を追加して `if` 文使って切り変えるようにして、 `development` などの環境では、 `<< *default` を使って ERB を使わないように修正しました。

```yaml:config/database.yml
mysql: &mysql
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  host: db_mysql

postgresql: &postgresql
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db_postgresql
  user: <%= ENV.fetch("POSTGRES_USER") %>
  password: <%= ENV.fetch("POSTGRES_PASSWORD") %>

default: &default
  <% database = ENV.fetch("DB"){ 'postgresql' } %>
  <% if database == 'postgresql' %>
  <<: *postgresql
  <% else %>
  <<: *mysql
  <% end %>

development:
  <<: *default
  database: app_development
  ...

test:
  <<: *default
  database: app_test
  ...

production:
  <<: *default
  database: app_production
  ...
```

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try112_warn_database_yml - 警告が出る方
https://github.com/suketa/rails_sandbox/tree/try112_warn_database_yml_fix - 警告が出ないように修正した方

# 参考情報

* [Warn if we can't read the yaml to create database tasks](https://github.com/rails/rails/pull/36560)
* [Load YAML for rake tasks without parsing ERB](https://github.com/rails/rails/pull/35497)