ネット上の CSV データを、Req パッケージを使ってダウンロードし、NimbleCSV でデコードしようとしていたのですが。
二つのパッケージをインストールして、Req のレスポンスのボディを NimbleCSV でデコードしたら失敗し、ボディがテキストデータでないことに気がつきました。 なにやらボディの内容がリストになっています。
最初は iodata になっているのかと勘違いしたのですが、実はレスポンスのボディがすでに CSV としてデコードされ、リストのリストになっているのでした。
よくよく Req のドキュメントを調べてみると、JSON や ZIP などは自動的にデコードする仕組みになっているのですが、NimbleCSV を一緒にインストールした場合には CSV も自動的なデコードの対象になる仕組みになっていることがわかりました。
実現方法はいたって単純で、
Code.ensure_loaded?/1
で NimbleCSV がロードされているか調べる- ロードされていれば NimbleCSV を使ってボディをデコードする、ロードされていなければ何もしない
というもの。 ただし、これだけではコンパイル時に NimbleCSV が見つからないと警告が出てしまいます。 警告を抑えるために、
mix.exs
のproject/0
で:xref
を使って対象外にする
という細工がされていました。
なお :xref
の仕様を調べきれていないので具体的な機序はまだ確認できていません。
とはいえ。 実現方法がわかり実践することは可能なので、サンプルを書いてみることにしました。
最初に、二つのパッケージを利用する MyApp と、利用される Foo, Bar を作成します。
$ mix new my_app $ mix new foo $ mix new bar
my_app/lib/my_app.ex
を編集して Foo を利用する関数を追加します。
defmodule MyApp do def do_something do Foo.do_something() |> IO.puts() end end
my_app/mix.exs
の依存パッケージの記述に Foo を追加します。
defmodule MyApp.MixProject do use Mix.Project # 略 defp deps do [ {:foo, path: "../foo"} ] end end
次に foo/lib/foo.ex
を編集します。
Bar がロードされているか Code.ensure_loaded?/1
で判定します。
ロードされていれば Bar の関数を実行し、そうでなければ自分で値を返します。
defmodule Foo do def do_something do if Code.ensure_loaded?(Bar) do Bar.do_something() else "do something by Foo" end end end
最後に bar/lib/bar.ex
を編集します。
defmodule Bar do def do_something do "do something by Bar" end end
my_app
ディレクトリに移動して MyApp.do_something/0
を実行します。
$ cd my_app $ mix run -e 'MyApp.do_something()'
Foo.do_something/0
が呼び出され、do something by Foo
が表示されますが、Bar が未定義であることの警告が表示されてしまいます。
==> foo Compiling 1 file (.ex) warning: Bar.do_something/0 is undefined (module Bar is not available or is yet to be defined) │ 4 │ Bar.do_something() │ ~ │ └─ (foo 0.1.0) lib/foo.ex:4:11: Foo.do_something/0 Generated foo app do something by Foo
そこで foo/mix.exs
を編集して以下の設定を追加します。
defmodule Foo.MixProject do use Mix.Project def project do [ # 略 xref: [ exclude: [ Bar ] ], # 略 ] end # 略 end
これで再度実行すると Foo が再コンパイルされますが、未定義の警告は表示されなくなりました。
$ mix run -e 'MyApp.do_something()' ==> foo Compiling 1 file (.ex) Generated foo app do something by Foo
もう一度 my_app/mix.exs
を編集して依存パッケージの記述に Bar を追加します。
defmodule MyApp.MixProject do use Mix.Project # 略 defp deps do [ {:foo, path: "../foo"}, {:bar, path: "../bar"} ] end end
もう一度再実行します。
MyApp や Foo はコンパイルされず、Bar だけがコンパイルされるのがわかります。
そして MyApp.do_something/0
から Foo.do_something/0
が、Foo.do_something/0
から Bar.do_something/0
が呼び出され、最終的に do something by Bar
が表示されました。
$ mix run -e 'MyApp.do_something()' ==> bar Compiling 1 file (.ex) Generated bar app do something by Bar