add ash_tutorial
This commit is contained in:
309
ash_tutorial/attributes.livemd
Normal file
309
ash_tutorial/attributes.livemd
Normal file
@@ -0,0 +1,309 @@
|
||||
# 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>
|
||||
Reference in New Issue
Block a user