1. Tsuyoshi84

    Posted

    Tsuyoshi84
Changes in title
+Elixirで設定ファイルをロードする(JSON, YAML, TOML)
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,226 @@
+# はじめに
+
+この記事では、Elixirを用いた各種設定ファイルのロード方法やTipsについて説明します。
+なお、以降の説明は、以下のようなディレクトリ構成で各ファイルが配置されているという前提のもとで進めます。
+
+```
+config
+ |-setting.json
+ |-setting.toml
+ |-setting.yml
+lib
+ |-loader.ex
+```
+
+
+# JSON
+
+まずはJSON形式のファイルロードについて考えてみます。
+JSONの設定ファイルには、次のような値が格納されているとしましょう。
+
+```json:setting.json
+{
+ "job": {
+ "name": "new_job",
+ "step": [
+ {"level": 1, "desc": "Start"},
+ {"level": 2, "desc": "Stop"}
+ ],
+ "repeat": false
+ }
+}
+```
+
+このようなJSON形式の文字列をパースす場合は [Poison](https://github.com/devinus/poison) を用いることができます。
+
+次のように deps を追加し、Poisonによって読み込んだ文字列をオブジェクトへとパースします。
+
+```elixir:mix.exs
+ defp deps do
+ [{:poison, "~> 3.1"}]
+ end
+```
+
+```elixir:loader.ex
+ def load_json() do
+ "./config/setting.json"
+ |> File.read!()
+ |> Poison.decode!()
+ end
+```
+
+上記のコードを実行すると、次の結果が得られます。
+
+```elixir:iex
+iex> Loader.load_json()
+%{
+ "job" => %{
+ "name" => "new_job",
+ "repeat" => false,
+ "step" => [
+ %{"desc" => "Start", "level" => 1},
+ %{"desc" => "Stop", "level" => 2}
+ ]
+ }
+}
+```
+
+# YAML
+
+次にYAMLを使うケースについて考えてみましょう。
+次のようなYAMLの設定ファイルがあるとします。
+
+```yml:setting.yml
+job:
+ name: new_job
+ step:
+ - level: 1
+ desc: Start
+ - level: 2
+ desc: Stop
+ repeat: false
+```
+
+このような YAML で定義されたファイルの内容を取得する場合、 [YamlElixir](https://github.com/KamilLelonek/yaml-elixir) を使うことができます。
+以下のように、deps の追加と関数の定義を行いましょう。
+
+```elixir:mix.exs
+ defp deps do
+ [{:yaml_elixir, "~> 2.1"}]
+ end
+```
+
+```elixir:loader.ex
+ def load_yaml() do
+ "./config/setting.yml"
+ |> File.read!()
+ |> YamlElixir.read_from_string()
+ end
+```
+
+このコードを実行すると、次のように YAML がパースされたオブジェクトが得られます。
+
+```elixir:iex
+iex> Loader.load_yaml()
+%{
+ "job" => %{
+ "name" => "new_job",
+ "repeat" => false,
+ "step" => [
+ %{"desc" => "Start", "level" => 1},
+ %{"desc" => "Stop", "level" => 2}
+ ]
+ }
+}
+```
+
+また、YamlElixirは `read_from_file/1` や `read_from_file!/1` といった関数も提供しているので、次のようにファイルの読み込みとオブジェクトへのパースをまとめて行うこともできます。
+
+```elixir:loader.ex
+ def load_yaml_from_file() do
+ "./config/setting.yml"
+ |> YamlElixir.read_from_file!()
+ end
+```
+
+
+
+# TOML
+
+[TOML](https://github.com/toml-lang/toml) は設定を定義するためのファイルフォーマットであり、ハッシュテーブルへと明確にマップすることが可能であるという特徴を持ちます。これまで登場したような設定は、以下のように定義されます。
+
+```toml:setting.toml
+[job]
+name = "new_job"
+repeat = false
+
+ [[job.step]]
+ level = 1
+ desc = "Start"
+
+ [[job.step]]
+ level = 2
+ desc = "Stop"
+```
+
+(ここでは TOML 自体の説明は割愛しますが、 [設定ファイル記述言語 TOML](https://qiita.com/b4b4r07/items/77c327742fc2256d6cbe) のQiita記事が詳しく説明していますので参考にしてみてください)
+
+上記のようなTOMLの設定をロードするには、 [Toml](https://github.com/bitwalker/toml-elixir) というElixirライブラリを利用することができます。
+
+次のように、 deps の追加、関数の定義をしましょう。
+
+```elixir:mix.exs
+ defp deps do
+ [{:toml, "~> 0.3"}]
+ end
+```
+
+```elixir:loader.ex
+ def load_toml() do
+ "./config/setting.toml"
+ |> File.read!()
+ |> Toml.decode!()
+ end
+```
+
+このコードにより、次のようにTOMLの設定値を取り出すことができます。
+
+```elixir:iex
+iex(10)> Loader.load_toml()
+%{
+ "job" => %{
+ "name" => "new_job",
+ "repeat" => false,
+ "step" => [
+ %{"desc" => "Start", "level" => 1},
+ %{"desc" => "Stop", "level" => 2}
+ ]
+ }
+}
+```
+
+また、 Toml はファイルから直接オブジェクトを取り出すための関数 `decode_file!` も提供しているので、次のように書くこともできます。
+
+```elixir:loader.ex
+ def load_toml_from_file() do
+ "./config/setting.toml"
+ |> Toml.decode_file!()
+ end
+```
+
+# ビルド時の設定ロード
+
+これまで紹介した方法は、コードが実行される際にファイルの内容を読み出してオブジェクトへとパースするという方法を取ってきました。しかし、この方法は実行毎にファイルIOが発生するため、頻繁に実行される処理などでは好ましいものではありません。また、設定ファイルの内容を反映させた上でDistilleryなどで配布したいうというケースもあるでしょう。
+
+このようなケースでは、ビルドの時点でファイルから設定を読み込んでおき、モジュールの属性(attribute)として定義しておくという方法を取ることができます。次のコードを見てみましょう。
+
+```elixir: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環境でのみ設定ファイルを毎回ロードさせることができます。
+
+```elixir: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](https://www.openmymind.net/Dependency-Injection-In-Elixir/) を利用するという方法を取ることもできます)