How to reference the id of a struct argument to a read action

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 :bowing_man:

WIll do .

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