Agent - abstraction around STATE

      iex> {:ok, pid} = Agent.start_link(fn -> %{} end)
       {:ok, #PID<0.72.0>}
      iex> Agent.update(pid, fn m -> Map.put(m, :hello, :world) end)
       :ok
      iex> Agent.get(pid, fn m -> Map.get(m, :hello) end)
       :world

API

start (fun , [option] \\\\ []) -> 
  {:ok, pid} | {:error, :timeout | {:already_started, pid} | term}

start (module, function, [arg], [option] \\\\ []) ->
  {:ok, pid} | {:error, :timeout | {:already_started, pid} | term}

start_link (fun, [option] \\\\ []) ->
  {:ok, pid} | {:error, :timeout  |  {:already_started, pid} | term}

start_link (module, function, [arg], [option] \\\\ []) ->
  {:ok, pid} | {:error, :timeout | {:already_started, pid} | term}

the agent is spawned, the given function is invoked, its return value is used as the state


stop (agent, reason \\\\ :normal, timeout \\\\ :infinity) -> ok

it returns :ok if the server terminates with the given reason
if it terminates with another reason, the call will exit
if the reason is any other than :normal, :shutdown or {:shutdown, _}, an error report will be logged


cast (agent, fun/1) -> :ok

    performs a cast (fire and forget) operation on the agent state
    the fun must return the new state

cast (agent, module, :func_name, [arg])  ->  :ok

    the state is added as first argument to the given list of args

returns :ok immediately, regardless of whether the destination node or agent exists


get (agent, fun/1, timeout \\\\ 5000) -> some_result

   the result of the function invocation is returned

get (agent, module, :func_name, [arg], timeout \\\\ 5000) -> some_result
  
    the state is added as first argument to the given list of args


update (agent, fun/1, timeout \\\\ 5000) ->  :ok

update (agent, module, :func_name, [arg], timeout \\\\ 5000) ->  :ok

     in update/4 the state is added as first argument to the given list of args

all code inside the function passed to the agent is executed by the agent
you may want to avoid expensive operations inside the agent, as it will effectively
block the agent until the request is fulfilled
  

registration

An Agent is bound to the same name registration rules as GenServer - you use the second (optional) parameter name: in Agent.start_link function

agent :: pid | {atom, node} | name

name :: atom | {:global, term} | {:via, module, term}

example:

supervisor:

defmodule Exjson do

  use Application

  def start(_type, _args) do
    import Supervisor.Spec

    children = [
      supervisor(Exjson.Endpoint, []),
      worker(Exjson.Myagent, [])
    ]
    opts = [strategy: :one_for_one, name: Exjson.Supervisor]
    Supervisor.start_link(children, opts)
  end

end

agent:

  
defmodule Exjson.Myagent do
  
  @mock_data Application.app_dir(:exjson, "priv/data/MOCK_DATA.json")
             |> File.read! |> Poison.decode!

  def start_link() do
    Agent.start_link(fn -> @mock_data end , name: Exjson.Smith)
  end            

end

usage:

defmodule Exjson.UserTwoController do

  use Exjson.Web, :controller

  def show(conn , %{"id" => id}) do
    x = String.to_integer(id)
    render conn ,
           upload: Agent.get(Exjson.Smith ,
             fn r -> r |> Enum.filter(&(&1["id"] === x)) end)
  end

end

distributed agents

Agents provide two APIs, one that works with anonymous functions and another that expects an explicit module, function, and arguments. in a distributed setup with multiple nodes, the API that accepts anonymous functions only works if the caller (client) and the agent have the same version of the caller module

Keep in mind this issue also shows up when performing “rolling upgrades” with agents: you wish to deploy a new version of your software by shutting down some of your nodes and replacing them with nodes running a new version of the software. in this setup, part of your environment will have one version of a given module and the other part another version (the newer one) of the same module. the best solution is to simply use the explicit MFA APIs when working with distributed agents