こんにちは!kossyです!
今回はcofig_forメソッドでカスタム値を設定する方法について、ブログに残してみたいと思います。
使い方
# config/environments/development.rb Rails.application.configure do config.custom_value = config_for(:custom_value) end
上記のように設定を行なった場合、config/custom_value.yml を見に行って、その定義値を Rails.application.config.custom_value で参照することができます。
# config/custom_value.yml default: &default status: custom development: <<: *default test: <<: *default production: <<: *default
ではコンソールで試してみます。
# In Console $ Rails.application.config.custom_value.status => "custom"
問題なくymlの値を呼び出すことができました。
OSSでの使われ方を見てみる
Railsを用いているOSSではどのように使われている見てみました。
# frozen_string_literal: true require 'health_cards' Rails.application.configure do config.smart = config_for('well-known') config.metadata = config_for('metadata') config.operation = config_for('operation') config.hc_key_path = ENV['KEY_PATH'] FileUtils.mkdir_p(File.dirname(ENV['KEY_PATH'])) kp = HealthCards::PrivateKey.load_from_or_create_from_file(config.hc_key_path) config.hc_key = kp config.issuer = HealthCards::Issuer.new(url: ENV['HOST'], key: config.hc_key) config.auth_code = ENV['AUTH_CODE'] config.client_id = ENV['CLIENT_ID'] end
どうやらシンボルでも文字列でもconfig/配下のymlファイルを探しに行くようですね。
上記のアプリケーションでは、APIのエンドポイントや外部ライブラリのバージョンや返り値のフォーマット?をymlファイルに切り出して管理しているようでした。
config_for メソッドのソースコードを見てみる
まずはソースコードの定義位置を確認してみます。
# In Console $ Rails.application.method(:config_for).source_location => [".../lib/rails/application.rb", 218]
こちらでした。
def config_for(name, env: Rails.env) yaml = name.is_a?(Pathname) ? name : Pathname.new("#{paths["config"].existent.first}/#{name}.yml") if yaml.exist? require "erb" all_configs = ActiveSupport::ConfigurationFile.parse(yaml).deep_symbolize_keys config, shared = all_configs[env.to_sym], all_configs[:shared] if shared config = {} if config.nil? && shared.is_a?(Hash) if config.is_a?(Hash) && shared.is_a?(Hash) config = shared.deep_merge(config) elsif config.nil? config = shared end end if config.is_a?(Hash) config = ActiveSupport::OrderedOptions.new.update(config) end config else raise "Could not load configuration. No such file - #{yaml}" end end
一行ずつ見てみましょう。
yaml = name.is_a?(Pathname) ? name : Pathname.new("#{paths["config"].existent.first}/#{name}.yml")
引数のnameがPathnameクラスのインスタンスであれば、nameをそのまま返し、
なければnameと同名のymlファイルをconfigディレクトリ配下から見つけてPathnameインスタンスにして返却するようになっていました。
なので、symbol or string or Pathname でもymlファイルを見つけられるみたいですね。
# 以下3つはどの書き方でもymlファイルを読み込むことができる config_for('custom_value') config_for(:custom_value) config_for(Pathname.new('config/custom_value.yml'))
もしymlファイルが見つからなければ例外が上がりますね。
if yaml.exist? # 省略 else raise "Could not load configuration. No such file - #{yaml}" end $ Rails.application.config_for(Pathname.new('config/custom_value.yml')) => RuntimeError (Could not load configuration. No such file - config/custom_value.yml)
ymlファイルが存在する場合のコードを読んでみます。
require "erb" all_configs = ActiveSupport::ConfigurationFile.parse(yaml).deep_symbolize_keys config, shared = all_configs[env.to_sym], all_configs[:shared]
こちらはコンソールで試してみますか。
$ all_configs = ActiveSupport::ConfigurationFile.parse(yaml).deep_symbolize_keys => {:default=> {:status=>"custom_value"}, :development=> {:status=>"custom_value"}, :test=> {:status=>"custom_value"}, :production=> {:status=>"custom_value"}, } $ config, shared = all_configs[env.to_sym], all_configs[:shared] => [{:status=>"custom_value"}, nil]
ymlで定義した値をシンボル化して、Rails.envと合致するシンボルを持つ値をconfigとして変数に格納し、
sharedというシンボルの値があればそちらも変数にしていました。(私の環境ではsharedは設定していないので nil が返っています)
もしsharedが存在していれば、という処理も記載されていましたが、今回のコードリーディングのスコープからは除外します。
次は ActiveSupport::OrderedOptions 周りを読んでみます。
if config.is_a?(Hash) config = ActiveSupport::OrderedOptions.new.update(config) end
ドキュメントを見てみます。
OrderedOptions inherits from Hash and provides dynamic accessor methods.
With a Hash, key-value pairs are typically managed like this:
h = {} h[:boy] = 'John' h[:girl] = 'Mary' h[:boy] # => 'John' h[:girl] # => 'Mary' h[:dog] # => nilUsing OrderedOptions, the above code can be written as:
h = ActiveSupport::OrderedOptions.new h.boy = 'John' h.girl = 'Mary' h.boy # => 'John' h.girl # => 'Mary' h.dog # => nil出典: https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html
使い方は理解できました。便利ですね。
updateメソッドはどうやら定義されていないようなので、method_missingメソッドで捕捉されているものと思われます。
config = ActiveSupport::OrderedOptions.new.update(config)
の実行結果は以下です。
$ config = ActiveSupport::OrderedOptions.new.update(config) => { :status=>"custom_value"}
結局config_forメソッドの返り値は configが返っていました。
まとめ
settingslogicやfigaro等でアプリケーションの設定値を管理するのがメジャーかと思っていたんですが、config_forでも全然良さそうですね。