278 lines
6.8 KiB
Markdown
278 lines
6.8 KiB
Markdown
# Ash: 5 - Customizing Actions
|
|
|
|
```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)
|
|
```
|
|
|
|
## Customizing Actions
|
|
|
|
<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="attributes.livemd">Attributes</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="relationships.livemd">Relationships</a>
|
|
<i class="ri-arrow-right-fill"></i>
|
|
</div>
|
|
</div>
|
|
|
|
### In this tutorial you will add custom Actions on the Ticket resource
|
|
|
|
Create 2 custom actions, `:open` and `:close`.
|
|
|
|
Custom actions allow you to attach *semantics* to actions.
|
|
|
|
* Instead of *Creating* a ticket, you can *Open* a ticket.
|
|
* Instead of *Updating* the status on a ticket you can *Close* it.
|
|
|
|
In addition, you can customize the behaviour of these actions. For example, closing a ticket only sets the status to `:closed`.
|
|
|
|
It also allows you to define what attributes can be set when calling that action. For example for the `:open` action, you can only allow the `:subject` and `:description` to be set.
|
|
It does not make sense to set the `:status` in this case as it should always be `:open`. For the closing action you won't allow any attributes to be set.
|
|
|
|
Custom actions go inside the `actions do ... end` block.
|
|
|
|
To define the `:open` action, open a `do end` block like so:
|
|
|
|
<!-- livebook:{"force_markdown":true} -->
|
|
|
|
```elixir
|
|
create :open do
|
|
|
|
end
|
|
```
|
|
|
|
Same for the `:close` action, but instead of `create`, use `update`.
|
|
|
|
You then can define the accepted attributes like so:
|
|
|
|
`accept [:subject, :description]`
|
|
|
|
Or in the case of the `:close` action:
|
|
|
|
`accept []`
|
|
|
|
Then for the `:close` action define a `change`:
|
|
|
|
`change set_attribute(:status, :closed)`
|
|
|
|
That's it, you defined your first custom actions.
|
|
|
|
<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
|
|
defmodule Tutorial.Support.Ticket do
|
|
use Ash.Resource,
|
|
domain: Tutorial.Support,
|
|
data_layer: Ash.DataLayer.Ets
|
|
|
|
actions do
|
|
defaults [:read]
|
|
|
|
create :open do
|
|
# By default you can provide all public attributes to an action
|
|
# This action should only accept the subject
|
|
accept [:subject, :description]
|
|
end
|
|
|
|
update :close do
|
|
# We don't want to accept any input here
|
|
accept []
|
|
|
|
change set_attribute(:status, :closed)
|
|
# A custom change could be added like so:
|
|
#
|
|
# change MyCustomChange
|
|
# change {MyCustomChange, opt: :val}
|
|
end
|
|
end
|
|
|
|
attributes do
|
|
uuid_primary_key :id
|
|
|
|
attribute :subject, :string, allow_nil?: false
|
|
attribute :description, :string
|
|
attribute :status, :atom do
|
|
constraints [one_of: [:open, :closed]]
|
|
default :open
|
|
allow_nil? false
|
|
end
|
|
|
|
create_timestamp :created_at
|
|
update_timestamp :updated_at
|
|
end
|
|
end
|
|
|
|
defmodule Tutorial.Support do
|
|
use Ash.Domain
|
|
|
|
resources do
|
|
resource Tutorial.Support.Ticket
|
|
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]
|
|
|
|
# <-- Add the :open and :close action
|
|
end
|
|
|
|
attributes do
|
|
uuid_primary_key(:id)
|
|
|
|
attribute :subject, :string, allow_nil?: false
|
|
attribute :description, :string
|
|
|
|
attribute :status, :atom do
|
|
constraints one_of: [:open, :closed]
|
|
default :open
|
|
allow_nil? false
|
|
end
|
|
|
|
create_timestamp :created_at
|
|
update_timestamp :updated_at
|
|
end
|
|
end
|
|
|
|
defmodule Tutorial.Support do
|
|
use Ash.Domain
|
|
|
|
resources do
|
|
resource Tutorial.Support.Ticket
|
|
end
|
|
end
|
|
```
|
|
|
|
## Open a Ticket
|
|
|
|
Open a ticket.
|
|
|
|
Remember, when creating a resource, use a changeset (`Ash.Changeset.for_create/3`), which gets passed to `Ash.create!/1`. But in this case use the `:open` argument instead of `:create`.
|
|
|
|
<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
|
|
Tutorial.Support.Ticket
|
|
|> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
|
|
|> Ash.create!()
|
|
```
|
|
|
|
</div>
|
|
</details>
|
|
|
|
Try setting the `:status` to `:closed` and see if it works.
|
|
|
|
<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
|
|
Tutorial.Support.Ticket
|
|
|> Ash.Changeset.for_create(:open, %{subject: "My Subject", status: :closed})
|
|
|> Ash.create!()
|
|
```
|
|
|
|
</div>
|
|
</details>
|
|
|
|
The output when trying to set `:status` should look something like this:
|
|
|
|
```
|
|
** (Ash.Error.Invalid) Input Invalid
|
|
|
|
* Invalid value provided for status: cannot be changed.
|
|
```
|
|
|
|
This is because you set the accepted attributes to `:subject` and `:description` only.
|
|
|
|
**Enter your solution**
|
|
|
|
```elixir
|
|
|
|
```
|
|
|
|
Create a Ticket and store it in the `ticket` variable.
|
|
|
|
<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
|
|
ticket =
|
|
Tutorial.Support.Ticket
|
|
|> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
|
|
|> Ash.create!()
|
|
```
|
|
|
|
</div>
|
|
</details>
|
|
|
|
**Enter your solution**
|
|
|
|
```elixir
|
|
|
|
```
|
|
|
|
## Close a Ticket
|
|
|
|
Close the `ticket` you created in the previous section.
|
|
|
|
Remember to use `Ash.Changeset.for_update/2` with the `:close` action.
|
|
|
|
To update use `Ash.update!/1`.
|
|
|
|
<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
|
|
ticket
|
|
|> Ash.Changeset.for_update(:close)
|
|
|> Ash.update!()
|
|
```
|
|
|
|
</div>
|
|
</details>
|
|
|
|
```elixir
|
|
|
|
```
|
|
|
|
<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="attributes.livemd">Attributes</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="relationships.livemd">Relationships</a>
|
|
<i class="ri-arrow-right-fill"></i>
|
|
</div>
|
|
</div>
|