rbino
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
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
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
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!
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









