Powered by AppSignal & Oban Pro

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 Home Customizing 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 ```elixir 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 ```elixir 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 ```elixir 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 ```elixir 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 ```elixir 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 Home Customizing Actions