Packages
ash
3.5.12
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
usage-rules.md
# Rules for working with Ash
## Understanding Ash
Ash is an opinionated, composable framework for building applications in Elixir. It provides a declarative approach to modeling your domain with resources at the center. Read documentation *before* attempting to use it's features. Do not assume that you have prior knowledge of the framework or its conventions.
## Code Structure & Organization
- Organize code around domains and resources
- Each resource should be focused and well-named
- Create domain-specific actions rather than generic CRUD operations
- Put business logic inside actions rather than in external modules
- Use resources to model your domain entities
## Code Interfaces
Use code interfaces on domains to define the contract for calling into Ash resources. See the [Code interface guide for more](https://hexdocs.pm/ash/code-interfaces.html/).
Define code interfaces on the domain, like this:
```elixir
resource ResourceName do
define :fun_name, action: :action_name
end
```
For more complex interfaces with custom transformations:
```elixir
define :custom_action do
action :action_name
args [:arg1, :arg2]
custom_input :arg1, MyType do
transform do
to :target_field
using &MyModule.transform_function/1
end
end
end
```
## Actions
- Create specific, well-named actions rather than generic ones
- Put all business logic inside action definitions
- Use hooks like `Ash.Changeset.after_action/2`, `Ash.Changeset.before_action/2` to add additional logic
inside the same transaction.
- Use hooks like `Ash.Changeset.after_transaction/2`, `Ash.Changeset.before_transaction/2` to add additional logic
inside the same transaction.
- Use action arguments for inputs that need validation
- Use preparations to modify queries before execution
- Use changes to modify changesets before execution
- Use validations to validate changesets before execution
- Prefer domain code interfaces to call actions instead of directly building queries/changesets and calling functions in the `Ash` module
- A resource could be *only generic actions*. This can be useful when you are using a resource only to model behavior.
## Anonymous Functions
Prefer to put code in its own module and refer to that in changes, preparations, validations etc.
For example, prefer this:
```elixir
# in
defmodule MyApp.MyDomain.MyResource.Changes.SlugifyName do
use Ash.Resource.Change
def change(changeset, _, _) do
Ash.Changeset.before_action(changeset, fn changeset, _ ->
slug = MyApp.Slug.get()
Ash.Changeset.force_change_attribute(changeset, :slug, slug)
end)
end
end
change MyApp.MyDomain.MyResource.Changes.SlugifyName
```
### Action Types
- **Read**: For retrieving records
- **Create**: For creating records
- **Update**: For changing records
- **Destroy**: For removing records
- **Generic**: For custom operations that don't fit the other types
## Relationships
Relationships describe connections between resources and are a core component of Ash. Define relationships in the `relationships` block of a resource.
### Best Practices for Relationships
- Be descriptive with relationship names (e.g., use `:authored_posts` instead of just `:posts`)
- Configure foreign key constraints in your data layer if they have them (see `references` in AshPostgres)
- Always choose the appropriate relationship type based on your domain model
### Types of Relationships
#### belongs_to
Use when a resource "belongs to" another resource. This adds a foreign key to the source resource.
```elixir
relationships do
belongs_to :owner, MyApp.User do
# Customize the foreign key attribute (defaults to :owner_id)
source_attribute :custom_name
# Customize the type (defaults to :uuid)
attribute_type :integer
# Control whether the attribute is public
attribute_public? true
# Set constraints on the relationship
allow_nil? false
primary_key? false
end
end
```
#### has_one
Use when a resource "has one" of another resource. The foreign key is on the destination resource.
```elixir
relationships do
has_one :profile, MyApp.Profile do
# These are typically used with defaults
source_attribute :id # Default
destination_attribute :user_id # Default is <resource_name>_id
end
end
```
#### has_many
Use when a resource "has many" of another resource. The foreign key is on the destination resource.
```elixir
relationships do
has_many :posts, MyApp.Post do
# Similar to has_one but returns a list of related records
source_attribute :id # Default
destination_attribute :user_id # Default is <resource_name>_id
# Filter the related records
filter expr(published == true)
# Sort the related records
sort published_at: :desc
end
end
```
#### many_to_many
Use when many resources can be related to many other resources. Requires a join resource.
```elixir
relationships do
many_to_many :tags, MyApp.Tag do
through MyApp.PostTag
source_attribute_on_join_resource :post_id
destination_attribute_on_join_resource :tag_id
end
end
```
The join resource must be defined separately:
```elixir
defmodule MyApp.PostTag do
use Ash.Resource,
data_layer: AshPostgres.DataLayer
attributes do
uuid_primary_key :id
# Add additional attributes if you need metadata on the relationship
attribute :added_at, :utc_datetime_usec do
default &DateTime.utc_now/0
end
end
relationships do
belongs_to :post, MyApp.Post, primary_key?: true, allow_nil?: false
belongs_to :tag, MyApp.Tag, primary_key?: true, allow_nil?: false
end
actions do
defaults [:read, :destroy, create: :*, update: :*]
end
end
```
### Loading Relationships
Load relationships either in a query or directly on records:
```elixir
# In a query
MyApp.Post
|> Ash.Query.load(:author)
|> Ash.Query.load(comments: [:author])
|> MyDomain.read!()
# On records
post = MyDomain.get_post!(id)
post_with_author = Ash.load!(post, :author)
# Complex loading with customized queries
MyApp.Post
|> Ash.Query.load(comments:
MyApp.Comment
|> Ash.Query.filter(is_approved == true)
|> Ash.Query.sort(created_at: :desc)
|> Ash.Query.limit(5)
)
|> MyDomain.read!()
```
Prefer to use the `strict?` option when loading to only load necessary fields on related data.
```Elixir
MyApp.Post
|> Ash.Query.load([comments: [:title]], strict?: true)
```
### Managing Relationships
Use `manage_relationship` to handle related data in actions:
```elixir
actions do
update :update do
# Define argument for the related data
argument :comments, {:array, :map} do
allow_nil? false
end
argument :new_tags, {:array, :map}
# Link argument to relationship management
change manage_relationship(:comments, type: :append)
# For different argument and relationship names
argument :new_tags, {:array, :map}
change manage_relationship(:new_tags, :tags, type: :append)
end
end
```
#### Built in relationship management types
- `:create` - Create new related records
- `:append` - Add existing records to the relationship
- `:remove` - Remove specific related records from the relationship
- `:append_and_remove` - Add related records from the relationship, removing any not provided.
- `:direct_control` - Fully replace all related records with the provided data, creating anything new, deleting anything not provided, and updating any existing records.
#### Practical Examples
Creating a post with tags:
```elixir
MyDomain.create_post!(%{
title: "New Post",
body: "Content here...",
tags: [%{name: "elixir"}, %{name: "ash"}] # Creates new tags
})
# Updating a post to replace its tags
MyDomain.update_post!(post, %{
tags: [tag1.id, tag2.id] # Replaces tags with existing ones by ID
})
```
## Generating Code
Use `mix ash.gen.*` tasks as a basis for code generation when possible. Check the task docs with `mix help <task>`.
Be sure to use `--yes` to bypass confirmation prompts. Use `--yes --dry-run` to preview the changes.
## Data Layers
Data layers determine how resources are stored and retrieved. Examples of data layers:
- **Postgres**: For storing resources in PostgreSQL (via `AshPostgres`)
- **ETS**: For in-memory storage (`Ash.DataLayer.Ets`)
- **Mnesia**: For distributed storage (`Ash.DataLayer.Mnesia`)
- **Embedded**: For resources embedded in other resources (`data_layer: :embedded`) (typically JSON under the hood)
- **Ash.DataLayer.Simple**: For resources that aren't persisted at all. Leave off the data layer, as this is the default.
Specify a data layer when defining a resource:
```elixir
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp.Blog,
data_layer: AshPostgres.DataLayer
postgres do
table "posts"
repo MyApp.Repo
end
# ... attributes, relationships, etc.
end
```
For embedded resources:
```elixir
defmodule MyApp.Address do
use Ash.Resource,
data_layer: :embedded
attributes do
attribute :street, :string
attribute :city, :string
attribute :state, :string
attribute :zip, :string
end
end
```
Each data layer has its own configuration options and capabilities. Refer to the rules & documentation of the specific data layer package for more details.
## Migrations and Schema Changes
After creating or modifying Ash code, run `mix ash.codegen <short_name_describing_changes>` to ensure any required additional changes are made (like migrations are generated).
## Authorization
- When performing administrative actions, you can bypass authorization with `authorize?: false`
- To run actions as a particular user, look that user up and pass it as the `actor` option
- Always set the actor on the query/changeset/input, not when calling the action
- Use policies to define authorization rules
```elixir
# Good
Post
|> Ash.Query.for_read(:read, %{}, actor: current_user)
|> Ash.read!()
```
### Policies
To use policies, add the `Ash.Policy.Authorizer` to your resource:
```elixir
defmodule MyApp.Post do
use Ash.Resource,
domain: MyApp.Blog,
authorizers: [Ash.Policy.Authorizer]
# Rest of resource definition...
end
```
### Policy Basics
Policies determine what actions on a resource are permitted for a given actor. Define policies in the `policies` block:
```elixir
policies do
# A simple policy that applies to all read actions
policy action_type(:read) do
# Authorize if record is public
authorize_if expr(public == true)
# Authorize if actor is the owner
authorize_if relates_to_actor_via(:owner)
end
# A policy for create actions
policy action_type(:create) do
# Only allow active users to create records
forbid_unless actor_attribute_equals(:active, true)
# Ensure the record being created relates to the actor
authorize_if relating_to_actor(:owner)
end
end
```
### Policy Evaluation Flow
Policies evaluate from top to bottom with the following logic:
1. All policies that apply to an action must pass for the action to be allowed
2. Within each policy, checks evaluate from top to bottom
3. The first check that produces a decision determines the policy result
4. If no check produces a decision, the policy defaults to forbidden
### Bypass Policies
Use bypass policies to allow certain actors to bypass other policy restrictions. This should be used almost exclusively for admin bypasses.
```elixir
policies do
# Bypass policy for admins - if this passes, other policies don't need to pass
bypass actor_attribute_equals(:admin, true) do
authorize_if always()
end
# Regular policies follow...
policy action_type(:read) do
# ...
end
end
```
### Field Policies
Field policies control access to specific fields (attributes, calculations, aggregates):
```elixir
field_policies do
# Only supervisors can see the salary field
field_policy :salary do
authorize_if actor_attribute_equals(:role, :supervisor)
end
# Allow access to all other fields
field_policy :* do
authorize_if always()
end
end
```
### Policy Checks
There are two main types of checks used in policies:
1. **Simple checks** - Return true/false answers (e.g., "is the actor an admin?")
2. **Filter checks** - Return filters to apply to data (e.g., "only show records owned by the actor")
You can use built-in checks or create custom ones:
```elixir
# Built-in checks
authorize_if actor_attribute_equals(:role, :admin)
authorize_if relates_to_actor_via(:owner)
authorize_if expr(public == true)
# Custom check module
authorize_if MyApp.Checks.ActorHasPermission
```
#### Custom Simple Check Example
Create a custom simple check by implementing `Ash.Policy.SimpleCheck`:
```elixir
defmodule MyApp.Checks.ActorHasRequiredRole do
use Ash.Policy.SimpleCheck
# Provide a description for logging and debugging
def describe(opts) do
"actor has required role: #{opts[:role] || "admin"}"
end
# Implement the check logic - must return true or false
def match?(%{role: actor_role} = _actor, _context, opts) do
required_role = opts[:role] || :admin
actor_role == required_role
end
# Handle case when actor doesn't have role attribute
def match?(_, _, _), do: false
end
# Usage in policies
policy action_type(:read) do
# Pass options to the check
authorize_if {MyApp.Checks.ActorHasRequiredRole, role: :manager}
end
```
#### Custom Filter Check Example
Create a custom filter check by implementing `Ash.Policy.FilterCheck`:
```elixir
defmodule MyApp.Checks.VisibleToUserLevel do
use Ash.Policy.FilterCheck
# Provide a description (optional as it can be derived from the filter)
def describe(opts) do
"records with visibility level at or below actor's level"
end
# Return an expression that filters the records
def filter(actor, _authorizer, _opts) do
# This filter will only show records with visibility_level
# less than or equal to the actor's user_level
expr(visibility_level <= ^actor.user_level)
end
end
# Usage in policies
policy action_type(:read) do
authorize_if MyApp.Checks.VisibleToUserLevel
end
```
## Calculations
Calculations allow you to define derived values based on a resource's attributes or related data. Define calculations in the `calculations` block of a resource:
```elixir
calculations do
# Simple expression calculation
calculate :full_name, :string, expr(first_name <> " " <> last_name)
# Expression with conditions
calculate :status_label, :string, expr(
cond do
status == :active -> "Active"
status == :pending -> "Pending Review"
true -> "Inactive"
end
)
# Using module calculations for more complex logic
calculate :risk_score, :integer, {MyApp.Calculations.RiskScore, min: 0, max: 100}
end
```
### Expression Calculations
Expression calculations use Ash expressions and can be pushed down to the data layer when possible:
```elixir
calculations do
# Simple string concatenation
calculate :full_name, :string, expr(first_name <> " " <> last_name)
# Math operations
calculate :total_with_tax, :decimal, expr(amount * (1 + tax_rate))
# Date manipulation
calculate :days_since_created, :integer, expr(
date_diff(^now(), inserted_at, :day)
)
end
```
### Module Calculations
For complex calculations, create a module that implements `Ash.Resource.Calculation`:
```elixir
defmodule MyApp.Calculations.FullName do
use Ash.Resource.Calculation
# Validate and transform options
@impl true
def init(opts) do
{:ok, Map.put_new(opts, :separator, " ")}
end
# Specify what data needs to be loaded
@impl true
def load(_query, _opts, _context) do
[:first_name, :last_name]
end
# Implement the calculation logic
@impl true
def calculate(records, opts, _context) do
Enum.map(records, fn record ->
[record.first_name, record.last_name]
|> Enum.reject(&is_nil/1)
|> Enum.join(opts.separator)
end)
end
end
# Usage in a resource
calculations do
calculate :full_name, :string, {MyApp.Calculations.FullName, separator: ", "}
end
```
### Calculations with Arguments
You can define calculations that accept arguments:
```elixir
calculations do
calculate :full_name, :string, expr(first_name <> ^arg(:separator) <> last_name) do
argument :separator, :string do
allow_nil? false
default " "
constraints [allow_empty?: true, trim?: false]
end
end
end
```
### Using Calculations
Load calculations in queries or on records:
```elixir
# In a query
User
|> Ash.Query.load(:full_name)
|> MyDomain.read!()
# With arguments
User
|> Ash.Query.load(full_name: [separator: ", "])
|> MyDomain.read!()
# On existing records
users = MyDomain.list_users!()
users_with_calcs = Ash.load!(users, :full_name)
# Filter and sort by calculations
User
|> Ash.Query.filter(full_name(separator: " ") == "John Doe")
|> Ash.Query.sort(full_name: {%{separator: " "}, :asc})
|> MyDomain.read!()
```
### Code Interface for Calculations
Define calculation functions on your domain for standalone use:
```elixir
# In your domain
resource User do
define_calculation :full_name, args: [:first_name, :last_name, {:optional, :separator}]
end
# Then call it directly
MyDomain.full_name("John", "Doe", ", ") # Returns "John, Doe"
```
## Aggregates
Aggregates allow you to retrieve summary information over groups of related data, like counts, sums, or averages. Define aggregates in the `aggregates` block of a resource:
```elixir
aggregates do
# Count the number of published posts for a user
count :published_post_count, :posts do
filter expr(published == true)
end
# Sum the total amount of all orders
sum :total_sales, :orders, :amount
# Check if a user has any admin roles
exists :is_admin, :roles do
filter expr(name == "admin")
end
end
```
### Aggregate Types
- **count**: Counts related items meeting criteria
- **sum**: Sums a field across related items
- **exists**: Returns boolean indicating if matching related items exist
- **first**: Gets the first related value matching criteria
- **list**: Lists the related values for a specific field
- **max**: Gets the maximum value of a field
- **min**: Gets the minimum value of a field
- **avg**: Gets the average value of a field
### Using Aggregates
Load aggregates in queries or on records:
```elixir
# In a query
User
|> Ash.Query.load(:published_post_count)
|> MyDomain.read!()
# On existing records
users = MyDomain.list_users!()
users_with_counts = Ash.load!(users, :published_post_count)
```
Filter and sort by aggregates:
```elixir
# Filter users with more than 5 published posts
User
|> Ash.Query.filter(published_post_count > 5)
|> MyDomain.read!()
# Sort users by their post count
User
|> Ash.Query.sort(published_post_count: :desc)
|> MyDomain.read!()
```
### Join Filters
For complex aggregates involving multiple relationships, use join filters:
```elixir
aggregates do
sum :redeemed_deal_amount, [:redeems, :deal], :amount do
# Filter on the aggregate as a whole
filter expr(redeems.redeemed == true)
# Apply filters to specific relationship steps
join_filter :redeems, expr(redeemed == true)
join_filter [:redeems, :deal], expr(active == parent(require_active))
end
end
```
### Inline Aggregates
Use aggregates inline within expressions:
```elixir
calculate :grade_percentage, :decimal, expr(
count(answers, query: [filter: expr(correct == true)]) * 100 /
count(answers)
)
```
## Testing
When testing resources:
- Test your domain actions through the code interface
- Test authorization policies work as expected using `Ash.can?`
- Use `authorize?: false` in tests where authorization is not the focus
- Write generators using `Ash.Generator`