12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElixirのConfigを使ってみる

Posted at

configの基本的な使い方

configファイルに, Mix.Configモジュールのconfigマクロを使い

config(application_name, key, value)

と値を登録し, 使用したいときはApplicationモジュールのget_env/2関数等を使い

value = Application.get_env(application_name, key)

という形で取得する

使用例を見てみる

手元のconfigファイルには, DBとの接続設定として, 以下の様な記述があります.

config/dev.exs
# Configure your database                                                                                                                                                                        
config :phoenix_sample, PhoenixSample.Repo,
  adapter: Ecto.Adapters.MySQL,
  username: "phoenix",
  password: "****",
  database: "phoenix_sample_dev",
  hostname: "localhost",
  pool_size: 10

この記述は, 上記の呼び出しと随分違うものにみえるかもしれませんが,
Elixirの

  • 関数呼び出し時, 引数を囲む括弧は省略してもよい
  • 関数の引数としてキーワードリストを渡す場合は, カギ括弧を省略しても良い
    というルールを使って書いているだけなので, 実際は
# Configure your database                                                                                                                                                                        
config(:phoenix_sample, PhoenixSample.Repo, [adapter: Ecto.Adapters.MySQL, username: "phoenix", password: "****", database: "phoenix_sample_dev", hostname: "localhost", pool_size: 10])

と書かれているのと同じで, PhoenixSample.Repoというkeyにキーワードリストを対応させているわけです.

実際にこの設定を使っている部分を追ってみると, まずPhoenixSample.Repoの中でEcto.Repoがuseされ

lib/repo.ex
  use Ecto.Repo, otp_app: :phoenix_sample

Ecto.Repoの__using__マクロ(use時に呼び出されるマクロ)の中で, さらにEcto.Repo.Supervisorのparse_config関数を呼び出し

ecto/repo.ex
  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      @behaviour Ecto.Repo

      {otp_app, adapter, config} = Ecto.Repo.Supervisor.parse_config(__MODULE__, opts)

呼びだされたparse_config関数の中で, モジュール名とオプションリストに含まれているアプリケーション名を元に設定のキーワードリストを取得し

ecto/repo/supervisor.ex
  @doc """
  Parses the OTP configuration for compile time.
  """
  def parse_config(repo, opts) do
    otp_app = Keyword.fetch!(opts, :otp_app)
    config  = Application.get_env(otp_app, repo, [])
    adapter = opts[:adapter] || config[:adapter]

    unless adapter do
      raise ArgumentError, "missing :adapter configuration in " <>
                           "config #{inspect otp_app}, #{inspect repo}"
    end

    unless Code.ensure_loaded?(adapter) do
      raise ArgumentError, "adapter #{inspect adapter} was not compiled, " <>
                           "ensure it is correct and it is included as a project dependency"
    end

    {otp_app, adapter, config}
  end

parse_configの結果をPhoenixSample.Repoモジュールのattributeに設定します.

ecto/repo.ex
      {otp_app, adapter, config} = Ecto.Repo.Supervisor.parse_config(__MODULE__, opts)
      @otp_app otp_app
      @adapter adapter
      @config  config
      @before_compile adapter

そして, DBとの接続を開く際には, このattributeの値を使うことになるわけです.

この__using__マクロの引数を使って設定を読み取り, モジュールのattributeに値をセットするというテクニックは非常に便利そうですね.

微妙に気になるのは, 設定からadpterを読み取る部分が

    adapter = opts[:adapter] || config[:adapter]

となっている点.
このoptsは, Ecto.Repoモジュールをuseする際の引数のはずなのですが, ここで指定した
アダプタを設定ファイルで指定したアダプタより優先するのはどんな想定なんでしょうかね?

使ってみる

先日作成した, Memcachedアクセスモジュールの接続情報を設定ファイルに移動してみたいと思います.

まず, 設定ファイルにmemcachedへの接続情報を追加.

config/dev.exs
config :phoenix_sample, PhoenixSample.Memcached,
  host: 'localhost',
  port: 11211

そして, 子プロセス立ち時に, 接続情報を設定から取得するように変更.

lib/phoenix_sample/memcached.ex
#    arg = ['localhost', 11211]
    config = Application.get_env(:phoenix_sample, __MODULE__, [])
    arg = [config[:host], config[:port]]

動作を確認してみると...

[vagrant@vagrant-centos7 phoenix_sample]$ iex -S mix phoenix.server
Erlang/OTP 18 [erts-7.2] [source-e6dd627] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

...

iex(1)> [info] Creating interface #PID<0.451.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.454.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.457.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.460.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.463.0> to memcached on 'localhost':11211

23 Mar 09:10:37 - info: compiled 5 files into 2 files, copied 3 in 1.2 sec

nil
iex(2)> Application.get_all_env :phoenix_sample
[{PhoenixSample.Endpoint,
  [url: [host: "localhost"], root: "/home/vagrant/phoenix_sample",
   secret_key_base: "y6QJ70XCl3Vxy2GdAcDuT2fA6guGddfwhFz4jDYfm330ENhgzPSI6eM+jI0mCz9T",
   render_errors: [accepts: ["html", "json"]],
   pubsub: [name: PhoenixSample.PubSub, adapter: Phoenix.PubSub.PG2],
   http: [port: 4000], debug_errors: true, code_reloader: true,
   check_origin: false,
   watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin"]],
   live_reload: [patterns: [~r/priv\/static\/.*(js|css|png|jpeg|jpg|gif|svg)$/,
     ~r/priv\/gettext\/.*(po)$/, ~r/web\/views\/.*(ex)$/,
     ~r/web\/templates\/.*(eex)$/]]]},
 {PhoenixSample.Memcached, [host: 'localhost', port: 11211]},
 {PhoenixSample.Repo,
  [adapter: Ecto.Adapters.MySQL, username: "phoenix", password: "****",
   database: "phoenix_sample_dev", hostname: "localhost", pool_size: 10]},
 {:included_applications, []}]
iex(3)> Application.get_env :phoenix_sample, PhoenixSample.Memcached
[host: 'localhost', port: 11211]
iex(4)> PhoenixSample.Memcached.get "name"
"YMST"

無事, 設定ファイルから接続情報を取得するようにできました.

ちょっとした疑問

ここまで書いてきて不思議に思っているのは, 各設定にkeyだけでなく, アプリケーション名まで指定してやる必要があるのはなぜか?
通常設定は設定ファイルは特定のアプリケーションの為に作られ, その有効範囲もアプリケーション内部のはず.
そのアプリケーションが内部で2つ以上の名前をもっているという状態も考えにくい以上, 設定ファイルでアプリケーション名を指定してやる必要性は内容に思える.
ひょっとして, この設定は複数のアプリケーションをまたがって読み書きできたりするんですかね?

参考

Mix Config - Elixir プロジェクトの設定管理 : http://qiita.com/yulii/items/326af9cc802f3cf3670a

Elixir.Application : http://elixir-lang.org/docs/stable/elixir/Application.html

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?