Learning Elixir, Phoenix and Ash Part 4: Code Interfaces
In a previous post I talked about there being “more than one way to do it” when writing actions for resources. I mentioned two ways to query for a record, a long form query builder style and a slightly abbreviated version that takes some of the query params as arguments (see below to jog your memory). There is another method which I think is probably the right way, Code Interfaces
The Phoenix framework has a concept of contexts which is the way you are supposed to access your app layer from your presentation layer. Phoenix apps have a distinct boundary between the application side and the presentation side. You are supposed to use contexts as the interface between the two, that is you’re not supposed to be reaching into your database or models directly from your views. This is a good thing™. The Ash equivalent of contexts is Domains. Domains are where you define code interfaces that call your resource actions.
You define a code interface like this
defmodule MyApp.Factories do
resources do
resource MyApp.Factories.Part do
define :get_part, action: :read, get_by: :id
...
This defines a MyApp.Factories.get_part
action. So this…
{:ok, part} =
MyApp.Factories.Part
|> Ash.Query.for_read(:get_by_id, %{id: id})
|> Ash.Query.load(:bill_of_materials_entries, [:child_part])
|> Ash.Query.set_actor(socket.assigns.current_user)
|> Ash.Query.set_tenant(socket.assigns.current_user.factory_id)
|> Ash.read_one()
or this…
{:ok, part} =
MyApp.Factories.Part
|> Ash.Query.for_read(:get_by_id, %{id: id})
|> Ash.read_one(
actor: socket.assigns.current_user,
tenant: socket.assigns[:current_tenant],
load: [bill_of_materials_entries: [:child_part]]
)
becomes this…
{:ok, part} =
MyApp.Factories.get_part(id,
actor: socket.assigns.current_user,
tenant: socket.assigns[:current_tenant],
load: [bill_of_materials_entries: [:child_part]]
)
As much as the geek in me likes the “I’m a real programmer” vibe of the first version I like this more. Code interfaces partially address my complaints in a previous post about the poor ergonomics. I would like it even more if I could write
{:ok, part} = MyApp.Factories.get_part(id)
and hide away the actor
, tenant
and load
shenanigans, but I’ll take what I can get. Maybe I’ll figure out how to do that later.
I haven’t found anything in the docs that officially sanctions one particular way of writing actions other than in the code interface docs which describes them as
something more idiomatic and simple.
Feeling much better about the code after this refactor 👍🏼