diff --git a/ash_tutorial/README.MD b/ash_tutorial/README.MD
new file mode 100644
index 0000000..48c273f
--- /dev/null
+++ b/ash_tutorial/README.MD
@@ -0,0 +1,9 @@
+# Get Started
+
+To get started you need a running instance of [Livebook](https://livebook.dev/)
+
+[](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fwoutdp%2Fash_tutorial%2Fblob%2Fmaster%2Foverview.livemd)
+
+## Contributions
+
+Anything missing? Any errors? Tutorials out of date? Contributions are welcome!
diff --git a/ash_tutorial/actions.livemd b/ash_tutorial/actions.livemd
new file mode 100644
index 0000000..3a4ea96
--- /dev/null
+++ b/ash_tutorial/actions.livemd
@@ -0,0 +1,93 @@
+# Ash: 2 - Actions
+
+```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)
+```
+
+## What is an Action?
+
+
+
+
+
+Actions are much simpler: an action describes a CRUD operation that can be performed on a resource, but using whatever language makes the most sense for your given resource. For example, you may have a `publish` action on a `BlogPost` resource that changes the resource to be publicly visible. Under CRUD, this would be an `update` operation, but calling it the "publish action" more fully conveys what the operation does.
+
+An action is defined on a resource in an `actions do ... end` block.
+
+
+
+```elixir
+ actions do
+ create :create do
+ accept [:name]
+ end
+ end
+```
+
+
+
+Adding that to our `Resource` module from earlier,
+
+```elixir
+defmodule Tutorial.Profile do
+ use Ash.Resource,
+ domain: Tutorial
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+
+ actions do
+ create :create do
+ accept [:name]
+ end
+ end
+end
+
+defmodule Tutorial do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Profile
+ end
+end
+```
+
+We can now use `for_create/3` to create a changeset.
+
+```elixir
+Tutorial.Profile
+|> Ash.Changeset.for_create(:create, %{name: "My Name"})
+```
+
+
diff --git a/ash_tutorial/aggregates.livemd b/ash_tutorial/aggregates.livemd
new file mode 100644
index 0000000..3ed991f
--- /dev/null
+++ b/ash_tutorial/aggregates.livemd
@@ -0,0 +1,210 @@
+# Ash: 8 - Aggregates
+
+```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)
+```
+
+## Aggregates
+
+
+
+### In this tutorial you will add an Aggregate on a Resource
+
+[Aggregates](https://hexdocs.pm/ash/aggregates.html) in Ash allow for retrieving summary information over groups of related data. Aggregates can be either _count_, _first_, _sum_ or _list_.
+
+Implement a count aggregate on the 'open tickets' a representative is assigned to.
+
+First explore the comments in the code below, to make this work properly you have to add the inverse relationship of belongs_to inside the Representative resource.
+
+To add an aggregate define an `aggregates do .. end` block inside the representative.
+
+Inside the `aggregates` block, define a `count :count_of_open_tickets, :tickets do .. end` block.
+
+Then inside this block, define a filter like so: `filter expr(status == :open)`
+
+Also, notice what happens when you don't define a filter.
+
+
+ Show Solution
+
+
+ ```elixir
+ aggregates do
+ count :count_of_open_tickets, :tickets do
+ filter expr(status == :open)
+ end
+ end
+ ```
+
+
+
+
+### Enter your solution
+
+```elixir
+defmodule Tutorial.Support.Ticket do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ # On creation set the representative by providing the id
+ create :open do
+ accept [:subject, :description, :representative_id]
+ end
+
+ update :close do
+ accept []
+ change set_attribute(:status, :closed)
+ end
+
+ update :assign do
+ accept [:representative_id]
+ end
+ end
+
+ attributes do
+ uuid_primary_key(:id)
+ attribute :subject, :string, allow_nil?: false
+ attribute :description, :string, allow_nil?: true
+
+ attribute :status, :atom do
+ constraints one_of: [:open, :closed]
+ default :open
+ allow_nil? false
+ end
+
+ create_timestamp :created_at
+ update_timestamp :updated_at
+ end
+
+ relationships do
+ belongs_to :representative, Tutorial.Support.Representative
+ end
+
+ code_interface do
+ define :assign, args: [:representative_id]
+ # <- added representative_id
+ define :open, args: [:subject, :description, :representative_id]
+ define :close, args: []
+ end
+end
+
+defmodule Tutorial.Support.Representative do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ create :create do
+ accept [:name]
+ end
+ end
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+
+ # Added the inverse relationship of belongs_to in the ticket.
+ # This way we can reference :tickets inside the aggregates.
+ relationships do
+ has_many :tickets, Tutorial.Support.Ticket
+ end
+
+ code_interface do
+ define :create, args: [:name]
+ end
+
+ # <- Add the aggregates here
+end
+```
+
+```elixir
+defmodule Tutorial.Support do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Support.Ticket
+ resource Tutorial.Support.Representative
+ end
+end
+```
+
+## Query the Aggregate
+
+Use a [Bulk Create](https://hexdocs.pm/ash/create-actions.html#bulk-creates) to create 4 tickets, 3 of which are assigned to Joe.
+
+```elixir
+# Create a representative
+joe = Tutorial.Support.Representative.create!("Joe Armstrong")
+
+# Bulk create 4 tickets, 3 of which are assigned to Joe
+[
+ %{subject: "I can't see my eyes!"},
+ %{subject: "I can't find my hand!", representative_id: joe.id},
+ %{subject: "My fridge is flying away!", representative_id: joe.id},
+ %{subject: "My bed is invisible!", representative_id: joe.id}
+]
+|> Ash.bulk_create(Tutorial.Support.Ticket, :open, return_records?: true)
+```
+
+Close the last ticket assigned to Joe.
+
+```elixir
+require Ash.Query
+
+# Retrieve the last ticket
+[last_ticket] =
+ Tutorial.Support.Ticket
+ |> Ash.Query.filter(representative_id == ^joe.id)
+ |> Ash.Query.sort(created_at: :desc)
+ |> Ash.Query.limit(1)
+ |> Ash.read!()
+
+# Close the last ticket
+Tutorial.Support.Ticket.close!(last_ticket)
+```
+
+```elixir
+joe = Ash.load!(joe, [:count_of_open_tickets])
+joe.count_of_open_tickets
+```
+
+The result should be __2__, as you opened 4 tickets, 3 were assigned to Joe, but the last assigned ticket was closed.
+
+
+
+
diff --git a/ash_tutorial/attributes.livemd b/ash_tutorial/attributes.livemd
new file mode 100644
index 0000000..86ad9e0
--- /dev/null
+++ b/ash_tutorial/attributes.livemd
@@ -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
+
+
+
+### 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:
+
+
+
+```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:
+
+
+
+```elixir
+create_timestamp :created_at
+update_timestamp :updated_at
+```
+
+
+ Show 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
+
+ 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
+ ```
+
+
+
+
+### 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
+```
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:create, %{})
+ |> Ash.create!()
+ ```
+
+
+
+
+```elixir
+
+```
+
+Now create a Ticket with a `subject`
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:create, %{subject: "This is the subject"})
+ |> Ash.create!()
+ ```
+
+
+
+
+```elixir
+
+```
+
+Now create a ticket with the `status` set to `:closed`
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :closed})
+ |> Ash.create!()
+ ```
+
+
+
+
+```elixir
+
+```
+
+Now try creating a ticket with the status set to `:pending`. This should give an error because `:pending` is not a valid `status`.
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:create, %{subject: "This is the subject", status: :pending})
+ |> Ash.create!()
+ ```
+
+
+
+
+```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*
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Query.sort(created_at: :desc)
+ |> Ash.Query.limit(1)
+ |> Ash.read_one!()
+ ```
+
+
+
+
+```elixir
+
+```
+
+
diff --git a/ash_tutorial/calculations.livemd b/ash_tutorial/calculations.livemd
new file mode 100644
index 0000000..0d0d157
--- /dev/null
+++ b/ash_tutorial/calculations.livemd
@@ -0,0 +1,113 @@
+# Ash: 9 - Calculations
+
+```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)
+```
+
+## Calculations
+
+
+
+[Calculations](https://hexdocs.pm/ash/calculations.html) in Ash allow for displaying complex values as a top level value of a resource.
+
+Create a `full_name` calculation on the User resource.
+
+You already have the `first_name` and `last_name` defined, you can get the `full_name` by concatenating them with a space in the middle.
+
+First, add a `calculations do ... end` block.
+
+Then inside this block, add `calculate :full_name, :string, expr(first_name <> " " <> last_name)`
+
+
+ Show Solution
+
+
+ ```elixir
+ calculations do
+ calculate :full_name, :string, expr(first_name <> " " <> last_name)
+ end
+ ```
+
+
+
+
+### Enter your solution
+
+```elixir
+defmodule Tutorial.Accounts.User do
+ use Ash.Resource,
+ domain: Tutorial.Accounts,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults([:read])
+
+ create :create do
+ accept([:first_name, :last_name])
+ end
+ end
+
+ attributes do
+ uuid_primary_key(:id)
+ attribute(:first_name, :string, allow_nil?: false)
+ attribute(:last_name, :string, allow_nil?: false)
+ end
+
+ # <--- Add the calculations here
+end
+```
+
+```elixir
+defmodule Tutorial.Accounts do
+ use Ash.Domain
+
+ resources do
+ resource(Tutorial.Accounts.User)
+ end
+end
+```
+
+## Create a User
+
+Create a User and load in the `full_name`
+
+```elixir
+Tutorial.Accounts.User
+|> Ash.Changeset.for_create(:create, %{first_name: "Joe", last_name: "Armstrong"})
+|> Ash.create!()
+|> Ash.load!([:full_name])
+```
+
+This is the most basic example of a calculation. Note that calculations allow you to do any arbitrary operation on the Resource. More on this inside the [documentation](https://hexdocs.pm/ash/calculations.html).
+
+
+
+
diff --git a/ash_tutorial/code_interfaces.livemd b/ash_tutorial/code_interfaces.livemd
new file mode 100644
index 0000000..7cf1b75
--- /dev/null
+++ b/ash_tutorial/code_interfaces.livemd
@@ -0,0 +1,230 @@
+# Ash: 7 - Code Interfaces
+
+```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)
+```
+
+## Code Interfaces
+
+
+
+### In this tutorial you will add a Code Interface
+
+In previous tutorials you always used `Changesets` directly to CRUD on resources.
+While this is a perfectly valid way of handling CRUD operations, Ash provides [Code Interfaces](https://hexdocs.pm/ash/code-interfaces.html) to make this more ergonomic for you.
+The code interface can be defined on the resources *or* the domain. In this example, we will define it on the domain.
+
+You will add 3 code interfaces for:
+
+* Creating a representative
+* Opening a ticket
+* Assigning a representative
+
+For each resource you want to add a code interface to, inside that resource definition on the domain, you will place "definitions".
+For example:
+
+
+
+```elixir
+resource MyApp.Tweet do
+ define :create_tweet, args: [:content]
+end
+```
+
+
+
+Then add the 3 interfaces by defining the following inside the `Ticket` resource's block:
+
+* `define :assign_ticket, args: [:representative_id], action: :assign`
+* `define :open_ticket, args: [:subject, :description], action: :open`
+
+And the following inside the `Representative` resource's block.
+
+* `define :create_representative, args: [:name], action: :create`
+
+
+ Show Solution
+
+
+ ```elixir
+ resource Tutorial.Support.Ticket do
+ define :assign_ticket, args: [:representative_id], action: :assign
+ define :open_ticket, args: [:subject, :description], action: :open
+ end
+
+ resource Tutorial.Support.Representative do
+ define :create_representative, args: [:name], action: :create
+ end
+ ```
+
+
+
+
+### 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 :open do
+ accept [:subject, :description]
+ end
+
+ update :close do
+ change set_attribute(:status, :closed)
+ end
+
+ update :assign do
+ accept [:representative_id]
+ end
+ end
+
+ attributes do
+ uuid_primary_key :id
+ attribute :subject, :string, allow_nil?: false
+ attribute :description, :string, allow_nil?: true
+
+ attribute :status, :atom do
+ constraints one_of: [:open, :closed]
+ default :open
+ allow_nil? false
+ end
+
+ create_timestamp :created_at
+ update_timestamp :updated_at
+ end
+
+ relationships do
+ belongs_to :representative, Tutorial.Support.Representative
+ end
+end
+
+defmodule Tutorial.Support.Representative do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ create :create do
+ accept [:name]
+ end
+ end
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+end
+
+defmodule Tutorial.Support do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Support.Ticket do
+ # <-- Add ticket code interface here
+ end
+
+ resource Tutorial.Support.Representative do
+ # <-- Add representative code interface here
+ end
+ end
+end
+```
+
+## Using the Code Interfaces
+
+Create a Representative, but instead of using a changeset, use the code interface you created.
+
+You can use a code interface by calling `Tutorial.Support.create_representative!/1` with the desired name.
+
+Set the name to `Joe Armstrong` and store it in a `joe` variable.
+
+
+ Show Solution
+
+
+ ```elixir
+ joe = Tutorial.Support.create_representative!("Joe Armstrong")
+ ```
+
+
+
+
+```elixir
+
+```
+
+Create a Ticket with the created code interface.
+
+Call `Tutorial.Support.open_ticket!/2` with a `subject` and `description` and store the result in a `ticket` variable.
+
+
+ Show Solution
+
+
+ ```elixir
+ ticket = Tutorial.Support.open_ticket!("I can't find my hand!", "I think someone stole it.")
+ ```
+
+
+
+
+```elixir
+
+```
+
+Assign the Representative `joe` to the Ticket using the code Interface.
+
+Use `Tutorial.Support.assign_ticket!/2`.
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.assign_ticket!(ticket, joe.id)
+ ```
+
+
+
+
+```elixir
+
+```
+
+
diff --git a/ash_tutorial/customizing_actions.livemd b/ash_tutorial/customizing_actions.livemd
new file mode 100644
index 0000000..0b9256e
--- /dev/null
+++ b/ash_tutorial/customizing_actions.livemd
@@ -0,0 +1,277 @@
+# Ash: 5 - Customizing Actions
+
+```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)
+```
+
+## Customizing Actions
+
+
+
+### In this tutorial you will add custom Actions on the Ticket resource
+
+Create 2 custom actions, `:open` and `:close`.
+
+Custom actions allow you to attach *semantics* to actions.
+
+* Instead of *Creating* a ticket, you can *Open* a ticket.
+* Instead of *Updating* the status on a ticket you can *Close* it.
+
+In addition, you can customize the behaviour of these actions. For example, closing a ticket only sets the status to `:closed`.
+
+It also allows you to define what attributes can be set when calling that action. For example for the `:open` action, you can only allow the `:subject` and `:description` to be set.
+It does not make sense to set the `:status` in this case as it should always be `:open`. For the closing action you won't allow any attributes to be set.
+
+Custom actions go inside the `actions do ... end` block.
+
+To define the `:open` action, open a `do end` block like so:
+
+
+
+```elixir
+create :open do
+
+end
+```
+
+Same for the `:close` action, but instead of `create`, use `update`.
+
+You then can define the accepted attributes like so:
+
+`accept [:subject, :description]`
+
+Or in the case of the `:close` action:
+
+`accept []`
+
+Then for the `:close` action define a `change`:
+
+`change set_attribute(:status, :closed)`
+
+That's it, you defined your first custom actions.
+
+
+ Show Solution
+
+
+ ```elixir
+ defmodule Tutorial.Support.Ticket do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ create :open do
+ # By default you can provide all public attributes to an action
+ # This action should only accept the subject
+ accept [:subject, :description]
+ end
+
+ update :close do
+ # We don't want to accept any input here
+ accept []
+
+ change set_attribute(:status, :closed)
+ # A custom change could be added like so:
+ #
+ # change MyCustomChange
+ # change {MyCustomChange, opt: :val}
+ end
+ end
+
+ attributes do
+ uuid_primary_key :id
+
+ attribute :subject, :string, allow_nil?: false
+ attribute :description, :string
+ attribute :status, :atom do
+ 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
+ ```
+
+
+
+
+### Enter your solution
+
+```elixir
+defmodule Tutorial.Support.Ticket do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ # <-- Add the :open and :close action
+ end
+
+ attributes do
+ uuid_primary_key(:id)
+
+ attribute :subject, :string, allow_nil?: false
+ attribute :description, :string
+
+ attribute :status, :atom do
+ 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
+```
+
+## Open a Ticket
+
+Open a ticket.
+
+Remember, when creating a resource, use a changeset (`Ash.Changeset.for_create/3`), which gets passed to `Ash.create!/1`. But in this case use the `:open` argument instead of `:create`.
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
+ |> Ash.create!()
+ ```
+
+
+
+
+Try setting the `:status` to `:closed` and see if it works.
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:open, %{subject: "My Subject", status: :closed})
+ |> Ash.create!()
+ ```
+
+
+
+
+The output when trying to set `:status` should look something like this:
+
+```
+** (Ash.Error.Invalid) Input Invalid
+
+* Invalid value provided for status: cannot be changed.
+```
+
+This is because you set the accepted attributes to `:subject` and `:description` only.
+
+**Enter your solution**
+
+```elixir
+
+```
+
+Create a Ticket and store it in the `ticket` variable.
+
+
+ Show Solution
+
+
+ ```elixir
+ ticket =
+ Tutorial.Support.Ticket
+ |> Ash.Changeset.for_create(:open, %{subject: "My Subject"})
+ |> Ash.create!()
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+## Close a Ticket
+
+Close the `ticket` you created in the previous section.
+
+Remember to use `Ash.Changeset.for_update/2` with the `:close` action.
+
+To update use `Ash.update!/1`.
+
+
+ Show Solution
+
+
+ ```elixir
+ ticket
+ |> Ash.Changeset.for_update(:close)
+ |> Ash.update!()
+ ```
+
+
+
+
+```elixir
+
+```
+
+
diff --git a/ash_tutorial/overview.livemd b/ash_tutorial/overview.livemd
new file mode 100644
index 0000000..bb3e4fe
--- /dev/null
+++ b/ash_tutorial/overview.livemd
@@ -0,0 +1,37 @@
+# Ash: 0 - Overview
+
+```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)
+```
+
+## Overview
+
+In this tutorial you will create Ash Resources, query on them and use ETS as your data storage. You won't be building an overarching application, instead you'll build Resources that introduce you to Ash concepts.
+
+Each tutorial builds on top of the previous one, it is therefore recommended to start the tutorial **sequentially from the beginning** as each section introduces new concepts that will later be used.
+
+The best way to follow the tutorial is reading the instructions in the tutorial, coming up with your own solution, and then **verifying with the proposed solution**. It is also recommended to **not copy paste** code.
+
+The goal of this tutorial is to get you familiar with the basic concepts of Ash, and give you a good sense of what's possible. It serves as a primer for the [Ash documentation](https://hexdocs.pm/ash/get-started.html). Feel free to consult this documentation alongside the tutorial to learn more. That being said, the documenation shouldn't be necessary to complete the tutorial.
+
+Have fun!
+
+
+
+1. [Resources](resources.livemd)
+2. [Actions](actions.livemd)
+3. [Querying](querying.livemd)
+4. [Attributes](attributes.livemd)
+5. [Customizing Actions](customizing_actions.livemd)
+6. [Relationships](relationships.livemd)
+7. [Code Interfaces](code_interfaces.livemd)
+8. [Aggregates](aggregates.livemd)
+9. [Calculations](calculations.livemd)
+
+## Contributing?
+
+_Anything missing? Any errors? Tutorials out of date? [Contributions are welcome!](https://github.com/ash-project/ash_tutorial)_
+
+_When contributing, please follow the guidelines in [diataxis](https://diataxis.fr/tutorials/)._
diff --git a/ash_tutorial/querying.livemd b/ash_tutorial/querying.livemd
new file mode 100644
index 0000000..6e795eb
--- /dev/null
+++ b/ash_tutorial/querying.livemd
@@ -0,0 +1,271 @@
+# Ash: 3 - Querying
+
+```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)
+```
+
+## But First, Data Persistence
+
+
+
+
+
+So far we have just created changesets, but we have not actually created any instances of a resource, also known as records. Before we can play around with querying, we need to add a data layer. It is much more common to use [Postgres](https://github.com/ash-project/ash_postgres) in production, but we will use [ETS data layer](https://hexdocs.pm/ash/dsl-ash-datalayer-ets.html#ets) for the remainder of this tutorial to keep dependencies to a minimum.
+
+
+
+```elixir
+ use Ash.Resource,
+ domain: YourDomain,
+ data_layer: Ash.DataLayer.Ets
+```
+
+## In this tutorial you will do basic Query and CRUD operations
+
+But first you need to enable all basic **CRUD** operations.
+
+Do this by adding default `:read` and `:destroy` actions, as well as `:create` and `:update` actions that accept `[:name]`.
+
+
+ Show Solution
+
+
+ ```elixir
+ defmodule Tutorial.Profile do
+ use Ash.Resource,
+ domain: Tutorial,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read, :destroy]
+
+ create :create do
+ accept [:name]
+ end
+
+ update :update do
+ accept [:name]
+ end
+ end
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+ end
+
+ defmodule Tutorial do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Profile
+ end
+ end
+ ```
+
+
+
+
+### Enter your solution
+
+```elixir
+defmodule Tutorial.Profile do
+ use Ash.Resource,
+ domain: Tutorial,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ # <--- Create your actions here
+ end
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+end
+
+defmodule Tutorial do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Profile
+ end
+end
+```
+
+## Creating
+
+Add 2 Profiles to the database.
+
+* One with the name "Joe Armstrong"
+* One with your name
+
+You can create a `Profile` by:
+
+* Creating a Changeset with `Ash.Changeset.for_create(Tutorial.Profile, :create, %{name: "Your Name"})`,
+* Then giving the changeset to `Ash.create!()`.
+
+*Hint: Use a pipeline*
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Profile
+ |> Ash.Changeset.for_create(:create, %{name: "The Name"})
+ |> Ash.create!()
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+## Reading
+
+Now, read all the generated Profiles.
+
+Call `Ash.read!/1` with the `Tutorial.Profile` module.
+
+
+ Show Solution
+
+
+ ```elixir
+ Tutorial.Profile
+ |> Ash.read!()
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+Now fetch the "Joe Armstrong" Profile.
+
+You can achieve this by introducing a filter.
+
+First you'll need to `require Ash.Query`
+
+Then call `Ash.Query.filter(name == "Joe Armstrong")` with the `Tutorial.Profile`.
+
+Put the result into the `joe` variable. `Ash.read!/1` returns a list, so make sure to extract the single returned value out of that list.
+
+
+ Show Solution
+
+
+ ```elixir
+ require Ash.Query
+
+ [joe] =
+ Tutorial.Profile
+ |> Ash.Query.filter(name == "Joe Armstrong")
+ |> Ash.read!()
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+You'll use the `joe` variable in the next sections.
+
+## Updating
+
+Now change `Joe Armstrong`'s name to `Neil Armstrong`.
+
+You can do this by providing the `Ash.Changeset.for_update/3` with:
+
+* the resource you want to change, in this case `joe`
+* the `:update` atom
+* a map of the values you want to change, in this case `%{name: "Neil Armstrong"}`
+
+Then apply the changeset by calling `Ash.update!/1` with the changeset.
+
+*Hint: Using a pipeline might be a good idea.*
+
+
+ Show Solution
+
+
+ ```elixir
+ joe
+ |> Ash.Changeset.for_update(:update, %{name: "Neil Armstrong"})
+ |> Ash.update!()
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+## Destroying
+
+Finally, remove `joe` from the database.
+
+Do this using the `Ash.destroy!/1` function.
+
+
+ Show Solution
+
+
+ ```elixir
+ Ash.destroy!(joe)
+ ```
+
+
+
+
+**Enter your solution**
+
+```elixir
+
+```
+
+
diff --git a/ash_tutorial/relationships.livemd b/ash_tutorial/relationships.livemd
new file mode 100644
index 0000000..042432d
--- /dev/null
+++ b/ash_tutorial/relationships.livemd
@@ -0,0 +1,163 @@
+# Ash: 6 - Relationships
+
+```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)
+```
+
+## Relationships
+
+
+
+### In this tutorial you will create a relationship between a Ticket and Representative resource
+
+Create a `relationships do .. end` block in the Ticket resource.
+
+Inside the `relationships` block, define a `belongs_to` representative like so:
+
+`belongs_to :representative, Tutorial.Support.Representative`
+
+
+ Show Solution
+
+
+ ```elixir
+ defmodule Tutorial.Support.Ticket do
+
+ # ...
+
+ 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, Tutorial.Support.Representative
+ end
+
+ # ...
+ ```
+
+
+
+
+### 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
+ attribute :subject, :string, allow_nil?: false
+ attribute :description, :string
+
+ attribute :status, :atom do
+ constraints one_of: [:open, :closed]
+ default :open
+ allow_nil? false
+ end
+
+ create_timestamp :created_at
+ update_timestamp :updated_at
+ end
+
+ # <-- Add the relationship here
+end
+
+defmodule Tutorial.Support.Representative do
+ use Ash.Resource,
+ domain: Tutorial.Support,
+ data_layer: Ash.DataLayer.Ets
+
+ actions do
+ defaults [:read]
+
+ create :create do
+ accept [:name]
+ end
+ end
+
+ attributes do
+ uuid_primary_key(:id)
+ attribute(:name, :string)
+ end
+end
+```
+
+```elixir
+defmodule Tutorial.Support do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Support.Ticket
+ resource Tutorial.Support.Representative
+ end
+end
+```
+
+## Creating a Ticket record with a representative Relationship
+
+First, create the representative Joe.
+
+```elixir
+# Creates a Representative
+joe =
+ Tutorial.Support.Representative
+ |> Ash.Changeset.for_create(:create, %{name: "Joe Armstrong"})
+ |> Ash.create!()
+```
+
+Next up, create a ticket and assign the representative Joe to the ticket.
+
+```elixir
+# Creates a Ticket with the representative
+Tutorial.Support.Ticket
+|> Ash.Changeset.for_create(:create, %{subject: "My spoon is too big!", representative_id: joe.id})
+|> Ash.create!()
+# `load!/2` loads in the representative. Try removing this line to see what changes
+|> Ash.load!([:representative])
+```
+
+As you can see it didn't quite work. The `representative_id` is not accepted by the create action on `Tutorial.Support.Ticket`. If you add `:representative_id` to that list,
+and try again you should now see that the representative was correctly assigned.
+
+
+
+
diff --git a/ash_tutorial/resources.livemd b/ash_tutorial/resources.livemd
new file mode 100644
index 0000000..cd3a82d
--- /dev/null
+++ b/ash_tutorial/resources.livemd
@@ -0,0 +1,79 @@
+# Ash: 1 - Resources
+
+```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)
+```
+
+## What is a resource?
+
+
+
+
+
+The fundamental building blocks of Ash are resources, actions, and domains. The resource is Ash's central concept, so we need to define it well for anything else to make sense, so let's turn to the [Ash Glossary](https://hexdocs.pm/ash/glossary.html#resource).
+
+> The central concept in Ash, a resource is a domain model object in your system, the nouns that your app revolves around. Resources contain definitions of the data they hold in the form of attributes, but also define actions that can be taken on that data and actors that are allowed to run them.
+>
+> It is not a strict requirement that resources contain data - they can be used purely to create a standard interface for performing tasks - but in practice, most resources will be used to manage data.
+
+A domain simply groups together related resources. Some things can be defined in a domain instead of a resource. We will do this when we get to code interfaces later on.
+
+## Example Code
+
+Here we have a minimal resource called `Profile` that has, aside from its primary key, a single string attribute `name`.
+
+```elixir
+defmodule Tutorial.Profile do
+ use Ash.Resource,
+ domain: Tutorial
+
+ attributes do
+ uuid_primary_key :id
+ attribute :name, :string
+ end
+end
+```
+
+To be able to do anything with this resource, we also need a domain.
+
+```elixir
+defmodule Tutorial do
+ use Ash.Domain
+
+ resources do
+ resource Tutorial.Profile
+ end
+end
+```
+
+Now we can create a changeset for the resource.
+
+```elixir
+Tutorial.Profile
+|> Ash.Changeset.new()
+|> Ash.Changeset.change_attributes(%{name: "John Doe"})
+```
+
+Changesets are used to create and update data in Ash. (**Note:** without a data layer, you won't be able to persist creations and changes. This will be shown in a later tutorial.)
+
+
diff --git a/elixir_koan.livemd b/elixir_koan.livemd
new file mode 100644
index 0000000..5679054
--- /dev/null
+++ b/elixir_koan.livemd
@@ -0,0 +1,59 @@
+# elixir_koan
+
+## Section
+
+```elixir
+Integer.parse("2A", 16)
+```
+
+```elixir
+Integer.parse("5 years")
+```
+
+```elixir
+Integer.parse("1.2")
+```
+
+```elixir
+Float.parse("34.5")
+```
+
+```elixir
+Float.parse("1.5 million dollars")
+```
+
+```elixir
+Float.ceil(34.25)
+```
+
+```elixir
+Float.ceil(34.25, 1)
+```
+
+```elixir
+Float.floor(99.99)
+```
+
+```elixir
+Float.floor(12.345,2)
+```
+
+```elixir
+Float.round(5.5)
+```
+
+```elixir
+Float.round(5.4)
+```
+
+```elixir
+Float.round(8.94, 1)
+```
+
+```elixir
+Float.round(-5.5674, 3)
+```
+
+```elixir
+tuple_size({:a, :b, :c})
+```
diff --git a/learn_elixir.livemd b/learn_elixir.livemd
index 494e3b5..627c66e 100644
--- a/learn_elixir.livemd
+++ b/learn_elixir.livemd
@@ -24,3 +24,11 @@ Kernel./(10, 3)
```elixir
Kernel.*(10, 3)
```
+
+```elixir
+:simple != :easy
+```
+
+```elixir
+:hello == :world
+```