Help us understand the problem. What is going on with this article?

Elixirで設定ファイルをロードする(JSON, YAML, TOML)

More than 1 year has passed since last update.

はじめに

この記事では、Elixirを用いた各種設定ファイルのロード方法やTipsについて説明します。
なお、以降の説明は、以下のようなディレクトリ構成で各ファイルが配置されているという前提のもとで進めます。

config
  |-setting.json
  |-setting.toml
  |-setting.yml
lib
  |-loader.ex

JSON

まずはJSON形式のファイルロードについて考えてみます。
JSONの設定ファイルには、次のような値が格納されているとしましょう。

setting.json
{
  "job": {
    "name": "new_job",
    "step": [
      {"level": 1, "desc": "Start"},
      {"level": 2, "desc": "Stop"}
    ],
    "repeat": false
  }
}

このようなJSON形式の文字列をパースす場合は Poison を用いることができます。

次のように deps を追加し、Poisonによって読み込んだ文字列をオブジェクトへとパースします。

mix.exs
  defp deps do
    [{:poison, "~> 3.1"}]
  end
loader.ex
  def load_json() do
    "./config/setting.json"
    |> File.read!()
    |> Poison.decode!()
  end

上記のコードを実行すると、次の結果が得られます。

iex
iex> Loader.load_json()
%{
  "job" => %{
    "name" => "new_job",
    "repeat" => false,
    "step" => [
      %{"desc" => "Start", "level" => 1},
      %{"desc" => "Stop", "level" => 2}
    ]
  }
}

YAML

次にYAMLを使うケースについて考えてみましょう。
次のようなYAMLの設定ファイルがあるとします。

setting.yml
job:
  name: new_job
  step:
  - level: 1
    desc: Start
  - level: 2
    desc: Stop
  repeat: false

このような YAML で定義されたファイルの内容を取得する場合、 YamlElixir を使うことができます。
以下のように、deps の追加と関数の定義を行いましょう。

mix.exs
  defp deps do
    [{:yaml_elixir, "~> 2.1"}]
  end
loader.ex
  def load_yaml() do
    "./config/setting.yml"
    |> File.read!()
    |> YamlElixir.read_from_string()
  end

このコードを実行すると、次のように YAML がパースされたオブジェクトが得られます。

iex
iex> Loader.load_yaml()
%{
  "job" => %{
    "name" => "new_job",
    "repeat" => false,
    "step" => [
      %{"desc" => "Start", "level" => 1},
      %{"desc" => "Stop", "level" => 2}
    ]
  }
}

また、YamlElixirは read_from_file/1read_from_file!/1 といった関数も提供しているので、次のようにファイルの読み込みとオブジェクトへのパースをまとめて行うこともできます。

loader.ex
  def load_yaml_from_file() do
    "./config/setting.yml"
    |> YamlElixir.read_from_file!()
  end

TOML

TOML は設定を定義するためのファイルフォーマットであり、ハッシュテーブルへと明確にマップすることが可能であるという特徴を持ちます。これまで登場したような設定は、以下のように定義されます。

setting.toml
[job]
name = "new_job"
repeat = false

  [[job.step]]
  level = 1
  desc = "Start"

  [[job.step]]
  level = 2
  desc = "Stop"

(ここでは TOML 自体の説明は割愛しますが、 設定ファイル記述言語 TOML のQiita記事が詳しく説明していますので参考にしてみてください)

上記のようなTOMLの設定をロードするには、 Toml というElixirライブラリを利用することができます。

次のように、 deps の追加、関数の定義をしましょう。

mix.exs
  defp deps do
    [{:toml, "~> 0.3"}]
  end
loader.ex
  def load_toml() do
    "./config/setting.toml"
    |> File.read!()
    |> Toml.decode!()
  end

このコードにより、次のようにTOMLの設定値を取り出すことができます。

iex
iex(10)> Loader.load_toml()
%{
  "job" => %{
    "name" => "new_job",
    "repeat" => false,
    "step" => [
      %{"desc" => "Start", "level" => 1},
      %{"desc" => "Stop", "level" => 2}
    ]
  }
}

また、 Toml はファイルから直接オブジェクトを取り出すための関数 decode_file! も提供しているので、次のように書くこともできます。

loader.ex
  def load_toml_from_file() do
    "./config/setting.toml"
    |> Toml.decode_file!()
  end

ビルド時の設定ロード

これまで紹介した方法は、コードが実行される際にファイルの内容を読み出してオブジェクトへとパースするという方法を取ってきました。しかし、この方法は実行毎にファイルIOが発生するため、頻繁に実行される処理などでは好ましいものではありません。また、設定ファイルの内容を反映させた上でDistilleryなどで配布したいうというケースもあるでしょう。

このようなケースでは、ビルドの時点でファイルから設定を読み込んでおき、モジュールの属性(attribute)として定義しておくという方法を取ることができます。次のコードを見てみましょう。

loader.ex
defmodule Loader do
  @json_config "./config/setting.json" |> File.read!() |> Poison.decode!()
  def json_config(), do: @json_config
end

このコードをビルドすることで、 setting.json から読み出した内容を @json_config へと反映させます。そのため、 Loader.json_config を実行すると、ファイルIOを発生させることなく特定の設定ファイルの内容を取得することができるようになります。

dev環境のみ動的読み込み

前述した方法では、ビルド時に設定内容をロードしておくことでファイルアクセス無しに設定内容を取得することができました。しかし、これは逆に言うと、ビルドをし直さない限り設定ファイルの内容が反映されないということを意味します。これは、開発時のように設定を頻繁に変更してトライアンドエラーを繰り返すようなケースでは都合が悪く、ビルド無しで毎回設定ファイルを読み込んでくれた方が効率が良いでしょう。

そこで、以下のようにすると、dev環境でのみ設定ファイルを毎回ロードさせることができます。

loader.ex
defmodule Loader do
  if Mix.env == :dev do
    def json_config() do
      "./config/setting.json" |> File.read!() |> Poison.decode!()
    end
  else
    @json_config "./config/setting.json" |> File.read!() |> Poison.decode!()
    def json_config(), do: @json_config
  end
end

見るとわかるように、 Mix.env によって得られる値が :dev であるかどうかによってビルドされるコードが決定されます。そのためこのケースでは、dev でのみ設定ファイルを都度読み込む処理となります。
(他にも Dependency Injection を利用するという方法を取ることもできます)

Tsuyoshi84
Angular/Vue.jsを用いたフロントエンド開発、Elixir/Phoenixを用いたバックエンド開発、Swiftを用いたiOSアプリ開発などを行なっています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away