Ash: 11 - Aggregates
Application.put_env(:ash, :validate_domain_resource_inclusion?, false)
Application.put_env(:ash, :validate_domain_config_inclusion?, false)
Mix.install([{:ash, "~> 3.0"}], consolidate_protocols: false)
Aggregates
Code Interfaces HomeCalculationsIn this tutorial you will add an Aggregate on a Resource
Aggregates in Ash allow for retrieving summary information over groups of related data. Aggregates can be either count, first, sum or list.
Implement a count aggregate on the ‘open tickets’ a representative is assigned to.
First explore the comments in the code below, to make this work properly you have to:
- Set the representative belongs_to relationship to writable
- Add the representative_id to the open action
- Add the representative_id to the code interface
- Add the inverse relationship of belongs_to inside the Representative resource
Only the latter is really required for aggregates to work, the first 3 allow you to be a bit more concise when creating new tickets.
To add an aggregate define an aggregates do .. end
block inside the representative.
Inside the aggregates
block, define a count :count_of_open_tickets, :tickets do .. end
block.
Then inside this block, define a filter like so: filter expr(status == :open)
Also, notice what happens when you don’t define a filter.
Show Solution
aggregates do
count :count_of_open_tickets, :tickets do
filter expr(status == :open)
end
end
Enter your solution
defmodule Tutorial.Support.Ticket do
use Ash.Resource,
domain: Tutorial.Support,
data_layer: Ash.DataLayer.Ets
actions do
defaults [:read]
# On creation set the representative by providing the id
create :open do
accept [:subject, :description, :representative_id]
end
update :close do
accept []
change set_attribute(:status, :closed)
end
update :assign do
accept [:representative_id]
end
end
attributes do
uuid_primary_key(:id)
attribute :subject, :string, allow_nil?: false
attribute :description, :string, allow_nil?: true
attribute :status, :atom do
constraints one_of: [:open, :closed]
default :open
allow_nil? false
end
create_timestamp :created_at
update_timestamp :updated_at
end
relationships do
belongs_to :representative, Tutorial.Support.Representative
end
code_interface do
define :assign, args: [:representative_id]
# <- added representative_id
define :open, args: [:subject, :description, :representative_id]
define :close, args: []
end
end
defmodule Tutorial.Support.Representative do
use Ash.Resource,
domain: Tutorial.Support,
data_layer: Ash.DataLayer.Ets
actions do
defaults [:read]
create :create do
accept [:name]
end
end
attributes do
uuid_primary_key :id
attribute :name, :string
end
# Added the inverse relationship of belongs_to in the ticket.
# This way we can reference :tickets inside the aggregates.
relationships do
has_many :tickets, Tutorial.Support.Ticket
end
code_interface do
define :create, args: [:name]
end
# <- Add the aggregates here
aggregates do
count :count_of_open_tickets, :tickets do
filter expr(status == :open)
end
end
end
defmodule Tutorial.Support do
use Ash.Domain
resources do
resource Tutorial.Support.Ticket
resource Tutorial.Support.Representative
end
end
Query the Aggregate
Use a Bulk Action to create 4 tickets, 3 of which are assigned to Joe.
# Create a representative
joe = Tutorial.Support.Representative.create!("Joe Armstrong")
# Bulk create 4 tickets, 3 of which are assigned to Joe
[
%{subject: "I can't see my eyes!"},
%{subject: "I can't find my hand!", representative_id: joe.id},
%{subject: "My fridge is flying away!", representative_id: joe.id},
%{subject: "My bed is invisible!", representative_id: joe.id}
]
|> Ash.bulk_create(Tutorial.Support.Ticket, :open, return_records?: true)
Close the last ticket assigned to Joe.
require Ash.Query
# Retrieve the last ticket
[last_ticket] =
Tutorial.Support.Ticket
|> Ash.Query.filter(representative_id == ^joe.id)
|> Ash.Query.sort(created_at: :desc)
|> Ash.Query.limit(1)
|> Ash.read!()
# Close the last ticket
Tutorial.Support.Ticket.close!(last_ticket)
joe = Ash.load!(joe, [:count_of_open_tickets])
joe.count_of_open_tickets
The result should be 2, as you opened 4 tickets, 3 were assigned to Joe, but the last assigned ticket was closed.
Code Interfaces HomeCalculations