mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 13:33:20 +12:00
WIP: on guides
This commit is contained in:
parent
87eca9fdcd
commit
f678c585d6
5 changed files with 272 additions and 177 deletions
|
@ -101,7 +101,7 @@ defmodule Ash.Api.Dsl do
|
||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
modules: [:registry]
|
no_depend_modules: [:registry]
|
||||||
}
|
}
|
||||||
|
|
||||||
@sections [@resources, @execution, @authorization]
|
@sections [@resources, @execution, @authorization]
|
||||||
|
|
0
priv/documentation/how_to/structure_a_project.md
Normal file
0
priv/documentation/how_to/structure_a_project.md
Normal file
270
priv/documentation/tutorials/get-started.md
Normal file
270
priv/documentation/tutorials/get-started.md
Normal 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
|
||||||
|
```
|
1
priv/documentation/tutorials/philosophy.md
Normal file
1
priv/documentation/tutorials/philosophy.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Philosophy
|
|
@ -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)
|
|
Loading…
Reference in a new issue