defmodule Exto do
@moduledoc "./README.md" |> File.stream!() |> Enum.drop(1) |> Enum.join()
defmacro __using__(_options) do
quote do
import Exto, only: [flex_schema: 1]
use Accessible
end
end
@doc """
Adds additional associations dynamically based on the config found under the name of the current module for the given OTP application.
Each key maps to an Ecto.Schema function:
* `belongs_to`
* `field`
* `has_many`
* `has_one`
* `many_to_many`
Each of these keys should map to a keyword list where the key is the name of the field or association and the value is one of:
* A type
* A tuple of type and options (keyword list)
"""
defmacro flex_schema(otp_app) when is_atom(otp_app) do
module = __CALLER__.module
config = Application.get_env(otp_app, module, [])
code = Enum.flat_map(config, &flex_category/1)
quote do
(unquote_splicing(code))
end
end
# flex_schema impl
@cats [:belongs_to, :field, :has_one, :has_many, :many_to_many]
defp flex_category({:code, code}), do: [code]
defp flex_category({cat, items}) when cat in @cats and is_list(items),
do: Enum.map(items, &flex_association(cat, &1))
# skip over anything else, they might use it!
defp flex_category(_), do: []
defp flex_association(rel, {name, type})
when is_atom(name) and is_atom(type),
do: flex_association(rel, name, type, [])
defp flex_association(rel, {name, opts})
when is_atom(name) and is_list(opts),
do: flex_association(rel, name, opts)
defp flex_association(rel, {name, {type, opts}})
when is_atom(name) and is_atom(type) and is_list(opts),
do: flex_association(rel, name, type, opts)
defp flex_association(rel, {name, {opts}})
when is_atom(name) and is_list(opts),
do: flex_association(rel, name, opts)
defp flex_association(rel, name, opts) do
quote do
unquote(rel)(unquote(name), unquote(opts))
end
end
defp flex_association(rel, name, type, opts) do
quote do
unquote(rel)(unquote(name), unquote(type), unquote(opts))
end
end
end