上京エンジニアの葛藤

都会に染まる日々

dotenv-rails を流し読み

前回の記事からかなり期間が空いてしまいました。

dotenv の Gem のコードリーディングをしたので、まとめておきたいと思います。 github.com

dotenvとは

説明不要だと思いますが、環境変数をファイル管理することができる Gem で rails 以外の WAF や素のRuby でも使うことができます。

コードリーディング

rails で使用したケースを追っていきたいと思います。

まず config.before_configurationRails::Application から定数を継承した直後に実行されるので lib/dotenv/rails.rb の以下のコードが呼ばれます。

config.before_configuration { load }

load メソッドは同ファイル内の以下のメソッドです。

def load
  Dotenv.load(*dotenv_files)
end

dotenv_files は private メソッドが定義されていて env ファイルの path を取得します。

def dotenv_files
  [
    root.join(".env.#{Rails.env}.local"),
    (root.join(".env.local") unless Rails.env.test?),
    root.join(".env.#{Rails.env}"),
    root.join(".env")
  ].compact
end

Dotenv.loadlib/dotenv.rb で定義されているメソッドで、初めに with メソッドが呼ばれます。

def load(*filenames)
  with(*filenames) do |f|
    ignoring_nonexistent_files do
      env = Environment.new(f, true)
      instrument("dotenv.load", env: env) { env.apply }
    end
  end
end

以下が with メソッドです。

def with(*filenames)
  filenames << ".env" if filenames.empty?

  filenames.reduce({}) do |hash, filename|
    hash.merge!(yield(File.expand_path(filename)) || {})
  end
end

with メソッドでは filename を取得して、yield で以下のブロックを処理します。 ignoring_nonexistent_files は対象の file ポインタが存在しなければ skip する処理が書かれています。

    ignoring_nonexistent_files do
      env = Environment.new(f, true)
      instrument("dotenv.load", env: env) { env.apply }
    end

file ポインタが存在すれば Environment.new(f, true) で file 内の文字列を hash に parse して返却しています。
実際の parse処理は lib/dotenv/parser.rb で行なっています。

そして最後に instrument("dotenv.load", env: env) { env.apply }環境変数に読み込まれます。

def apply
  each { |k, v| ENV[k] ||= v }
end

この時 ActiveSupport::Notifications.instrument でイベントが登録されます。

以上です。

おまけというか疑問

lib/dotenv/rails.rb で以下のようにリッスンされているので .env ファイルが変更されたら Spring が再読み込みする認識なのですが、更新されずでして理解が間違っているんですかね。。

begin
  require "spring/commands"
  ActiveSupport::Notifications.subscribe(/^dotenv/) do |*args|
    event = ActiveSupport::Notifications::Event.new(*args)
    Spring.watch event.payload[:env].filename if Rails.application
  end
rescue LoadError, ArgumentError
  # Spring is not available
end

分かる方いましたら教えて下さい。