Generate boilerplate genserver callbacks

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