RubyのENV.fetch()で空文字列の時もデフォルト値を使うようにする

Rubyには、指定された環境変数の値を読み込み、存在しなければデフォルト値を返すENV.fetch()というメソッドがある。Railsなどでは設定ファイルで環境変数から値を読み込むのによく使う。

config/database.yml:

default: &default
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

しかし、たとえば指定された環境変数が空文字列だったりすると、デフォルト値ではなく空文字列が返されてしまう。

HOGE='' ruby -e 'puts ENV.fetch("HOGE"){1}' #=> 

これは direnv の .envrc や Docker Compose の compose.yaml環境変数を指定していたりすると起きがちである。

.envrc:

export HOGE=

compose.yaml:

services:
  app:
    environment:
      HOGE: ${HOGE}

なので、空文字列の時もデフォルト値が参照されるようにENVをモンキーパッチして、新たにENV.fetch2()というENV.fetch()とほぼ同様の動作をするメソッドを実装する。

lib/env.rb:

class << ENV
  # 基本的な動作は`ENV.fetch()`と同様だが、空文字列のときもデフォルト値を返すようにする
  def fetch2(*args, &block)
    key, default = args
    value = fetch(*args, &block)
    return value if value != ''

    raise KeyError, format('key not found: "%s"', key) if args.size == 1 && !block
    warn('block supersedes default value argument', uplevel: 1) if args.size == 2 && block
    block ? yield(key) : default
  end
end
HOGE='' ruby -r ./lib/env.rb -e 'puts ENV.fetch2("HOGE"){1}' #=> 1

これで問題を解決できた。なお、空文字列の時はデフォルト値ではなく空文字列が返ってきてほしい時があるので、ENV.fetch() をオーバーライドするのはやめた方がよい。

Gist: https://gist.github.com/mrk21/3c6f29aa91659dcaf45eefba11f866a3

環境