README.md

# README

Adding a Map API to a GenServer or Module with Agent-held State.

Amlapio can be *use*-d to generate "wrapper" functions that call
Map functions on the state of a GenServer, or a module using
an Agent to hold its state.

Wrappers can be generated for the state itself or submaps of the state
(e.g. *buttons* in the examples below).

Wrappers for just a subset of Map functions can be specified using **funs**.

The wrapper functions can be named explicitly by supplying a **namer** function.

See my
[blog post](<http://ianrumford.github.io/elixir/map/api/genserver/module/agent/state/2016/09/13/amlapio.html>) for
some background.

## Installation

Add **amlapio** to your list of dependencies in <span class="underline">mix.exs</span>:

    def deps do
      [{:amlapio, "~> 0.2.0"}]
    end

## Agent Usage

The example below generates wrappers for the *buttons*, *menus* and
  *checkboxes* submaps of a Module using an Agent to hold its state. The
  names of the submap wrappers, by default, are of the form *submap\_function* e.g.
  *buttons\_pop*

It also generates three wrappers for the state itself by setting the
  submap names to nil (*agent: nil*). Also a
  **namer** (function) is given to name the state  wrappers *agent\_state\_get* ,
  *agent\_state\_put*, and *agent\_state\_pop*.

    defmodule ExampleAgent1 do
    
      # generate wrappers for three submaps
      use Amlapio, agent: [:buttons, :menus, :checkboxes]
    
      # generate *only* get, put and pop wrappers for the state itself and
      # use a namer function to name the wrappers "agent_state_get",
      # "agent_state_put" and "agent_state_pop"
      use Amlapio, agent: nil, funs: [:get, :put, :pop],
        namer: fn _map_name, fun_name ->
        ["agent_state_", to_string(fun_name)] |> Enum.join |> String.to_atom
      end
    
      # create the agent; note the default state is an empty map
      def start_link(state \\ %{}) do
        Agent.start_link(fn -> state end)
      end
    
    end

The state wrappers would be used as you'd expect and as shown in the test below:

    test "agent_state1" do
    
      buttons_state = %{1 => :button_back, 2 => :button_next, 3 => :button_exit}
      menus_state = %{menu_a: 1, menu_b: :two, menu_c: "tre"}
      checkboxes_state = %{checkbox_yesno: [:yes, :no], checkbox_bool: [true, false]}
      agent_state = %{buttons: buttons_state, menus: menus_state, checkboxes: checkboxes_state}
    
      # create the agent
      {:ok, agent} = ExampleAgent1.start_link(agent_state)
    
      # some usage examples
    
      assert buttons_state == agent |> ExampleAgent1.agent_state_get(:buttons)
    
      assert agent == agent |> ExampleAgent1.agent_state_put(:menus, 42)
      assert 42 == agent |> Agent.get(fn s -> s end) |> Map.get(:menus)
    
      assert {checkboxes_state, agent} == agent |> ExampleAgent1.agent_state_pop(:checkboxes)
      assert %{buttons: buttons_state, menus: 42} == agent |> Agent.get(fn s -> s end)
    
      assert 99 == agent |> ExampleAgent1.agent_state_get(:some_other_key, 99)
    
    end

Similarly the submap wrappers as demonstrated in the test below:

    test "agent_submap1" do
    
      buttons_state = %{1 => :button_back, 2 => :button_next, 3 => :button_exit}
      menus_state = %{menu_a: 1, menu_b: :two, menu_c: "tre"}
      checkboxes_state = %{checkbox_yesno: [:yes, :no], checkbox_bool: [true, false]}
      agent_state = %{buttons: buttons_state, 
                      menus: menus_state, checkboxes: checkboxes_state}
    
      # create the agent
      {:ok, agent} = ExampleAgent1.start_link(agent_state)
    
      # some usage examples
    
      assert :button_back == agent |> ExampleAgent1.buttons_get(1)
      assert :button_default == 
        agent |> ExampleAgent1.buttons_get(99, :button_default)
    
      assert agent == agent |> ExampleAgent1.menus_put(:menu_d, 42)
      assert menus_state |> Map.put(:menu_d, 42) == agent |> ExampleAgent1.agent_state_get(:menus)
    
      assert {[:yes, :no], agent} == 
        agent |> ExampleAgent1.checkboxes_pop(:checkbox_yesno)
    
    end

## GenServer Usage

Creating wrappers for a GenServer's state is very similar. However,
each wrapper has two "parts": an *api* function and a *handle\_call* function.

The *api* wrapper for e.g. \`buttons\_get/3\` looks like:

    # api wrapper for buttons_get
    def buttons_get(pid, button_name, button_default \\ nil) do
     GenServer.call(pid, {:buttons_get, button_name, button_default})
    end

... while the matching *handle\_call* looks like:

    def handle_call({:buttons_get, button_name, button_default}, _fromref, state) do
      value = state |> Map.get(:buttons, %{}) |> Map.get(button_name, button_default)
      {:reply, value, state}
    end

To prevent compiler warnings all of the *handle\_call* functions for
  a GenServer must be grouped together in the source. So there are
  two *uses* to define the wrappers: one for the *apis* and one for the *handle\_calls*

As for an agent, the example below generates wrappers for the *buttons*, *menus* and
  *checkboxes* submaps of the GenServer's state. 

In a minor difference to the agent example, the example generate four
 wrappers for the state itself and uses a
  **namer** (function) to name them *state\_get*, *state\_put*, *state\_pop* and *state\_take*.

    defmodule ExampleGenServer1 do
    
      # its a genserver
      use GenServer
    
      # generate API wrappers for three submaps
      use Amlapio, genserver_api: [:buttons, :menus, :checkboxes]
    
      # generate *only* get, put, pop and take wrappers for the state itself and
      # use a namer function to name the wrappers "state_get",
      # "state_put", "state_pop", and "state_take"
      use Amlapio, genserver_api: nil, funs: [:get, :put, :pop, :take],
        namer: fn _map_name, fun_name ->
        ["state_", to_string(fun_name)] |> Enum.join |> String.to_atom
      end
    
      # create the genserver; note the default state is an empty map
      def start_link(state \\ %{}) do
        GenServer.start_link(__MODULE__, state)
      end
    
      # << more functions>>
    
      # handle_calls start here
    
      # generate the handle_call functions for three submaps' wrappers
      use Amlapio, genserver_handle_call: [:buttons, :menus, :checkboxes]
    
      # generate the handle_call functions for the state wrappers.
      use Amlapio, genserver_handle_call: nil, funs: [:get, :put, :pop, :take],
        namer: fn _map_name, fun_name ->
        ["state_", to_string(fun_name)] |> Enum.join |> String.to_atom
      end
    
    end

Some examples of the state wrappers:

    test "genserver_state1" do
    
      buttons_state = %{1 => :button_back, 2 => :button_next, 3 => :button_exit}
      menus_state = %{menu_a: 1, menu_b: :two, menu_c: "tre"}
      checkboxes_state = %{checkbox_yesno: [:yes, :no], checkbox_bool: [true, false]}
      genserver_state = %{buttons: buttons_state, menus: menus_state, checkboxes: checkboxes_state}
    
      # create the genserver
      {:ok, genserver} = ExampleGenServer1.start_link(genserver_state)
    
      # some examples
    
      assert buttons_state == genserver |> ExampleGenServer1.state_get(:buttons)
    
      assert genserver == genserver |> ExampleGenServer1.state_put(:menus, 42)
      assert 42 == genserver |> ExampleGenServer1.state_get(:menus)
    
      assert {checkboxes_state, genserver} == genserver |> ExampleGenServer1.state_pop(:checkboxes)
      assert %{buttons: buttons_state, menus: 42} == 
        genserver |> ExampleGenServer1.state_take([:buttons, :menus, :checkboxes])
    
      assert 99 == genserver |> ExampleGenServer1.state_get(:some_other_key, 99)
    
    end

The submap wrappers are used in an identical way to the agent example as demonstrated
in the test below. Note these tests use the state functions.

    test "genserver_submap1" do
    
      buttons_state = %{1 => :button_back, 2 => :button_next, 3 => :button_exit}
      menus_state = %{menu_a: 1, menu_b: :two, menu_c: "tre"}
      checkboxes_state = %{checkbox_yesno: [:yes, :no], checkbox_bool: [true, false]}
      genserver_state = %{buttons: buttons_state, menus: menus_state, checkboxes: checkboxes_state}
    
      # create the genserver
      {:ok, genserver} = ExampleGenServer1.start_link(genserver_state)
    
      # some examples
    
      assert :button_back == genserver |> ExampleGenServer1.buttons_get(1)
      assert :button_default == genserver |> ExampleGenServer1.buttons_get(99, :button_default)
    
      assert genserver == genserver |> ExampleGenServer1.menus_put(:menu_d, 42)
      assert 42 == genserver |> ExampleGenServer1.state_get(:menus) |> Map.get(:menu_d)
    
      assert {[:yes, :no], genserver} == genserver |> ExampleGenServer1.checkboxes_pop(:checkbox_yesno)
      assert %{checkbox_bool: [true, false]} == genserver |> ExampleGenServer1.state_get(:checkboxes)
    
    end