Claude Code Plugins

Community-maintained marketplace

Feedback

Defining, loading, and managing relationships between Ash resources

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-relationships
description Defining, loading, and managing relationships between Ash resources

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

Relationship Types

For Polymorphic relationships, you can model them using Ash.Type.Union; see the "Polymorphic Relationships" guide for more information.

relationships do
  # belongs_to - adds foreign key to source resource
  belongs_to :owner, MyApp.User do
    allow_nil? false
    attribute_type :integer  # defaults to :uuid
  end

  # has_one - foreign key on destination resource
  has_one :profile, MyApp.Profile

  # has_many - foreign key on destination resource, returns list
  has_many :posts, MyApp.Post do
    filter expr(published == true)
    sort published_at: :desc
  end

  # many_to_many - requires join resource
  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:

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

# Using code interface options (preferred)
post = MyDomain.get_post!(id, load: [:author, comments: [:author]])

# Complex loading with filters
posts = MyDomain.list_posts!(
  query: [load: [comments: [filter: [is_approved: true], limit: 5]]]
)

# Manual query building (for complex cases)
MyApp.Post
|> Ash.Query.load(comments: MyApp.Comment |> Ash.Query.filter(is_approved == true))
|> Ash.read!()

# Loading on existing records
Ash.load!(post, :author)

Prefer to use the strict? option when loading to only load necessary fields on related data.

MyApp.Post
|> Ash.Query.load([comments: [:title]], strict?: true)

Managing Relationships

There are two primary ways to manage relationships in Ash:

1. Using change manage_relationship/2-3 in Actions

Use this when input comes from action arguments:

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
    change manage_relationship(:new_tags, :tags, type: :append)
  end
end

2. Using Ash.Changeset.manage_relationship/3-4 in Custom Changes

Use this when building values programmatically:

defmodule MyApp.Changes.AssignTeamMembers do
  use Ash.Resource.Change

  def change(changeset, _opts, context) do
    members = determine_team_members(changeset, context.actor)

    Ash.Changeset.manage_relationship(
      changeset,
      :members,
      members,
      type: :append_and_remove
    )
  end
end

Quick Reference - Management Types

  • :append - Add new related records, ignore existing
  • :append_and_remove - Add new related records, remove missing
  • :remove - Remove specified related records
  • :direct_control - Full CRUD control (create/update/destroy)
  • :create - Only create new records

Quick Reference - Common Options

  • on_lookup: :relate - Look up and relate existing records
  • on_no_match: :create - Create if no match found
  • on_match: :update - Update existing matches
  • on_missing: :destroy - Delete records not in input
  • value_is_key: :name - Use field as key for simple values

For comprehensive documentation, see the Managing Relationships section.

Examples

Creating a post with tags:

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
})