Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Ash: 7 - Customizing Actions

ash_tutorial/customizing_actions.livemd

Ash: 7 - Customizing Actions

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

Attributes HomeRelationships

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:

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

Enter your solution

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
    create :open do
      accept [:subject, :description]
    end 

    update :close do
      accept []
      change set_attribute(:status, :closed)
    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

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.

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

Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "The subject", description: "the desc.", status: :closed})
|> Ash.create!()

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

ticket =
  Tutorial.Support.Ticket
  |> Ash.Changeset.for_create(:open, %{subject: "The subject", description: "The desc."})
  |> Ash.create!()

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.

Show Solution

  ticket
  |> Ash.Changeset.for_update(:close)
  |> Ash.update!()
ticket
|> Ash.Changeset.for_update(:close, %{})
|> Ash.update!()
Attributes HomeRelationships