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

Ash: 6 - Attributes

ash_tutorial/attributes.livemd

Ash: 6 - Attributes

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)

Attributes

Querying HomeCustomizing Actions

In this tutorial you will add and configure Attributes to a Ticket resource

The Ticket resource will represent a helpdesk ticket.

It will have 3 main attributes.

  • A subject or title
  • A description
  • A status which can be either :open or :closed

It will also have 2 attributes for keeping track of when the ticket was created, and when it was last updated.

Subject

You need to make sure the subject is always set, it’s not possible to create a ticket without a subject. You can do this by setting allow_nil? to false. Like so: attribute :subject, :string, allow_nil?: false

Description

The :description is simple, it’s a :string and it is allowed to be empty.

Status

:status is more complicated.

  • It is of the type :atom
  • It can only be the value :open or :closed
  • By default it is :open
  • And it can’t be nil

Attributes are set in a do end block like so:

attribute :status, :atom do

  # ...

  allow_nil? false
end

To set a constraint of values, you can use the constraints option, like so:

constraints [one_of: [:open, :closed]]

To set the default value, you use default :open.

Keeping track of Created and Updated

Ash provides create_timestamp and update_timestamp to keep track of when the Resource was first created, and when it was last updated.

Add the following to the attributes block:

create_timestamp :created_at
update_timestamp :updated_at

Show Solution

  defmodule Tutorial.Support.Ticket do
    use Ash.Resource,
      domain: Tutorial.Support,
      data_layer: Ash.DataLayer.Ets

    actions do
      defaults [:read]

      create :create do
        accept [:subject, :description, :status]
      end
    end

    attributes do
      uuid_primary_key :id

      attribute :subject, :string, allow_nil?: false
      attribute :description, :string

      # status is either `open` or `closed`.
      attribute :status, :atom do
        # Constraints allow you to provide extra rules for the value.
        # The available constraints depend on the type
        # See the documentation for each type to know what constraints are available
        # Since atoms are generally only used when we know all of the values
        # it provides a `one_of` constraint, that only allows those values
        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]

    create :create do
      accept [:subject, :description, :status]
    end
  end

  attributes do
    uuid_primary_key :id

    # Add the attributes here -->
    # - Subject
    attribute :subject, :string, allow_nil?: false
    # - Description
    attribute :description, :string
    # - Status
    attribute :status, :atom do
      constraints [one_of: [:open, :closed]]
      default :open
      allow_nil? :false
    end
    
    # - Create Timestamp
    create_timestamp :created_at
    # - Update Timestamp
    update_timestamp :updated_at
  end
end

defmodule Tutorial.Support do
  use Ash.Domain

  resources do
    resource Tutorial.Support.Ticket
  end
end

Creating a Ticket

Create a Ticket without any attributes.

Remember, when creating a resource use a changeset (Ash.Changeset.for_create/3), which gets passed to Ash.create!/1.

The output should look something like this:

** (Ash.Error.Invalid) Input Invalid

* attribute subject is required

Show Solution

  Tutorial.Support.Ticket
  |> Ash.Changeset.for_create(:create, %{})
  |> Ash.create!()
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()

Now create a Ticket with a subject

Show Solution

  Tutorial.Support.Ticket
  |> Ash.Changeset.for_create(:create, %{subject: "This is the subject"})
  |> Ash.create!()
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "subject one"})
|> Ash.create!()

Now create a ticket with the status set to :closed

Show Solution

  Tutorial.Support.Ticket
  |> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :closed})
  |> Ash.create!()
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "subject two", status: :closed})
|> Ash.create!()

Now try creating a ticket with the status set to :pending. This should give an error because :pending is not a valid status.

Show Solution

  Tutorial.Support.Ticket
  |> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :pending})
  |> Ash.create!()
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "subject three", status: :pending})
|> Ash.create!()

Latest created Ticket

Since you added a creation date, you can now query on the latest created ticket.

First, sort using the Ash.Query.sort/2 function by Ash.Query.sort(created_at: :desc)

Then limit the amount of records with the Ash.Query.limit/2 function by doing Ash.Query.limit(1)

Finally call Ash.read_one!() on the query.

Hint: Use a pipeline

Show Solution

  Tutorial.Support.Ticket
  |> Ash.Query.sort(created_at: :desc)
  |> Ash.Query.limit(1)
  |> Ash.read_one!()
Tutorial.Support.Ticket
|> Ash.Query.sort(created_at: :desc)
|> Ash.Query.limit(1)
|> Ash.read_one!()
Querying HomeCustomizing Actions