I have a resource with the following actions:
read :by_patio do
argument :patio, :map, allow_nil?: false
filter patios: arg(:patio) # there'a a relationship to patios
end
read :by_parceiro do
argument :parceiro, :map, allow_nil?: false
argument :voucher, :boolean, allow_nil?: false
filter parceiros: arg(:patio), voucher: arg(:voucher) # there'a a relationship to parceiros
end
Sometimes I have to query the resource only by_patio, sometimes only by_parceiro and sometimes by both (AND).
How do I write all three queries?
Thanks.
1 Like
It depends on how you are accessing the data. If you want to do it in code you can just use the default read function and add filters.
Resource
|> Ash.Query.for_read(:read)
|> Ash.Query.filter(patio == "value")
|> Ash.Query.filter(parceiro == "other_value")
|> Api.read()
You can write more complex filters Ash.Filter — ash v2.15.7 to also filter on relationships
You just use relationship_name.attribute == value
for example.
I’m not 100% sure abut the syntax with keyword lists, but I would guess that is is just nested lists for relationships.
e.g: relationship: [attribute: value]
You can put them inside the actions if you always want to filter when using those actions. If you use AshGraphql or AshJsonApi they both already include ways to filter in your queries.
Thanks @barnabasJ.
Patios and parceiros are many_to_many relatioships. I can follow your example, but I use this “composed” query (filters by patios and parceiros) many times in the code, so I’d like to write a single action that provides both filters. Sometimes I’ll filter only by patios, sometimes only by parceiros, sometimes by both. Is it possible to write an action for that?
You can write a preparation that will check which arguments were provided:
read :read do
argument :patio, :map, allow_nil?: false
argument :parceiro, :map, allow_nil?: false
argument :voucher, :boolean, allow_nil?: false
prepare fn query, _ ->
if ... do
Ash.Query.filter(....)
end
end
end
But generally speaking if you want composability at the call site, with named things that the caller chooses, you would write calculations and have the caller provide a query that filters on those calculations.
calculate :has_patio, :boolean, expr(exists(patios, id == ^arg(:id))) do
argument :patio_id, :uuid, allow_nil?: false
end
calculate :has_parciero, :boolean, expr(exists(parcieros, id == ^arg(:id)) do
argument :parciero_id, :uuid, allow_nil?: false
end
patio_id = something
YourResource.read(..., query: Ash.Query.filter(YourResource, has_patio(id: patio_id))
2 Likes
So then if they want to filter by one and/or both they can, and you can change the implementations of the calculations as you please.
1 Like