Open3
Elixir PhoenixでTrait(FactoryBot Trait)ライクを使うための習作
利用イメージ
user_fixture([:japanese])
user_fixture(email: "test@example.com")
user_fixture([:japanese, email: "test@example.com"])
user_fixture([:japanese, :male, email: "test@example.com"])
追記:下記を検討のこと
たたき台バージョン
共通定義
defmodule DataTrait do
def trait do
quote do
defp trait(obj, []), do: obj
defp trait(obj, [head | tail]) when is_atom(head) do
_trait(obj, head) |> trait(tail)
end
defp trait(obj, list) do
obj
|> Map.merge(Map.new(list))
end
defp _trait(obj, :touch), do: obj
end
end
defmacro __using__([]) do
apply(__MODULE__, :trait, [])
end
end
fixtureモジュール側
defmodule UsersFixtures do
use DataTrait
def user_fixture(traits \\ []) do
%User{}
|> trait(traits)
|> Repo.insert!()
end
defp _trait(user, :japanese),
do: Map.put(user, :language, "japanese")
defp _trait(user, :man),
do: Map.put(user, :sex, "male")
end
- 実用に耐えそう...?
- 複雑なパターンはおとなしく
user_hoge_fixture()
で分ける
使ってみて変更してみたバージョン
- 命名をやや変更
-
user_fixture(:japanese)
にも対応(1要素ならリストにしなくてよい) - 名前が各Fixturesで被るのでprefixを指定できるように対応。
use DataTraits, "user"
のように指定する- 結果、DataTraitsの可読性がぐっと下がったけれど仕方なしか
-
def <namespace>_trait(obj, :defaults)
を個別実装することを共通処理での前提に変更- 例えば、
def user_trait(obj, :defaults)
のような定義をuse
するモジュールで必要とする
- 例えば、
- キーがStringなMapとAtomなMapとが欲しいときがあるので、用意してしまった
/test/support/data_traits.ex
defmodule DataTraits do
def traits(name_space) do
prefix = name_space <> "_"
func_name_traits = String.to_atom(prefix <> "traits")
func_name_trait = String.to_atom(prefix <> "trait")
quote do
defp unquote(func_name_traits)(trait) when is_atom(trait) do
unquote(func_name_traits)(%{}, [:defaults, trait])
end
defp unquote(func_name_traits)(traits) when is_list(traits) do
unquote(func_name_traits)(%{}, [:defaults] ++ traits)
end
defp unquote(func_name_traits)(obj, []), do: obj
defp unquote(func_name_traits)(obj, [head | tail]) when is_atom(head) do
unquote(func_name_trait)(obj, head) |> unquote(func_name_traits)(tail)
end
defp unquote(func_name_traits)(obj, [{key, value} | tail]) do
obj
|> Map.put(key, value)
|> unquote(func_name_traits)(tail)
end
defp unquote(func_name_traits)(obj, [head | tail]) when is_list(head) do
obj
|> Map.merge(Map.new(head))
|> unquote(func_name_traits)(tail)
end
defp unquote(func_name_trait)(obj, :touch), do: obj
defp unquote(func_name_trait)(obj, :to_params) do
obj
|> Map.new(fn
{key, value} when is_atom(key) -> {Atom.to_string(key), value}
other -> other
end)
end
defp unquote(func_name_trait)(obj, :to_attrs) do
obj
|> Map.new(fn
{key, value} when is_bitstring(key) -> {String.to_atom(key), value}
other -> other
end)
end
end
end
defmacro __using__(name_space) do
apply(__MODULE__, :traits, [name_space])
end
end
fixtureモジュール側の例
defmodule MyApp.ThemesFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `MyApp.Themes` context.
"""
use DataTraits, "theme"
@doc """
Generate a theme.
"""
def theme_fixture(traits \\ []) do
params = theme_params(traits)
{:ok, theme} = Themes.create_theme(params)
theme
end
def theme_params(traits) do
theme_traits(List.wrap(traits) ++ [:to_params])
end
defp theme_trait(obj, :defaults) do
obj
|> Map.merge(%{
name: "テーマ",
})
end
defp theme_trait(obj, :invalid), do: Map.put(obj, :name, "")