setup usage_rules

This commit is contained in:
2026-03-30 01:07:49 -04:00
parent cb179333f0
commit 934879f95f
31 changed files with 3459 additions and 1 deletions

View File

@@ -0,0 +1,134 @@
# 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
```
Prefer using the primary read action for "get" style code interfaces, and using `get_by` when the field you are looking up by is the primary key or has an `identity` on the resource.
```elixir
resource ResourceName do
define :get_thing, action: :read, get_by: [:id]
end
```
**Avoid direct Ash calls in web modules** - Don't use `Ash.get!/2` and `Ash.load!/2` directly in LiveViews/Controllers, similar to avoiding `Repo.get/2` outside context modules:
You can also pass additional inputs in to code interfaces before the options:
```elixir
resource ResourceName do
define :create, action: :action_name, args: [:field1]
end
```
```elixir
Domain.create!(field1_value, %{field2: field2_value}, actor: current_user)
```
You should generally prefer using this map of extra inputs over defining optional arguments.
```elixir
# BAD - in LiveView/Controller
group = MyApp.Resource |> Ash.get!(id) |> Ash.load!(rel: [:nested])
# GOOD - use code interface with get_by
resource DashboardGroup do
define :get_dashboard_group_by_id, action: :read, get_by: [:id]
end
# Then call:
MyApp.Domain.get_dashboard_group_by_id!(id, load: [rel: [:nested]])
```
**Code interface options** - Prefer passing options directly to code interface functions rather than building queries manually:
```elixir
# PREFERRED - Use the query option for filter, sort, limit, etc.
# the query option is passed to `Ash.Query.build/2`
posts = MyApp.Blog.list_posts!(
query: [
filter: [status: :published],
sort: [published_at: :desc],
limit: 10
],
load: [author: :profile, comments: [:author]]
)
# All query-related options go in the query parameter
users = MyApp.Accounts.list_users!(
query: [filter: [active: true], sort: [created_at: :desc]],
load: [:profile]
)
# AVOID - Verbose manual query building
query = MyApp.Post |> Ash.Query.filter(...) |> Ash.Query.load(...)
posts = Ash.read!(query)
```
Supported options: `load:`, `query:` (which accepts `filter:`, `sort:`, `limit:`, `offset:`, etc.), `page:`, `stream?:`
**Using Scopes in LiveViews** - When using `Ash.Scope`, the scope will typically be assigned to `scope` in LiveViews and used like so:
```elixir
# In your LiveView
MyApp.Blog.create_post!("new post", scope: socket.assigns.scope)
```
Inside action hooks and callbacks, use the provided `context` parameter as your scope instead:
```elixir
|> Ash.Changeset.before_transaction(fn changeset, context ->
MyApp.ExternalService.reserve_inventory(changeset, scope: context)
changeset
end)
```
## Authorization Functions
For each action defined in a code interface, Ash automatically generates corresponding authorization check functions:
- `can_action_name?(actor, params \\ %{}, opts \\ [])` - Returns `true`/`false` for authorization checks
- `can_action_name(actor, params \\ %{}, opts \\ [])` - Returns `{:ok, true/false}` or `{:error, reason}`
Example usage:
```elixir
# Check if user can create a post
if MyApp.Blog.can_create_post?(current_user) do
# Show create button
end
# Check if user can update a specific post
if MyApp.Blog.can_update_post?(current_user, post) do
# Show edit button
end
# Check if user can destroy a specific comment
if MyApp.Blog.can_destroy_comment?(current_user, comment) do
# Show delete button
end
```
These functions are particularly useful for conditional rendering of UI elements based on user permissions.