2018-01-05

figaroコードリーディング

設定ファイルから環境変数をセットするfigaroのコードリーディングをしました。バージョンは1.1.1です

まずRailtieのconfig.before_configurationのフックでFigaro.loadを呼び出します

module Figaro
  module Rails
    class Railtie < ::Rails::Railtie
      config.before_configuration do
        Figaro.load
      end
    end
  end
end

Figaro.loadではFigaro::Application#loadを呼び出します

module Figaro
  extend self

  attr_writer :adapter, :application

  def env
    Figaro::ENV
  end

  def adapter
    @adapter ||= Figaro::Application
  end

  def application
    @application ||= adapter.new
  end

  def load
    application.load
  end

  def require_keys(*keys)
    missing_keys = keys.flatten - ::ENV.keys
    raise MissingKeys.new(missing_keys) if missing_keys.any?
  end
end

initializerファイルで Figaro.require_keys('hoge', ...)を呼び出すことで設定ファイルに含まれていないキーがあればエラーを返すようにできますが、パラメータのキーから::ENV.keysを引いてパラメータが残っていればエラーをraiseしています。

Figaro::Application#loadは設定ファイルを読み込んで各設定に応じて環境変数をセットします。

module Figaro
  class Application

    def load
      each do |key, value|
        skip?(key) ? key_skipped!(key) : set(key, value)
      end
    end

    def each(&block)
      configuration.each(&block)
    end

    private

    def set(key, value)
      non_string_configuration!(key) unless key.is_a?(String)
      non_string_configuration!(value) unless value.is_a?(String) || value.nil?

      ::ENV[key.to_s] = value.nil? ? nil : value.to_s
      ::ENV[FIGARO_ENV_PREFIX + key.to_s] = value.nil? ? nil: value.to_s
    end

    def skip?(key)
      ::ENV.key?(key.to_s) && !::ENV.key?(FIGARO_ENV_PREFIX + key.to_s)
    end

    def non_string_configuration!(value)
      warn "WARNING: Use strings for Figaro configuration. #{value.inspect} was converted to #{value.to_s.inspect}."
    end

    def key_skipped!(key)
      warn "WARNING: Skipping key #{key.inspect}. Already set in ENV."
    end
  end
end

環境変数は指定のキー値とFIGARO_ENV_PREFIX + キー値にセットされます。ロード前に既に環境変数をセットしている場合はskip?がtrueを返し環境変数を上書きしません。またフィガロで設定した環境変数に関してはFIGARO_ENV_PREFIXの方にも環境変数がセットされているのでskip?がfalseを返して再ロードされます。また、設定キーと値がStringではないときはWARNINGを表示します。

eachはconfiguration.eachにブロックを渡します

module Figaro
  class Application

    def initialize(options = {})
      @options = options.inject({}) { |m, (k, v)| m[k.to_sym] = v; m }
    end

    def path
      @options.fetch(:path) { default_path }.to_s
    end

    def path=(path)
      @options[:path] = path
    end

    def environment
      environment = @options.fetch(:environment) { default_environment }
      environment.nil? ? nil : environment.to_s
    end

    def environment=(environment)
      @options[:environment] = environment
    end

    def configuration
      global_configuration.merge(environment_configuration)
    end

    private

    def raw_configuration
      (@parsed ||= Hash.new { |hash, path| hash[path] = parse(path) })[path]
    end

    def parse(path)
      File.exist?(path) && YAML.load(ERB.new(File.read(path)).result) || {}
    end

    def global_configuration
      raw_configuration.reject { |_, value| value.is_a?(Hash) }
    end

    def environment_configuration
      raw_configuration[environment] || {}
    end
  end
end

configurationでYAMLファイルをパースして取得した設定ファイルからネストしている(=値がハッシュのもの。環境ごとの設定も含む)設定と、環境ごとの設定をマージします。このハッシュ値に対してeachで回して環境変数をセットしています。

figaro installのCLIの処理はThorを使っています。

require "thor/group"

module Figaro
  class CLI < Thor
    class Install < Thor::Group
      include Thor::Actions

      class_option "path",
        aliases: ["-p"],
        default: "config/application.yml",
        desc: "Specify a configuration file path"

      def self.source_root
        File.expand_path("../install", __FILE__)
      end

      def create_configuration
        copy_file("application.yml", options[:path])
      end

      def ignore_configuration
        if File.exists?(".gitignore")
          append_to_file(".gitignore", <<-EOF)

# Ignore application configuration
/#{options[:path]}
EOF
        end
      end
    end
  end
end
このエントリーをはてなブックマークに追加