Using Agent in place of database (as a "context") on start up

I’m experimenting and I’ve created a todo list app that renders a lists of todos.

The todos are stored using an Agent in lib/app/todos

This is the code for the Agent:

defmodule App.Todos do
  use Agent

  def start do
    Agent.start(
      fn ->
        [
          %{title: "some stuff-1"},
          %{title: "some stuff-2"},
          %{title: "some stuff-3"}
        ]
      end,
      name: __MODULE__
    )
  end

  def get(name) do
    __MODULE__
    |> Agent.get(& &1)
    |> Enum.filter(fn item -> item.name == name end)
  end

  def list_todos do
    Agent.get(__MODULE__, fn state -> state end)
  end

  def put(data) do
    Agent.update(__MODULE__, fn state ->
      [data | state]
    end)
  end
end

Here is my controller code:

defmodule AppWeb.TodoController do
  use AppWeb, :controller
  alias App.Todos

  def index(conn, _params) do
    todos = Todos.list_todos()
    render(conn, "index.html", todoItems: todos, stuff: "xyz", oink: "blah")
  end

  def create(conn, _params) do
    Todos.put(%{title: conn.body_params["todo"]})
    todos = Todos.list_todos()
    IO.inspect(conn.body_params["todo"])
    render(conn, "index.html", todoItems: todos)
  end
end

When I post todos they render as expected with one small problem.

When I start the app I get an error that says:

exited in: GenServer.call(App.Todos, {:get, #Function<2.53747013/1 in App.Todos.list_todos/0>}, 5000) ** (EXIT) no process: the process is not alive or there’s no process currently associated with the given name, possibly because its application isn’t started

If I start the agent by typing App.Todos.start() in the code base (I’ve been typing it at the bottom of the agent file) it will work per the auto reload of the app.

Question:

Is there anywhere I can place App.Todos.start() so that it works on the initial start of the app server and I don’t need to “manually” type it out after I’ve already started the server?

Generally you would place the agent within your application’s supervision tree. https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html

1 Like

I believe I need to pass and start the Agent inside lib/app/application.ex

I am not sure how.

The application.ex file looks like this:

defmodule App.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    children = [
      # Start the Ecto repository
      App.Repo,
      # Start the Telemetry supervisor
      AppWeb.Telemetry,
      # Start the PubSub system
      {Phoenix.PubSub, name: App.PubSub},
      # Start the Endpoint (http/https)
      AppWeb.Endpoint
      # Start a worker by calling: App.Worker.start_link(arg)
      # {App.Worker, arg},
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: App.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  def config_change(changed, _new, removed) do
    AppWeb.Endpoint.config_change(changed, removed)
    :ok
  end
end

My todos Agent code is at lib/app/todos.ex

This is the file contents:

defmodule App.Todos do
  use Agent

  def start do
    Agent.start(
      fn ->
        [
          %{title: "some stuff-1"},
          %{title: "some stuff-2"},
          %{title: "some stuff-3"}
        ]
      end,
      name: __MODULE__
    )
  end

  def get(name) do
    __MODULE__
    |> Agent.get(& &1)
    |> Enum.filter(fn item -> item.name == name end)
  end

  def list_todos do
    Agent.get(__MODULE__, fn state -> state end)
  end

  def put(data) do
    Agent.update(__MODULE__, fn state ->
      [data | state]
    end)
  end
end


I believe inside the application.ex file I need to do something like this:

  def start(_type, _args) do
    children = [
      App.Todos.start # <----- ????? 

      App.Repo,
      AppWeb.Telemetry,
      {Phoenix.PubSub, name: App.PubSub},
      AppWeb.Endpoint

    ]


    opts = [strategy: :one_for_one, name: App.Supervisor]
    Supervisor.start_link(children, opts)
  end

Of course that doesn’t work. I am reading about it now.

Don’t put a function call’s result in the supervision tree, that will never work (the App.Todos.start in your case).

The module alone should be fine.

I got an error saying that application.ex is looking for App.Todo.start_link([]).

So I just renamed my start function to match what is expected and it seems to have worked.

Still, don’t do trial and error on these things. Read the docs, it’s quite simple actually.

The way I learn is through trial and error. The docs are too complicated. It’s not simple for me. I still don’t know if what I did is right or wrong. I just know it works.

Actually I feel the docs on the topic of child_spec and supervision tree children isn’t currently in the shape it could be. The problem is that it’s scattered between the GenServer and Supervisor docs with neither giving the full picture and only explaining the topic form “their side” of things. I’d really like to see a dedicated, module independant, guide for that. I just didn’t find the time to contribute such a guide in the last weeks since this topic came to my attention.

2 Likes

The reason the docs are confusing is the same reason every api doc is confusing - the typical “API style” of technical writing is just hard to read unless you are already familiar with how the parts fit together - and you are simply looking up " parts ". Using API documentation to find the part you need is usually easy. Using API documentation to learn how the parts fit together is more difficult. When it comes to looking up basic functions, data structures etc the Elixir docs are great. With the larger and more encompassing stuff like OTP, Genserver and Phoenix I feel small tangible tutorials are much better for learning.

2 Likes

There’s a reason why many elixir docs have huge module docs. E.g. the GenServer docs are great for learning about genservers. They’re not great though for teaching how child_specs for processes being started as children of supervisors work. Both phoenix and elixir are already leveraging guides to teach overarching concepts, but for this topic there sadly doesn’t exist one yet.

1 Like

True, I feel there’s some level of OTP understanding required to be able to get useful info or if those docs in particular. Which is indeed not ideal.