A title for your blog

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 👍🏼