Adding .agent support in addition to .claude
This commit is contained in:
134
.agents/skills/ash-framework/references/code_interfaces.md
Normal file
134
.agents/skills/ash-framework/references/code_interfaces.md
Normal 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.
|
||||
Reference in New Issue
Block a user