I am trying to create a macro to generate handlers for my genserver. Each genserver has regular CRUD in may case so what I have
defmodule Foo.X do
use GenServer
use Foo.Service
@actions [:create, :update]
end
and in my service
defmacro __using__(_) do
quote do
import Foo.Service
@before_compile Foo.Service
end
end
defmacro __before_compile__(env) do
quote do
Module.eval_quoted(unquote(env.module), quote do
def hello, do: "world"
def hello({_, _}, _from, _state), do: "world aaa"
def handle_call({_, _}, _from, _state) do
"empty"
end
end)
end
end
So hello
method is there I can list it and call. But for example implemented handle_call
is not available when I call Foo.Xhandle_call({1,1}, 1, %{})
The final result that I am trying to achieve is
defmacro __before_compile__(env) do
actions = Module.get_attribute(env.module, :actions)
quote do
Enum.each(unquote(actions), fn action ->
Module.eval_quoted(unquote(env.module), quote do
def handle_call({unquote(action), entity}, _from, state) do
fun = String.to_atom("do_#{unquote(action)}")
quote do
unquote(fun)(entity)
end
{:reply, %{}, state}
end
end)
end)
end
end
I believe you are overcomplicating things. use
macro accepts a second parameter which probably would better suite your needs that a module attribute and all that dancing around __before_compile__
. You probably might do something like:
defmodule Foo.X do
use Foo.Service, actions: ~w|create update|a
end
and in Foo.Service
:
defmodule Foo.Service do
defmacro __using__(opts) do
actions = opts[:actions] || ~w|create update|a
init =
quote do
use GenServer
def start_link(opts), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(args), do: {:ok, args}
defoverridable init: 1
end
code =
Enum.map(actions, fn action ->
quote do
def unquote(action)(), do: GenServer.call(__MODULE__, unquote(action))
def handle_call(unquote(action), _from, state), do: do_unquote(action)()
def do_unquote(action), do: :ok
defoverridable do_unquote(action): 0
end
end)
[init | code]
end
end
I have not tested this code nor I think it’ll run as is, but I believe it might give a hint on where to evolve your attempt.
4 Likes
Finished with next code at the end
defmacro __using__(opts) do
actions = opts[:actions] || []
init =
quote do
use GenServer
def start_link(opts), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(args), do: {:ok, args}
defoverridable init: 1
end
code =
Enum.map(actions, fn action ->
func_name = :"do_#{action}"
quote do
def unquote(action)(entity), do: Dispatcher.dispatch(__MODULE__, {unquote(action), entity})
def handle_call({unquote(action), _entity} = params, _from, state) do
apply(__MODULE__, params)
{:reply, :ok, state}
end
def handle_cast({unquote(action), _entity} = params, state) do
apply(__MODULE__, params)
{:noreply, state}
end
def unquote(func_name)(entity), do: entity
defoverridable ["do_#{unquote(action)}": 1]
end
end)
[init | code]
end
1 Like
defoverridable ["do_#{unquote(action)}": 1]
in the last line should probably read as defoverridable [{unquote(func_name), 1}]
.
1 Like