While working on a phoenix application, I found the need to hang onto some
state between API calls. It wasn’t quite enough state, and losing the state
wasn’t exactly a huge loss, so I decided to use Elixir’s Agent
.
To ensure my state was easy to retrieve, I needed to name my Agent
after
something I’d easily have. As this is a Twilio application, the phone number
being called should do nicely.
defmodule HelloWorld.OngoingCall do
def start_call(%{number: number} = state) do
Agent.start(fn -> state end, String.to_atom(number))
end
end
If you wanted to make sure that the Agent
will recover, you could link it to
a dynamic supervisor. That will not be covered here.
Twilio will provide update events as your call progresses depending on the
listed events, and I want to capture failed
, busy
, and no-answer
events. Now, we need to add a controller action.
defmodule HelloWorld.HelloWorldController do
def update_call(conn, %{To => to, CallStatus => call_status}) do
state =
to
|> OngoingCall.get_state()
|> OngoingCall.update_state(call_status)
render(conn, "call_updated.xml", state)
end
end
Agent.update/3
takes a function that takes the current state and returns the
new state. I can write update functions that pattern match on the current
call_status
to write specific update functions.
defmodule HelloWorld.OngoingCall do
def update_state(%{number: number} = state, call_status) do
number
|> String.to_atom()
|> Agent.update(update(call_status))
end
def update("busy") do
fn state ->
%{state | action: "busy"}
end
end
end
Now, I can keep track of an ongoing call throughout many different status
events. When the call ends, we can tell the Agent
to exit.
defmodule HelloWorld.HelloWorldController do
def update_call(conn, %{To => to, CallStatus => "ended"}) do
to
|> OngoingCall.end_state()
render(conn, "call_ended.xml")
end
end
# ...
def end_call(to, reason // :normal) do
to
|> String.to_atom()
|> Agent.stop(reson)
end
# ...
Now, if anything abnormal happens, we can pass a reason
to ensure the failure
is captured.