310 lines
7.6 KiB
Markdown
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>
|