WIP: on guides

This commit is contained in:
Zach Daniel 2022-08-18 21:25:45 -04:00
parent 87eca9fdcd
commit f678c585d6
5 changed files with 272 additions and 177 deletions

View file

@ -101,7 +101,7 @@ defmodule Ash.Api.Dsl do
""" """
] ]
], ],
modules: [:registry] no_depend_modules: [:registry]
} }
@sections [@resources, @execution, @authorization] @sections [@resources, @execution, @authorization]

View file

@ -0,0 +1,270 @@
# Getting Started
## Goals
In this guide we will:
1. Create a new Elixir application, and add Ash as a dependency
2. Create a simple set of resources and show they can be used
3. Illustrate some core concepts of Ash, and how you can leverage them
4. Point you to good next resources so you can explore Ash further
## Things you may want to read first
- [Install Elixir](https://elixir-lang.org/install.html)
- {{ash:guide:Philosophy}}
## Requirements
If you want to follow along yourself, you will need the following things:
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`
## Steps
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.Tickets.Customer`
- `Helpdesk.Tickets.Comment`
The actions we will be able to take on these resources include:
- Opening a new ticket
- Closing a ticket
- Assigning a ticket to a representative
- Commenting on a ticket
### Create a new project
We first create a new project with the `--sup` flag to add a supervision tree. This will be necessary for later steps.
```bash
# In your terminal
mix new --sup helpdesk && cd helpdesk
```
It is a good idea to make it a git repository and commit the initial project. You'll be able to see what changes we made, and can save your changes once we're done.
```bash
# Run in your terminal
git init
git add -A
git commit -m "init"
```
Open the project in your text editor, and we'll get started.
### Add Ash to your application
Add the ash dependency to your `mix.exs`
{{mix_dep:ash}}
Add ash to your .formatter.exs file
```elixir
[
# import the formatter rules from ash
import_deps: [:ash],
inputs: [...]
]
```
And run `mix deps.get`
### Creating our first resources
The basic building blocks of an Ash application are resources. They are tied together by an API module (not to be confused with a web API), which will allow you to interact with those resources.
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/ticket.ex`.
We also create an accompanying registry, in , which is where we will list the resources for our Api.
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
```
Add the following to the files we created
```elixir
# lib/helpdesk/tickets/resources/ticket.ex
defmodule Helpdesk.Tickets.Ticket do
# This turns this module into a resource
use Ash.Resource
actions do
# Add a set of simple actions. You'll customize these later.
defaults [:create, :read, :update, :destroy]
end
# Attributes are the simple pieces of data that exist on your resource
attributes do
# Add an autogenerated UUID primary key called `:id`.
uuid_primary_key :id
# Add a string type attribute called `:subject`
attribute :subject, :string
end
end
```
```elixir
# lib/helpdesk/tickets/registry.ex
defmodule Helpdesk.Tickets.Registry do
use Ash.Registry,
extensions: [
# This extension adds helpful compile time validations
Ash.Registry.ResourceValidations
]
entries do
entry Helpdesk.Tickets.Ticket
end
end
```
```elixir
# lib/helpdesk/tickets.ex
defmodule Helpdesk.Tickets do
use Ash.Api
resources do
# This defines the set of resources that can be used with this API
registry Helpdesk.Tickets.Registry
end
end
```
### Try our first resource out
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`.
```elixir
Helpdesk.Tickets.Ticket
|> Ash.Changeset.for_create(:create)
|> Helpdesk.Tickets.create!()
```
This returns what we call a `record` which is an instance of a resource.
```elixir
{:ok, #Helpdesk.Tickets.Ticket<
...,
id: "c0f8dc32-a018-4eb4-8656-d5810118f4ea",
subject: nil,
...
>}
```
In this form, 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)
```
Which will result in nothing being returned.
```elixir
{:ok, []}
```
Later on, we will discuss adding a {{ash:guide:Data Layers:Data Layer}} to your resources to achieve persistence. For now, however, we will focus on prototyping our resources and what we can add to them.
### Customizing our Actions
One thing you may have noticed earlier is that we created a ticket without providing any input, and as a result our ticket had a `subject` of `nil`. Additionally, we don't have any other data on the ticket. Lets add a `status` attribute, ensure that `subject` can't be `nil`, and provide a better interface by making a custom action for opening a ticket, called `:open`.
We'll start with the attribute changes:
```elixir
attributes do
...
attribute :subject, :string do
# Don't allow `nil` values
allow_nil? false
end
# status is either `open` or `closed`. We can add more statuses later
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]]
# The status defaulting to open makes sense
default :open
# We also don't want status to ever be `nil`
allow_nil? false
end
end
```
And then add our customized action:
```elixir
actions do
...
create :open do
# By default you can provide all public attributes to an action
# This action should only accept the subject
accept [:subject]
end
end
```
Now we can play with these changes in iex:
We use `create!` with an exclamation point here because that will raise the error which gives a nicer view of the error in `iex`
```elixir
# Use this to pick up changes you've made to your code, or restart your session
recompile()
Helpdesk.Tickets.Ticket
|> Ash.Changeset.for_create(:open, %{subject: "My mouse won't click!"})
|> Helpdesk.Tickets.create!()
```
And we can see our newly created ticket with a subject and a status.
```elixir
#Helpdesk.Tickets.Ticket<
...
id: "3c94d310-7b5e-41f0-9104-5b193b831a5d",
status: :open,
subject: "My mouse won't click!",
...
>
```
If we didn't include a subject, or left out the input, we would see an error instead
```text
** (Ash.Error.Invalid) Input Invalid
* attribute subject is required
```

View file

@ -0,0 +1 @@
Philosophy

View file

@ -1,176 +0,0 @@
# Quick Start
## NOTICE
This guide is old, and will be replaced soon. Once the generators are done. At that point, the quick start will look something like
- `mix new app_name` or `mix phx.new app_name`
- Add the ash dependency `{{mix_dep:ash}}`
- Run `mix ash.init`
- Generate an api `mix ash.gen.api ApiName`
- Generate a resource `mix ash.gen.resource ApiName ResourceName`
## Start Here
The first step is to decide if you're building a phoenix application or not. Phoenix is an extremely high quality web framework, and is the suggested pairing with Ash if you expect to be building a web front end, or an API. For this guide, we assume that elixir has already been installed. We will be using a "helpdesk" example throughout the documentation, so if you want to play along with your own application, you'll need to replace various names.
## Installing With Phoenix
Install the Phoenix installation archive and then create a new Phoenix application. Be sure to look over the options available with `mix help phx.new`, and visit the phoenix [Phoenix Documentation](https://www.phoenixframework.org/) for more information.
```bash
mix archive.install hex phx_new
mix phx.new cool_desk --live
```
## Installing Without Phoenix
Create a new application. Be sure to look aver the options available with `mix help new`.
```bash
mix new cool_desk
```
## Adding Ash
1. First off, add Ash as a dependency. In `mix.exs`, add
`{:ash, "~> 1.52.0-rc.0"}` to your dependencies.
2. Next, add an API. Create `lib/cool_desk/tickets/tickets.ex`, with the following contents:
```elixir
defmodule CoolDesk.Tickets do
use Ash.Api, otp_app: :cool_desk
end
```
3. Add a Registry. A registry is where you list the resources that a given Api has access to. Create `lib/cool_desk/tickets/registry.ex` with the following contents:
```elixir
defmodule CoolDesk.Tickets.Registry do
use Ash.Registry,
extensions: [Ash.Registry.ResourceValidations]
entries do
# We will list our resources here
end
end
```
4. Configure your application. Add the following to `config/config.exs`.
```elixir
# Configure the list of APIs in your application.
config :cool_desk, ash_apis: [
CoolDesk.Tickets
]
# Configure the registry to be used for your first API
# Storing this in configuration instead of the API improves
# compile times.
config :my_app, CoolDesk.Tickets,
resources: [
registry: CoolDesk.Tickets.Registry
]
```
5. Define your first resource. Place it at `lib/cool_desk/tickets/resources/ticket.ex`.
```elixir
defmodule CoolDesk.Tickets.Ticket do
use Ash.Resource, data_layer: Ash.DataLayer.Ets
# For now, we will use the `Ets` data layer, which is builtin and is very useful for quick prototyping.
# Data is stored in memory and will be lost when the app restarts.
attributes do
# We generally recommend using UUIDs, but you can
# also use `integer_primary_key :id`, or simply define
# your own with:
#
# `attribute :name, :type, primary_key?: true`
uuid_primary_key :id
# Add the attributes of your resource. For example,
# A "User" might have a `username` and an `email` attribute,
# or a "BlogPost" might have a `body` attribute
attribute :subject, :string
end
end
```
6. Add your resource to the registry that you created
```elixir
entries do
entry Cooldesk.Tickets.Ticket
end
```
7. Resources are static descriptions of behavior, and don't do anything on their own. To give them functionality, we must first add [actions](../concepts/actions.md), and then we will "invoke" those actions through an Api module. The simplest way to start is to use the [defaults](ash-hq.org/docs/dsl/ash/latest/resource/actions/read) option to create the four default actions. Add the following to your resource.
```elixir
actions do
defaults [:create, :read, :update, :destroy]
end
```
8. Try it out in iex by running your app with `iex -S mix`. Here are some examples:
Create two tickets:
```elixir
CoolDesk.Tickets.Ticket
|> Ash.Changeset.for_create(
:create,
%{subject: "My computer fell into Mount Doom."}
)
|> CoolDesk.Tickets.create!()
```
List your tickets
```elixir
CoolDesk.Tickets.read!(CoolDesk.Tickets)
```
Find tickets matching a filter
```elixir
require Ash.Query
CoolDesk.Tickets
|> Ash.Query.filter(contains(subject, "Mount Doom"))
|> CoolDesk.Tickets.read!()
```
Update a ticket
```elixir
ticket =
CoolDesk.Tickets.Ticket
|> Ash.Changeset.for_create(
:create,
%{subject: "My computer fell into Mount Doom."}
)
|> CoolDesk.Tickets.create!()
ticket
|> Ash.Changeset.for_update(
:update,
%{subject: "I threw my computer into Mount Doom."}
)
|> CoolDesk.Tickets.update!()
```
Or destroy it
```elixir
CoolDesk.Tickets.destroy!(ticket)
```
9. More resources
- Back your resources with data using [Data Layers](../concepts/data-layers.md)
- Make more complex [actions](../concepts/actions.md)
- View the [Overview & Architecture Diagrams](../guides/overview.md)