View Source Getting Started

goals

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
  4. Point you to good next resources so you can explore Ash further

things-you-may-want-to-read-first

Things you may want to read first

requirements

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

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

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.

# 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.

# 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 Ash to your application

Add the ash dependency to your mix.exs

{{mix_dep:ash}}

Add ash to your .formatter.exs file

[
  # import the formatter rules from ash
  import_deps: [:ash],
  inputs: [...]
]

And run mix deps.get

creating-our-first-resources

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:

# 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

# 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
# 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
# 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

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.

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.

{: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:

Helpdesk.Tickets.read(Helpdesk.Tickets.Ticket)

Which will result in nothing being returned.

{: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

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:

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:

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

# 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.

#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

** (Ash.Error.Invalid) Input Invalid

* attribute subject is required