Pipe Ash actions - how do I write these queries?

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

    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

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?


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.

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

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

calculate :has_parciero, :boolean, expr(exists(parcieros, id == ^arg(:id)) do
  argument :parciero_id, :uuid, allow_nil?: false
patio_id = something
YourResource.read(..., query: Ash.Query.filter(YourResource, has_patio(id: patio_id))

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

Thanks Zach.