Adding .agent support in addition to .claude
This commit is contained in:
170
.agents/skills/ash-framework/references/relationships.md
Normal file
170
.agents/skills/ash-framework/references/relationships.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# 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.
|
||||
|
||||
```elixir
|
||||
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:
|
||||
|
||||
```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
|
||||
|
||||
```elixir
|
||||
# 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.
|
||||
|
||||
```elixir
|
||||
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:
|
||||
|
||||
```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
|
||||
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:
|
||||
|
||||
```elixir
|
||||
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](https://hexdocs.pm/ash/relationships.html#managing-relationships) section.
|
||||
|
||||
### 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
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user