defmodule MapBot do @moduledoc """ `MapBot` builds and creates Elixir Maps/Structs based on factory definitions and attributes. If you want to check how `MapBot` should be installed, configured and used please [check this out](https://hexdocs.pm/map_bot/). In summary you should create your own Factory module such as this: ```elixir defmodule YourApp.Factory do use MapBot @impl MapBot def repo(), do: Repo @impl MapBot def new(YouyApp.Car), do: %YouyApp.Car{model: "SUV", color: :black} def new(:greenish), do: %{color: :green} def new(:tomato), do: %{name: "Tomato", color: :red} def new(:with_code_and_ref), do: %{code: &"CODE-\#{&1}", reference: &"REF-\#{&1}"} end ``` For building your own maps and structs take a look on the functions `MapBot.build/4`, `MapBot.create/4` and `MapBot.create!/4`. """ @type factory :: module() @type name :: module() | atom() @type traits :: map() | keyword() @type attributes :: map() | keyword() @type result :: struct() | map() @type repo :: module() @callback new(name) :: result @callback repo :: repo @doc """ Builds an Elixir Map or Struct. ## Examples iex> YourApp.Factory.build(:tomato) %{name: "Tomato", color: :red} iex> YourApp.Factory.build(YourApp.Car) %YourApp.Car{model: "SUV", color: :black} iex> YourApp.Factory.build(:tomato, color: :green) %{name: "Tomato", color: :green} iex> YourApp.Factory.build(:tomato, %{color: :green}) %{name: "Tomato", color: :green} iex> YourApp.Factory.build(YourApp.Car, color: :yellow) %YourApp.Car{model: "SUV", color: :yellow} iex> YourApp.Factory.build(YourApp.Car, %{color: :yellow}) %YourApp.Car{model: "SUV", color: :yellow} iex> YourApp.Factory.build(YourApp.Car, [:greenish]) %YourApp.Car{model: "SUV", color: :green} iex> YourApp.Factory.build(YourApp.Car, [:greenish], model: "Sport") %YourApp.Car{model: "Sport", color: :green} iex> YourApp.Factory.build(YourApp.Car, [:greenish, model: "Sport"]) %YourApp.Car{model: "Sport", color: :green} """ @spec build(factory, name, traits, attributes) :: result def build(factory, name, traits \\ [], attrs \\ []) def build(factory, name, %{} = traits, attrs) do build(factory, name, Map.to_list(traits), attrs) end def build(factory, name, traits, %{} = attrs) do build(factory, name, traits, Map.to_list(attrs)) end def build(factory, name, traits, attrs) do ([name] ++ traits ++ attrs) |> Enum.reduce(%{}, &apply_attr(factory, &1, &2)) |> apply_sequence() end @doc """ Creates an Elixir Map or Struct using your Repo.insert/1 ## Examples iex> YourApp.Factory.create(YourApp.Car, color: :yellow) {:ok, %YourApp.Car{id: "123", model: "SUV", color: :yellow}} """ @spec create(factory, name, traits, attributes) :: {:ok, result} def create(factory, name, traits \\ [], attrs \\ []) do factory |> build(name, traits, attrs) |> factory.repo().insert() end @doc """ Creates an Elixir Map or Struct using your Repo.insert!/1 ## Examples iex> YourApp.Factory.create!(YourApp.Car, color: :yellow) %YourApp.Car{id: "123", model: "SUV", color: :yellow} """ @spec create!(factory, name, traits, attributes) :: result def create!(factory, name, traits \\ [], attrs \\ []) do factory |> build(name, traits, attrs) |> factory.repo().insert!() end defp apply_attr(_factory, {key, value}, map), do: Map.put(map, key, value) defp apply_attr(factory, name, map), do: Map.merge(map, factory.new(name)) defp apply_sequence(map) do next_int = MapBot.Sequence.next_int() map |> Map.to_list() |> Enum.reduce(map, &sequence(&1, &2, next_int)) end defp sequence({_key, val}, map, _i) when not is_function(val), do: map defp sequence({key, func}, map, i), do: Map.put(map, key, func.(i)) defmacro __using__(_opts) do quote do @behaviour MapBot def build(name, traits \\ [], attrs \\ []) do MapBot.build(__MODULE__, name, traits, attrs) end def create(name, traits \\ [], attrs \\ []) do MapBot.create(__MODULE__, name, traits, attrs) end def create!(name, traits \\ [], attrs \\ []) do MapBot.create!(__MODULE__, name, traits, attrs) end end end end