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

Ash: 4 - Attributes

livebooks/ash-tutorial/attributes.livemd

Ash: 4 - 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 an Issue resource

The Issue resource will represent a GitHub-style issue in BitHub.

It will have 3 main attributes:

  • A title that serves as the issue’s subject
  • A body containing the issue description
  • A state which can be either :open or :closed

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

Title

The title is required; an issue cannot be created without a title.
You can enforce this by setting allow_nil? to false, like so:

attribute :title, :string, allow_nil?: false

Body

The body is optional and can be left empty. It is simply a :string type.

State

:state is more structured:

  • It is of type :atom
  • It can only have the values :open or :closed
  • By default, it is :open
  • It cannot be nil

Attributes are set within a do end block like so:

attribute :state, :atom do
  # Constraints ensure only valid values can be used.
  constraints [one_of: [:open, :closed]]
  
  default :open
  allow_nil? false
end

Keeping track of Created and Updated timestamps

Ash provides create_timestamp and update_timestamp to track when the resource was first created and when it was last updated.

To add this, include the following in the attributes block:

create_timestamp :created_at
update_timestamp :updated_at

Show Solution

  defmodule BitHub.Issues.Issue do
    use Ash.Resource,
      domain: BitHub.Issues,
      data_layer: Ash.DataLayer.Ets

    actions do
      defaults [:read]

      create :create do
        accept [:title, :body, :state]
      end
    end

    attributes do
      uuid_primary_key :id

      attribute :title, :string, allow_nil?: false
      attribute :body, :string

      # State is either `open` or `closed`
      attribute :state, :atom do
        constraints [one_of: [:open, :closed]]
        default :open
        allow_nil? false
      end

      create_timestamp :created_at
      update_timestamp :updated_at
    end
  end

  defmodule BitHub.Issues do
    use Ash.Domain

    resources do
      resource BitHub.Issues.Issue
    end
  end
defmodule BitHub.Issues.Issue do
  use Ash.Resource,
    domain: BitHub.Issues,
    data_layer: Ash.DataLayer.Ets

  actions do
    defaults [:read]

    create :create do
      accept [:title, :body, :state]
    end
  end

  attributes do
    uuid_primary_key :id

    # Define the main attributes for an issue
    attribute :title, :string, allow_nil?: false
    attribute :body, :string

    attribute :state, :atom do
      constraints [one_of: [:open, :closed]]
      default :open
      allow_nil? false
    end

    create_timestamp :created_at
    update_timestamp :updated_at
  end
end

defmodule BitHub.Issues do
  use Ash.Domain

  resources do
    resource BitHub.Issues.Issue
  end
end

Creating an Issue

We are going to create an Issue without any attributes. This will show an error.

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

The error output will look like this:

** (Ash.Error.Invalid) 
Bread Crumbs:
  > Error returned from: BitHub.Issues.Issue.create

Invalid Error

* attribute title is required
  (ash 3.4.54) lib/ash/error/changes/required.ex:4:

It will show the Ash version and line number. Don't be afraid to look at the source code.

Show Solution

  BitHub.Issues.Issue
  |> Ash.Changeset.for_create(:create, %{})
  |> Ash.create!()
BitHub.Issues.Issue
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()

Now create an Issue with a title

Show Solution

  BitHub.Issues.Issue
  |> Ash.Changeset.for_create(:create, %{title: "This is the title"})
  |> Ash.create!()
BitHub.Issues.Issue
|> Ash.Changeset.for_create(:create, %{title: "This is the title"})
|> Ash.create!()

Now create an Issue with the state set to :closed

Show Solution

  BitHub.Issues.Issue
  |> Ash.Changeset.for_create(:create, %{title: "This is the title", state: :closed})
  |> Ash.create!()
BitHub.Issues.Issue
|> Ash.Changeset.for_create(:create, %{title: "This is the title", state: :closed})
|> Ash.create!()

Latest created Issue

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

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

  BitHub.Issues.Issue
  |> Ash.Query.sort(created_at: :desc)
  |> Ash.Query.limit(1)
  |> Ash.read_one!()
BitHub.Issues.Issue
|> Ash.Query.sort(created_at: :desc)
|> Ash.Query.limit(1)
|> Ash.read_one!()
Querying HomeCustomizing Actions