rbino

rbino

Ash Core Team

Equivalent of `has_one through` in Ash

In my application, I have three resources: Device, SystemModelPartNumber and SystemModel. Each Device belongs to a SystemModelPartNumber via its part_number column. A SystemModelPartNumber belongs to a SystemModel via system_model_id. A SystemModel has many SystemModelPartNumbers.

This are the relevant parts of the Ash resources

defmodule Device do
  # ...
  relationships do
    belongs_to :system_model_part_number, SystemModelPartNumber do
      attribute_type :string
      source_attribute :part_number
      destination_attribute :part_number
    end
  end
  # ...
end

defmodule SystemModelPartNumber do
  attributes do
    attribute :part_number, :string, allow_nil?: false
  end

  relationships do
    belongs_to :system_model, SystemModel
  end

  identities do
    identity :part_number, [:part_number]
  end
end

# def SystemModel ...

When I was using Ecto, I was exposing SystemModel using has_one through: [:system_model_part_number, :system_model].

How can achieve a similar result using Ash?

For now I tried:

  • Using a calculation
  calculations do
    calculate :system_model, :struct, expr(system_model_part_number.system_model) do
      constraints instance_of: SystemModel
    end
  end

but the DB query fails with:

* ** (Postgrex.Error) ERROR 42846 (cannot_coerce) cannot cast type bigint to jsonb

    query: SELECT d0."id", d0."device_id", d0."name", d0."online", s1."system_model_id"::bigint::jsonb::jsonb FROM "devices" AS d0 LEFT OUTER JOIN "public"."system_model_part_numbers" AS s1 ON d0."part_number" = s1."part_number" WHERE (d0."id"::bigint = $1::bigint) AND (d0."tenant_id"::bigint = $2::bigint)
  • Using an aggregate (first): doesn’t work for belongs_to

The next option I’ve wanted to try was a manual has_one, but I want to check if I’m overcomplicating things before.

Most Liked

zachdaniel

zachdaniel

Creator of Ash

Right now, a manual has_one is the way. Really just need to add a through option to has_many and has_one. It wouldn’t even be that difficult at this point TBH. The only thing is that it wouldn’t be compatible with manage_relationship, but I think that is to be expected.

rbino

rbino

Ash Core Team

I’ve tried to do it like this:

defmodule MyApp.Devices.Device.ManualRelationships.SystemModel do
  use Ash.Resource.ManualRelationship
  require Ash.Query

  alias MyApp.Devices

  @impl true
  def load(devices, _opts, %{query: query} = context) do
    device_ids = Enum.map(devices, & &1.id)

    related_system_models =
      query
      |> Ash.Query.filter(part_numbers.devices.id in ^device_ids)
      |> Ash.Query.load(part_numbers: [:devices])
      |> Devices.read!()

    device_id_to_system_model =
      related_system_models
      |> Enum.flat_map(fn system_model ->
        system_model.part_numbers
        |> Enum.flat_map(fn part_number ->
          Enum.map(part_number.devices, &{&1.id, system_model})
        end)
      end)

    {:ok, device_id_to_system_model}
  end
end

and it works when there’s a single SystemModel, but it fails if there’s more than one. It seems that Ash.Query.filter(SystemModel, part_numbers.devices.id in ^device_ids) only picks up the first SystemModelPartNumber in part_numbers.

Am I missing some pieces of the expression syntax?

rbino

rbino

Ash Core Team

Solved it, I needed to reset the limit because the query included limit: 1 by default (that’s actually mentioned in the docs)

# ...
    related_system_models =
      query
      |> Ash.Query.unset(:limit)
      |> Ash.Query.filter(part_numbers.devices.id in ^device_ids)
      |> Ash.Query.load(part_numbers: [:devices])
      |> Devices.read!()
# ...

Doing this, it works!

Where Next?

Popular in Questions Top

beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> somethi...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement