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)
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:
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.
Show Solution
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
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.
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
.
Show Solution
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
|> Ash.create!()
Try setting the :status
to :closed
and see if it works.
Show Solution
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My Subject", status: :closed})
|> Ash.create!()
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
Create a Ticket and store it in the ticket
variable.
Show Solution
ticket =
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
|> Ash.create!()
Enter your solution
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
.
Show Solution
ticket
|> Ash.Changeset.for_update(:close)
|> Ash.update!()