Files
livebook/ash_tutorial/attributes.livemd
2025-09-29 19:25:33 +08:00

310 lines
7.6 KiB
Markdown

# Ash: 4 - Attributes
```elixir
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
<div class="flex items-center w-full flex-start justify-between rounded-xl p-4" style="background-color: #f0f5f9; color: #61758a;">
<div class="flex">
<i class="ri-arrow-left-fill"></i>
<a class="flex ml-2" style="color: #61758a;" href="querying.livemd">Querying</a>
</div>
<div class="flex">
<i class="ri-home-fill"></i>
<a class="flex ml-2" style="color: #61758a;" href="overview.livemd">Home</a>
</div>
<div class="flex">
<a class="flex mr-2" style="color: #61758a;" href="customizing_actions.livemd">Customizing Actions</a>
<i class="ri-arrow-right-fill"></i>
</div>
</div>
### 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:
<!-- livebook:{"force_markdown":true} -->
```elixir
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](https://hexdocs.pm/ash/dsl-ash-resource.html#attributes-create_timestamp) and [update_timestamp](https://hexdocs.pm/ash/dsl-ash-resource.html#attributes-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:
<!-- livebook:{"force_markdown":true} -->
```elixir
create_timestamp :created_at
update_timestamp :updated_at
```
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
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
```
</div>
</details>
### Enter your solution
```elixir
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
# - Description
# - Status
# - Create Timestamp
# - Update Timestamp
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
```
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{})
|> Ash.create!()
```
</div>
</details>
```elixir
```
Now create a Ticket with a `subject`
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "This is the subject"})
|> Ash.create!()
```
</div>
</details>
```elixir
```
Now create a ticket with the `status` set to `:closed`
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :closed})
|> Ash.create!()
```
</div>
</details>
```elixir
```
Now try creating a ticket with the status set to `:pending`. This should give an error because `:pending` is not a valid `status`.
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
Tutorial.Support.Ticket
|> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :pending})
|> Ash.create!()
```
</div>
</details>
```elixir
```
## 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*
<details class="rounded-lg my-4" style="background-color: #96ef86; color: #040604;">
<summary class="cursor-pointer font-bold p-4"></i>Show Solution</summary>
<div class="p-4">
```elixir
Tutorial.Support.Ticket
|> Ash.Query.sort(created_at: :desc)
|> Ash.Query.limit(1)
|> Ash.read_one!()
```
</div>
</details>
```elixir
```
<div class="flex items-center w-full flex-start justify-between rounded-xl p-4" style="background-color: #f0f5f9; color: #61758a;">
<div class="flex">
<i class="ri-arrow-left-fill"></i>
<a class="flex ml-2" style="color: #61758a;" href="querying.livemd">Querying</a>
</div>
<div class="flex">
<i class="ri-home-fill"></i>
<a class="flex ml-2" style="color: #61758a;" href="overview.livemd">Home</a>
</div>
<div class="flex">
<a class="flex mr-2" style="color: #61758a;" href="customizing_actions.livemd">Customizing Actions</a>
<i class="ri-arrow-right-fill"></i>
</div>
</div>