Using the SimpleLazyObject a database query to fetch the user from the database will only be executed if the user object is accessed during the request/response cycle.
Another way of achieving this pattern in Python is to cache method return values on the object. eg
class UserMiddleware:
@property
def user(self):
if not hasattr(self, '_user'):
self._user = # Fetch user from database
return self._user
This requires storing state on the UserMiddleware object and return a User object. I know in Elixir you can only return a modified value, you can’t store state like Python in a class object.
How do I achieve the same outcome in Elixir? Perhaps using a server process to encapsulate the state?
Ok, I think I’ve managed to prevent repeated database calls using an Agent
defmodule Auth do
use Agent
alias Foo.Accounts
def start_link() do
Agent.start_link(fn -> nil end, name: __MODULE__)
end
def user(user_id) do
Agent.get_and_update(__MODULE__, fn state ->
case state do
user ->
{user, user}
nil ->
user = Accounts.get_user!(user_id)
{user, user}
end
end
end
end
Just need to figure out how to start the Agent on every incoming request.
Besides manually managing your conn.assigns there are quite a few data-caching libraries in Elixir that for instance sit between your web-application and your database. Cachex and Nebulex come to mind, but they are by no means the only ones.
Implicit DB loads are generally seen as an anti-pattern in the Elixir world. For instance, loading data while rendering a page makes it impossible for Phoenix LiveView to figure out when a re-render is needed.
An interesting approach is the fetch_cookies function in Plug: if cookies have already been fetched, it’s a noop, and otherwise it fetches them and stores them in the conn. It uses a special struct %Unfetched{} to represent that the data isn’t loaded yet.