Railsでconfig/database.ymlを使わずURL文字列でDB接続したい

れこです。今回はRailsネタです。
作ったアプリをHerokuにデプロイするときに、各種アドオンで

XXX_URL=pg://xxx:yyy@zzz/hoge

のような文字列を環境変数で指定して使うというパターンが有ると思うのですが、
config/database.ymlに一切触らず に、この文字列でDB接続したい…

と思ったのでRailsのソースやドキュメントを読み漁ってみました。

結論

先に結論を書くと、何もせずともDATABASE_URLという名前の環境変数を定義すればOKでした。
config/database.ymlを書き換えたり消したりする必要はなく、環境変数が優先されます。

以下はこの結論に至った経緯とおまけです。

ドキュメントを読んでみる

まずは何事にも公式ドキュメント。

You can connect to the database by setting an environment variable ENV['DATABASE_URL'] or by using a configuration file called config/database.yml.
3.14 Configuring a Database

とあるように、DATABASE_URLという環境変数が使用可能らしいということがわかりました。
ここで気になったのは、 config/database.ymlと環境変数どちらが優先されるのか
ドキュメントだけでは解消しないので詳しく追ってみます。

記事を探してみる

試してみた系記事ないかなーと探してみたらこんな記事が。

John Griffin: Rails 4.1: Database URLs

結局どっちなのかわからん。

Railsのソースを読んでみる

まずはそれっぽいテストがないか確認。
GithubのRailsのソースを検索してみた

このテストこのファイルのテストをみる限り、config/database.ymlよりも環境変数が優先されそうな気がする。

ソースを読んでみるとそれっぽい(?)記述が。

# Returns fully resolved connection hashes.
# Merges connection information from `ENV['DATABASE_URL']` if available.
def resolve
  ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
end

private
  def config
    @raw_config.dup.tap do |cfg|
      if url = ENV["DATABASE_URL"]
        cfg[@env] ||= {}
        cfg[@env]["url"] ||= url
      end
    end
  end

該当ファイルはrails/activerecord/lib/active_record/connection_handling.rb
もしDATABASE_URLという環境変数があれば設定ファイルでいうところの

development: # ※ここは実行時の環境による
  url: <%%= ENV['DATABASE_URL'] %>

に相当する処理を内部でやってくれる模様。
ただ、設定ファイルを見てみると

# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%%= ENV['DATABASE_URL'] %>
#

設定ファイルを書き換えるようコメントが書かれていたり、いまいちどっちを信じればよいのかわからない。

実験してみる

英語とRuby力が足らず決定打が見つからなかったので試してみました。
参考記事のコマンドをお借りして試してみます。

  • config/database.yml上ではSQLiteで接続するよう設定(gem等も入れとく)
  • ローカルにPostgreSQLは入っていない

という状態で下記コマンドを実行して、ポスグレで接続しようとすれば接続エラーになるはず。
もしconfig/database.ymlが優先されるならSQLiteの接続になるので正しく接続できてしまう

# 接続エラー(ポスグレで接続しようとしている)
DATABASE_URL=postgresql://localhost/app_development bundle exec rails s

# 接続できた(config/database.ymlは間違ってない)
bundle exec rails s

ということで試してみた結果、
config/database.ymlが存在しようと、環境変数が指定されていればそちらが優先される
ということがわかりました。

まとめ

この形式は環境変数1個で事足りるし、.env等に逃がせば接続情報をGit管理しなくて良くなるので、とても好きです。
データベースにかぎらず、RedisやSMTPサーバなんかもこの書式で表現できます。
各DBドライバによって必要な設定のキー名が変わるとか面倒くさくて覚えたくないので、接続系の処理はこの書き方に統一されてしまえばいいのに、なんて思ってます。

RailsアプリはHerokuにデプロイされることが多いからなのか、デフォルトで対応してくれていて助かりました。
さすがRails。といったところなんでしょうか。

ちなみにこの書き方ってなんて名称なんでしょう。
私はurl stringとかconnection stringなんて検索をしているのですが、正しい名前があれば知りたい。。。

おまけ:多言語の対応状況

私がよく触る言語たちの対応状況を調べてみました。

PHP

CakePHPだと対応している模様。
http://book.cakephp.org/3.0/en/development/configuration.html#environment-variables

Laravelだと、この形式でDB接続するのに対応してないので、
この記事のように設定ファイルにPHPの処理を書き加えてLaravelの設定に互換性があるようにパース処理を自前で実装しなきゃいけなかったりクソ面倒です。

FuelPHPも探してみたものの、それらしい記事が見つからず。

Nodejs

新進気鋭のフレームワークAdonisや個人的に好きなO/Rマッパーのobjectionが内部で使用しているknexはもちろん対応しています。

Nodeはフルスタックなフレームワークが少ない(流行ってない)ので、内部的にknexを使ってればOKくらいの認識で居ます

Go

Goは別格です。
あらゆるDBのドライバの根っこになっているdatabase/sqlパッケージがデフォルトで対応しています。
なのでdatabase/sqlパッケージを使わずにオレオレ実装でもしていない限り対応してます。

昔に作ったGoのデモアプリでもこの方式を利用しています。