add ash_tutorial
This commit is contained in:
210
ash_tutorial/aggregates.livemd
Normal file
210
ash_tutorial/aggregates.livemd
Normal file
@@ -0,0 +1,210 @@
|
||||
# Ash: 8 - Aggregates
|
||||
|
||||
```elixir
|
||||
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
|
||||
|
||||
<div class="flex items-center w-full flex-start justify-between rounded-xl p-4" style="background-color: #f0f5f9; color: #61758a;">
|
||||
<div class="flex">
|
||||
<i class="ri-arrow-left-fill"></i>
|
||||
<a class="flex ml-2" style="color: #61758a;" href="code_interfaces.livemd">Code Interfaces</a>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<i class="ri-home-fill"></i>
|
||||
<a class="flex ml-2" style="color: #61758a;" href="overview.livemd">Home</a>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<a class="flex mr-2" style="color: #61758a;" href="calculations.livemd">Calculations</a>
|
||||
<i class="ri-arrow-right-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### In this tutorial you will add an Aggregate on a Resource
|
||||
|
||||
[Aggregates](https://hexdocs.pm/ash/aggregates.html) 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 add the inverse relationship of belongs_to inside the Representative resource.
|
||||
|
||||
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.
|
||||
|
||||
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
|
||||
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
|
||||
<div class="p-4">
|
||||
|
||||
```elixir
|
||||
aggregates do
|
||||
count :count_of_open_tickets, :tickets do
|
||||
filter expr(status == :open)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
### Enter your solution
|
||||
|
||||
```elixir
|
||||
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
|
||||
end
|
||||
```
|
||||
|
||||
```elixir
|
||||
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 Create](https://hexdocs.pm/ash/create-actions.html#bulk-creates) to create 4 tickets, 3 of which are assigned to Joe.
|
||||
|
||||
```elixir
|
||||
# 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.
|
||||
|
||||
```elixir
|
||||
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)
|
||||
```
|
||||
|
||||
```elixir
|
||||
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.
|
||||
|
||||
<!-- livebook:{"break_markdown":true} -->
|
||||
|
||||
<div class="flex items-center w-full flex-start justify-between rounded-xl p-4" style="background-color: #f0f5f9; color: #61758a;">
|
||||
<div class="flex">
|
||||
<i class="ri-arrow-left-fill"></i>
|
||||
<a class="flex ml-2" style="color: #61758a;" href="code_interfaces.livemd">Code Interfaces</a>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<i class="ri-home-fill"></i>
|
||||
<a class="flex ml-2" style="color: #61758a;" href="overview.livemd">Home</a>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<a class="flex mr-2" style="color: #61758a;" href="calculations.livemd">Calculations</a>
|
||||
<i class="ri-arrow-right-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user