Packages

Elixir fake data generation for testing and development

Current section

Files

Jump to
blacksmith lib blacksmith.ex
Raw

lib/blacksmith.ex

defmodule Blacksmith do
defmacro __using__(_) do
quote do
import Blacksmith, except: [having: 2]
alias Blacksmith.Sequence
@default_type :struct
@new_function &Blacksmith.new/4
@save_one_function &Blacksmith.saved/1
@save_all_function &Blacksmith.new_saved_list/1
defmacro having(opts, block) do
Blacksmith.having(opts, block, __ENV__, __CALLER__)
end
end
end
def append_opts(opts_var, true, new_opts) do
quote do: unquote(opts_var) ++ unquote(new_opts)
end
def append_opts(_, false, new_opts) do
new_opts
end
# Allow a common set of arguments
def having(opts, [do: block], env, caller) do
opts_var = quote do: opts_var
block =
Macro.prewalk(block, fn
{{:., _, [{:__aliases__, _, _}, :having]}, _, _} = ast ->
ast
{{:., _, [{:__aliases__, _, _} = alias, _]} = function, meta3, args} = ast ->
if Macro.expand(alias, caller) == env.module do
{function, meta3, args ++ [opts_var]}
else
ast
end
ast ->
ast
end)
var_defined? = {:opts_var, env.module} in caller.vars
opts = Blacksmith.append_opts(opts_var, var_defined?, opts)
quote do
opts_var = unquote(opts)
context = unquote(block)
opts_var = []
context
end
end
defmacro register(name, opts \\ [], fields) do
name_attributes = :"#{name}_attributes"
name_opts = :"#{name}_opts"
quote do
def unquote(name_attributes)() do
unquote(fields)
end
def unquote(name_opts)() do
unquote(opts)
end
def unquote(name)(overrides \\ %{}, havings \\ %{}) do
@new_function.(unquote(name_attributes)(),
Blacksmith.merge(overrides, havings),
__MODULE__,
unquote(name_opts)())
end
def unquote(:"saved_#{name}")(overrides \\ %{}, havings \\ %{}) do
new_saved(unquote(name_attributes)(),
Blacksmith.merge(overrides, havings),
__MODULE__,
unquote(name_opts)(),
@new_function,
@save_one_function)
end
def unquote(:"#{name}_list")(number_of_records, overrides \\ %{}, havings \\ %{}) do
new_list(number_of_records,
&(unquote(Macro.var(name_attributes, nil))/0),
Blacksmith.merge(overrides, havings),
__MODULE__,
unquote(name_opts)(),
@new_function)
end
def unquote(:"saved_#{name}_list")(number_of_records, overrides \\ %{}, havings \\ %{}) do
list = unquote(:"#{name}_list")(number_of_records, overrides, havings)
@save_all_function.(list)
end
end
end
def new(attributes, overrides, module, opts) do
map = if prototype = opts[:prototype] do
apply(module, prototype, []) |> to_map
else
%{}
end
attributes = to_map(attributes)
overrides = to_map(overrides)
if Map.has_key?(map, :__struct__) and Map.has_key?(attributes, :__struct__) do
raise "prototype and current record cannot both be structs"
end
map
|> Map.merge(attributes)
|> Map.merge(overrides)
end
def new_saved(attributes, overrides, module, opts, new, save) do
model = new.(attributes, overrides, module, opts)
save.(model)
end
def saved(_map) do
raise "Save not configured. See readme.md for details."
end
def new_list(number_of_records, attributes_fun, overrides, module, opts, new) do
Enum.map(1..number_of_records, fn _ -> new.(attributes_fun.(), overrides, module, opts) end)
end
def new_saved_list(_list) do
raise "Save not configured. See readme.md for details."
end
def to_map(list) when is_list(list),
do: Enum.into(list, %{})
def to_map(map) when is_map(map),
do: map
def merge(left, right) do
Map.merge(to_map(left), to_map(right))
end
end