Current section
Files
Jump to
Current section
Files
lib/map_bot.ex
defmodule MapBot do
@moduledoc """
`MapBot` builds 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 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
defmodule YourApp.Car do
defstruct id: nil, model: nil, color: nil, code: nil, reference: nil
end
```
For building your own maps and structs take a look on the function `MapBot.build/4`.
"""
@type factory :: module()
@type name :: module() | atom()
@type traits :: map() | keyword()
@type attributes :: map() | keyword()
@type result :: struct() | map()
@callback new(name) :: result
@doc """
Builds an Elixir Map or Struct.
## Examples
iex> YourApp.Factory.build(YourApp.Car)
%YourApp.Car{model: "SUV", color: :black}
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}
iex> YourApp.Factory.build(:tomato)
%{name: "Tomato", color: :red}
iex> YourApp.Factory.build(:tomato, color: :green)
%{name: "Tomato", color: :green}
iex> YourApp.Factory.build(:tomato, %{color: :green})
%{name: "Tomato", color: :green}
"""
@spec build(factory, name, traits, attributes) :: result
def build(factory, name, traits, attrs) do
[name, traits, attrs]
|> Enum.flat_map(&to_list/1)
|> Enum.reduce(%{}, &apply_attr(&1, &2, factory))
|> apply_sequence()
end
def to_list(val) when is_list(val), do: val
def to_list(val) when is_map(val), do: Map.to_list(val)
def to_list(val), do: [val]
defp apply_attr({key, value}, map, _factory), do: Map.put(map, key, value)
defp apply_attr(name, map, factory), 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
@spec build(MapBot.name(), MapBot.traits(), MapBot.attributes()) :: MapBot.result()
def build(name, traits \\ [], attrs \\ []) do
MapBot.build(__MODULE__, name, traits, attrs)
end
end
end
end