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