Claude Code Plugins

Community-maintained marketplace

Feedback
1
0

Define and use code interfaces for calling Ash resources from domains

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ash-code-interfaces
description Define and use code interfaces for calling Ash resources from domains

Code Interfaces

Use code interfaces on domains to define the contract for calling into Ash resources. See the Code interface guide for more.

Define code interfaces on the domain, like this:

resource ResourceName do
  define :fun_name, action: :action_name
end

For more complex interfaces with custom transformations:

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.

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:

resource ResourceName do
  define :create, action: :action_name, args: [:field1]
end
Domain.create!(field1_value, %{field2: field2_value}, actor: current_user)

You should generally prefer using this map of extra inputs over defining optional arguments.

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

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

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

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

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