docs: change Api to Helpdesk.Support and add file comments (#378)

This commit is contained in:
brettkolodny 2022-09-13 12:35:29 -04:00 committed by GitHub
parent 9e99ce10bd
commit 9b52c94ce9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,11 +1,13 @@
# Get Started
<!--- ash-hq-hide-start --> <!--- -->
If you are reading this on hexdocs, you may notice a few strange things.
Primarily, that there are a lot of templates, like {{link:...}}
This documentation is best viewed at [ash-hq.org](https://ash-hq.org)
<!--- ash-hq-hide-stop --> <!--- -->
## Goals
@ -26,7 +28,7 @@ In this guide we will:
If you want to follow along yourself, you will need the following things:
1. Elixir and Erlang installed
1. Elixir and Erlang installed
2. A text editor to make the changes that we make
3. A terminal to run the commands we show using `iex`
@ -36,8 +38,8 @@ For this tutorial, we'll use examples based around creating a help desk.
We will make the following resources:
- `Helpdesk.Tickets.Ticket`
- `Helpdesk.Tickets.Representative`
- `Helpdesk.Support.Ticket`
- `Helpdesk.Support.Representative`
The actions we will be able to take on these resources include:
@ -95,9 +97,9 @@ The basic building blocks of an Ash application are resources. They are tied tog
Lets start by creating our first resource along with our first API. We will create the following files:
- The API - `lib/helpdesk/tickets.ex`
- A registry to list our resources - `lib/helpdesk/tickets/registry.ex`
- Our tickets resource - `lib/helpdesk/tickets/resources/tickets/ticket.ex`.
- The API - `lib/helpdesk/support.ex`
- A registry to list our resources - `lib/helpdesk/support/registry.ex`
- Our tickets resource - `lib/helpdesk/support/resources/tickets/ticket.ex`.
We also create an accompanying registry, in , which is where we will list the resources for our Api.
@ -106,16 +108,28 @@ To create the required folders and files, you can use the following command:
```bash
# Run in your terminal
touch lib/helpdesk/tickets.ex
mkdir -p lib/helpdesk/tickets/resources && touch $_/ticket.ex
touch lib/helpdesk/tickets/registry.ex
mkdir -p lib/helpdesk/support/resources && touch $_/ticket.ex
touch lib/helpdesk/support/registry.ex
```
Your project structure should now look like this:
```
lib/
├─ helpdesk/
│ ├─ support/
│ │ ├─ registry.ex
│ │ ├─ resources/
│ │ │ ├─ ticket.ex
│ ├─ support.ex
```
Add the following to the files we created
```elixir
# lib/helpdesk/tickets/resources/ticket.ex
# lib/helpdesk/support/resources/ticket.ex
defmodule Helpdesk.Tickets.Ticket do
defmodule Helpdesk.Support.Ticket do
# This turns this module into a resource
use Ash.Resource
@ -136,9 +150,9 @@ end
```
```elixir
# lib/helpdesk/tickets/registry.ex
# lib/helpdesk/support/registry.ex
defmodule Helpdesk.Tickets.Registry do
defmodule Helpdesk.Support.Registry do
use Ash.Registry,
extensions: [
# This extension adds helpful compile time validations
@ -146,20 +160,20 @@ defmodule Helpdesk.Tickets.Registry do
]
entries do
entry Helpdesk.Tickets.Ticket
entry Helpdesk.Support.Ticket
end
end
```
```elixir
# lib/helpdesk/tickets.ex
# lib/helpdesk/support.ex
defmodule Helpdesk.Tickets do
defmodule Helpdesk.Support do
use Ash.Api
resources do
# This defines the set of resources that can be used with this API
registry Helpdesk.Tickets.Registry
registry Helpdesk.Support.Registry
end
end
```
@ -168,18 +182,18 @@ end
Run `iex -S mix` in your project and try it out
To create a ticket, we first make an `Ash.Changeset` for the `:create` action of the `Helpdesk.Tickets.Ticket` resource. Then we pass it to the `create!/1` function on our API module `Helpdesk.Tickets`.
To create a ticket, we first make an `Ash.Changeset` for the `:create` action of the `Helpdesk.Support.Ticket` resource. Then we pass it to the `create!/1` function on our API module `Helpdesk.Support`.
```elixir
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:create)
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
```
This returns what we call a `record` which is an instance of a resource.
```elixir
{:ok, #Helpdesk.Tickets.Ticket<
{:ok, #Helpdesk.Support.Ticket<
...,
id: "c0f8dc32-a018-4eb4-8656-d5810118f4ea",
subject: nil,
@ -194,6 +208,8 @@ One thing you may have noticed earlier is that we created a ticket without provi
We'll start with the attribute changes:
```elixir
# lib/helpdesk/support/resources/ticket.ex
attributes do
...
attribute :subject, :string do
@ -222,6 +238,8 @@ end
And then add our customized action:
```elixir
# lib/helpdesk/support/resources/ticket.ex
actions do
...
create :open do
@ -238,17 +256,17 @@ We use `create!` with an exclamation point here because that will raise the erro
```elixir
# Use this to pick up changes you've made to your code, or restart your session
recompile()
recompile()
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My mouse won't click!"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
```
And we can see our newly created ticket with a subject and a status.
```elixir
#Helpdesk.Tickets.Ticket<
#Helpdesk.Support.Ticket<
...
id: "3c94d310-7b5e-41f0-9104-5b193b831a5d",
status: :open,
@ -272,11 +290,13 @@ Now lets add some logic to close a ticket. This time we'll add an `update` actio
Here we will use a `change`. Changes allow you to customize how an action executes with very fine-grained control. There are built-in changes that are automatically available as functions, but you can define your own and pass it in as shown below. You can add multiple, and they will be run in order. See the {{link:ash:guide:Actions}} guides for more.
```elixir
# lib/helpdesk/support/resources/ticket.ex
actions do
...
update :close do
# We don't want to accept any input here
accept []
accept []
change set_attribute(:status, :closed)
# A custom change could be added like so:
@ -292,16 +312,16 @@ Now we can try it out in iex, opening a ticket and closing it:
```elixir
# parenthesis so you can paste into iex
ticket = (
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My mouse won't click!"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
)
ticket
|> Ash.Changeset.for_update(:close)
|> Helpdesk.Tickets.update!()
|> Helpdesk.Support.update!()
#Helpdesk.Tickets.Ticket<
#Helpdesk.Support.Ticket<
...
status: :closed,
subject: "My mouse won't click!",
@ -314,7 +334,7 @@ ticket
So far, there is no persistence happening. All that this simple resource does is return the record back to us. You can see this lack of persistence by attempting to use a `read` action:
```elixir
Helpdesk.Tickets.read!(Helpdesk.Tickets.Ticket)
Helpdesk.Support.read!(Helpdesk.Support.Ticket)
```
Which will raise an error explaining that there is no data to be read for that resource.
@ -327,17 +347,17 @@ Try the following in iex. We will open some tickets, and close some of them, and
# Ash.Query is a macro, so it must be required
require Ash.Query
tickets =
tickets =
for i <- 0..5 do
ticket =
Helpdesk.Tickets.Ticket
ticket =
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "Issue #{i}"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
if rem(i, 2) == 0 do
ticket
|> Ash.Changeset.for_update(:close)
|> Helpdesk.Tickets.update!()
|> Helpdesk.Support.update!()
else
ticket
end
@ -345,16 +365,16 @@ tickets =
# Show the tickets where the subject contains "2"
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Query.filter(contains(subject, "2"))
|> Ash.DataLayer.Simple.set_data(tickets)
|> Helpdesk.Tickets.read!()
|> Helpdesk.Support.read!()
# Show the tickets that are closed and their subject does not contain "4"
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Query.filter(status == :closed and not(contains(subject, "4")))
|> Ash.DataLayer.Simple.set_data(tickets)
|> Helpdesk.Tickets.read!()
|> Helpdesk.Support.read!()
```
The examples shown here could be implemented easily using things like `Enum.filter`, but the real power here is to allow you to use the same tools when working with any data layer. If you were using AshPostgres, the above code would be exactly the same, except for the call to `set_data/2`.
@ -378,27 +398,27 @@ Now we can slightly modify our code above, by removing the `Ash.DataLayer.Simple
require Ash.Query
for i <- 0..5 do
ticket =
Helpdesk.Tickets.Ticket
ticket =
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "Issue #{i}"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
if rem(i, 2) == 0 do
ticket
|> Ash.Changeset.for_update(:close)
|> Helpdesk.Tickets.update!()
|> Helpdesk.Support.update!()
end
end
# Show the tickets where the subject contains "2"
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Query.filter(contains(subject, "2"))
|> Helpdesk.Tickets.read!()
|> Helpdesk.Support.read!()
# Show the tickets that are closed and their subject does not contain "4"
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Query.filter(status == :closed and not(contains(subject, "4")))
|> Helpdesk.Tickets.read!()
|> Helpdesk.Support.read!()
```
### Adding relationships
@ -406,9 +426,9 @@ Helpdesk.Tickets.Ticket
Now we want to be able to assign a ticket to a representative. First, lets create the representative resource:
```elixir
# lib/helpdesk/tickets/resources/representative.ex
# lib/helpdesk/support/resources/representative.ex
defmodule Helpdesk.Tickets.Representative do
defmodule Helpdesk.Support.Representative do
# This turns this module into a resource
use Ash.Resource,
data_layer: Ash.DataLayer.Ets
@ -431,7 +451,7 @@ defmodule Helpdesk.Tickets.Representative do
# has_many means that the destination attribute is not unique, meaning many related records could exist.
# We assume that the destination attribute is `representative_id` based
# on the module name of this resource and that the source attribute is `id`.
has_many :tickets, Helpdesk.Tickets.Ticket
has_many :tickets, Helpdesk.Support.Ticket
end
end
```
@ -439,25 +459,25 @@ end
And lets modify our tickets resource to have a relationship to the representative
```elixir
# lib/helpdesk/tickets/resources/ticket.ex
# lib/helpdesk/support/resources/ticket.ex
relationships do
# belongs_to means that the destination attribute is unique, meaning only one related record could exist.
# We assume that the destination attribute is `representative_id` based
# on the name of this relationship and that the source attribute is `representative_id`.
# We create `representative_id` automatically.
belongs_to :representative, Helpdesk.Tickets.Representative
belongs_to :representative, Helpdesk.Support.Representative
end
```
Finally, lets add our new resource to our registry
```elixir
# lib/helpdesk/tickets/registry.ex
# lib/helpdesk/support/registry.ex
entries do
...
entry Helpdesk.Tickets.Representative
entry Helpdesk.Support.Representative
end
```
@ -465,11 +485,13 @@ You may notice that if you don't add the resource to the registry, or if you don
## Working with relationships
There are a wide array of options when managing relationships, and going over all of them here wouldn't be reasonable. See the guide on {{link:ash:guide:Managing Relationships}} for a full explanation. For now, we'll show a simple example. Add the following action to allow us to assign a ticket to a representative.
There are a wide array of options when managing relationships, and going over all of them here wouldn't be reasonable. See the guide on {{link:ash:guide:Managing Relationships}} for a full explanation. For now, we'll show a simple example. Add the following action to allow us to assign a ticket to a representative.
Here we also show the use of action arguments, the method by which you can accept additional input to an action.
```elixir
# lib/helpdesk/support/resources/ticket.ex
update :assign do
# No attributes should be accepted
accept []
@ -492,23 +514,23 @@ Lets try it out!
```elixir
# Open a ticket
ticket = (
Helpdesk.Tickets.Ticket
Helpdesk.Support.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "I can't find my hand!"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
)
# Create a representative
representative = (
Helpdesk.Tickets.Representative
Helpdesk.Support.Representative
|> Ash.Changeset.for_create(:create, %{name: "Joe Armstrong"})
|> Helpdesk.Tickets.create!()
|> Helpdesk.Support.create!()
)
# Assign that representative
ticket = (
ticket
|> Ash.Changeset.for_update(:assign, %{representative_id: representative.id})
|> Helpdesk.Tickets.update!()
|> Helpdesk.Support.update!()
)
```
@ -518,7 +540,7 @@ What you've seen above constitutes some very simple usage of Ash, barely scratch
#### Clean up your code that uses Ash?
Creating and using changesets can be verbose. Check out the {{link:ash:guide:Code Interface}} to derive things like `Helpdesk.Tickets.Ticket.assign!(representative.id)`
Creating and using changesets can be verbose. Check out the {{link:ash:guide:Code Interface}} to derive things like `Helpdesk.Support.Ticket.assign!(representative.id)`
#### Persist your data