I’m stuck on how best to create a read action that takes a first argument of a struct (another Ash resource) that is used (via its id attribute) to filter the results of a query.
My code is currently along the lines of:
read :by_name_in_wh do
# argument :warehouse_id, Ash.Type.UUID, allow_nil?: false
argument :warehouse, Ash.Type.Struct do
constraints instance_of: Flow.Model.Warehouse
allow_nil? false
end
argument :name, :ci_string, allow_nil?: false
filter expr(warehouse == (^arg(:warehouse)).id)
filter expr(name == ^arg(:name))
end
How do I reference the id field of the input struct? The above code isn’t working but of course it will work if I use the warehouse_id argument input instead (but that feels like a less good api)
Thanks
Martin
(^arg(:warehouse))[:id]
, or you can do it functionally if that is more clear
prepare fn query, _ ->
Ash.Query.filter(query, query.arguments.warehouse.id)
end
1 Like
Many thanks (once again) @zachdaniel for the pointers.
My findings were that:
filter expr(warehouse == (^arg(:warehouse))[:id])
didn’t work. It generated a runtime error with an “Unsupported expression in Elixir.AshPostgres.SqlImplementation query”
Also… I found that:
prepare fn query, _ ->
Ash.Query.filter(query, query.arguments.warehouse.id)
end
needed some adjustment to work properly (some pinning or use of the keyword syntax) to create:
prepare fn query, _ ->
Ash.Query.filter(query, warehouse_id == ^query.arguments.warehouse.id)
end
or
prepare fn query, _ ->
Ash.Query.filter(query, [warehouse_id: query.arguments.warehouse.id])
end
I’m interested to understand why the filter DSL statement didn’t work, but also appreciate the clarity of the fn approach - especially having better read the Ash.Query.filter documentation!
Thanks again
Martin
Ah, sorry was a hastily drawn up example, thanks for adding the fixed version. It does seem pretty strange that the DSL version didn’t work, I wouldn’t have expected that. What does the full error look like?
It’s strange. To recreate the error I put back the filter expression as:
filter expr(warehouse_id == (^arg(:warehouse))[:id])
And then generated the changeset (using Ash.Changeset.for_read(…)) to see what the action generates, and noticed that the filter looks wonky/wrong:
filter: #Ash.Filter<name == #Ash.CiString<"fp22"> and warehouse_id == #Flow.Model.Warehouse<
count_of_uncapped_pools: #Ash.NotLoaded<:aggregate, field: :count_of_uncapped_pools>,
As you can see the warehouse_id is being set equal to the struct and not the id despite the filter syntax shown at the top - as though the [:id] had no narrowing effect.
The resulting error message is due to trying to match a guid value against a struct at runtime.
What’s wrong with the syntax of the filter expression? Any thoughts?
Martin
Isnt your access syntax outside the paranthesis? Does that matter?
Edit: I see thats what zach wrote, but maybe a typo?
I don’t think that this is a typo. It makes sense to resolve the pinned value of the specified arg and then index into it’s :id attribute. But I’d love to be corrected!
I think it may be a good old fashioned bug. Could you open an issue? Ideally a PR with a test reproduction but no worries if you don’t have time for that 
I’ve used get_path
for this in the past - eg. filter expr(artist_id == get_path(^arg(:artist), :id))
, to pull the ID out of the argument and use it in an expression. There might be a better way!
Thanks Rebecca. This works well even though I think that the functional approach prepare fn query, ...
is a bit clearer to understand in terms of Elixir code. I had to dig into the Expressions guide to discover where get_path
was mentioned.
Martin