#
View Source Relationships

Relationships describe the connections between resources and are a core component of Ash. Defining relationships enables you to do things like

- Loading related data
- Filtering on related data
- Managing related records through changes on a single resource
- Authorizing based on the state of related data

##

relationships-basics

Relationships Basics

A relationship exists between a source resource and a destination resource. These are defined in the `relationships` block of the source resource. For example, if `MyApp.Tweet` is the source resource, and `MyApp.User` is the destination resource, we could define a relationship called `:owner` like this:

    defmodule MyApp.Tweet do
      use Ash.Resource,
        data_layer: my_data_layer

      attributes do
        uuid_primary_key :id
        attribute :body, :string
      end

      relationships do
        belongs_to :owner, MyApp.User
      end
    end

##

managing-related-data

Managing related data

See Managing Relationships for more information.

##

kinds-of-relationships

Kinds of relationships

There are four kinds of relationships:

- `belongs_to`
- `has_one`
- `has_many`
- `many_to_many`

Each of these relationships has a `source` resource and a `destination` resource with a corresponding attribute on the source resource (`source_attribute`), and destination resource (`destination_attribute`). Relationships will validate that their configured attributes exist at compile time.

You don't need to have a corresponding "reverse" relationship for every relationship, i.e if you have a `MyApp.Tweet` resource with `belongs_to :user, MyApp.User` you aren't required to have a `has_many :tweets, MyApp.Tweet` on `MyApp.User`. All that is required is that the attributes used by the relationship exist.

###

belongs-to

Belongs To

    # on MyApp.Tweet
    belongs_to :owner, MyApp.User

A `belongs_to` relationship means that there is an attribute (`source_attribute`) on the source resource that uniquely identifies a record with a matching attribute (`destination_attribute`) in the destination. In the example above, the source attribute on `MyApp.Tweet` is `:owner_id` and the destination attribute on `MyApp.User` is `:id`.

#### Attribute Defaults

By default, the `source_attribute` is defined as `:<relationship_name>_id` of the type `:uuid` on the source resource and the `destination_attribute` is assumed to be `:id`. You can override the attribute names by specifying the `source_attribute` and `destination_attribute` options like so:

    belongs_to :owner, MyApp.User do
      # defaults to :<relationship_name>_id (i.e. :owner_id)
      source_attribute :custom_attribute_name

      # defaults to :id
      destination_attribute :custom_attribute_name
    end

You can further customize the `source_attribute` using options such as:

- `Ash.Resource.Dsl.relationships.belongs_to.define_attribute?` to define it yourself
- `d:Ash.Resource.Dsl.relationships.belongs_to.attribute_type` to modify the default type
- `d:Ash.Resource.Dsl.relationships.belongs_to.attribute_writable?` to make the source attribute `private?: false, writable?: true` (both are not the default)

For example:

    belongs_to :owner, MyApp.User do
      attribute_type :integer
      attribute_writable? true
    end

Or if you wanted to define the attribute yourself,

    attributes do
      attribute :owner_foo, MyApp.CustomType
    end

    ...
    relationships do
      belongs_to :owner, MyApp.User do
        define_attribute? false
        source_attribute :owner_foo
      end
    end

#### Customizing default belongs_to attribute type

Destination attributes that are added by default are assumed to be `:uuid`. To change this, set the following configuration in `config.exs`:

    config :ash, :default_belongs_to_type, :integer

See the docs for more: `Ash.Resource.Dsl.relationships.belongs_to`

###

has-one

Has One

    # on MyApp.User
    has_one :profile, MyApp.Profile

A `has_one` relationship means that there is a unique attribute (`destination_attribute`) on the destination resource that identifies a record with a matching unique attribute (`source_resource`) in the source. In the example above, the source attribute on `MyApp.User` is `:id` and the destination attribute on `MyApp.Profile` is `:user_id`.

A `has_one` is similar to a `belongs_to` except the reference attribute is on the destination resource, instead of the source.

#### Attribute Defaults

By default, the `source_attribute` is assumed to be `:id`, and `destination_attribute` defaults to `<snake_cased_last_part_of_module_name>_id`.

See the docs for more: `Ash.Resource.Dsl.relationships.has_one`

###

has-many

Has Many

    # on MyApp.User
    has_many :tweets, MyApp.Tweet

A `has_many` relationship means that there is a non-unique attribute (`destination_attribute`) on the destination resource that identifies a record with a matching unique attribute (`source_resource`) in the source. In the example above, the source attribute on `MyApp.User` is `:id` and the destination attribute on `MyApp.Tweet` is `:user_id`.

A `has_many` relationship is similar to a `has_one` because the reference attribute exists on the destination resource. The only difference between this and `has_one` is that the destination attribute is not unique, and therefore will produce a list of related items. In the example above, `:tweets` corresponds to a list of `MyApp.Tweet` records.

#### Attribute Defaults

By default, the `source_attribute` is assumed to be `:id`, and `destination_attribute` defaults to `<snake_cased_last_part_of_module_name>_id`.

See the docs for more: `Ash.Resource.Dsl.relationships.has_many`

###

many-to-many

Many To Many

A `many_to_many` relationship can be used to relate many source resources to many destination resources. To achieve this, the `source_attribute` and `destination_attribute` are defined on a join resource. A `many_to_many` relationship can be thought of as a combination of a `has_many` relationship on the source/destination resources and a `belongs_to` relationship on the join resource.

For example, consider two resources `MyApp.Tweet` and `MyApp.Hashtag` representing tweets and hashtags. We want to be able to associate a tweet with many hashtags, and a hashtag with many tweets. To do this, we could define the following `many_to_many` relationship:

    # on MyApp.Tweet
    many_to_many :hashtags, MyApp.Tweet do
      through MyApp.TweetHashtag
      source_attribute_on_join_resource :tweet_id
      destination_attribute_on_join_resource :hashtag_id
    end

The `through` option specifies the "join" resource that will be used to store the relationship. We need to define this resource as well:

    defmodule MyApp.TweetHashtag do
      use Ash.Resource,
        data_layer: your_data_layer

      relationships do
        belongs_to :tweet, MyApp.Tweet, primary_key?: true, allow_nil?: false
        belongs_to :hashtag, MyApp.Hashtag, primary_key?: true, allow_nil?: false
      end
    end

It is convention to name this resource `<source_resource_name><destination_resource_name>` however this is not required. The attributes on the join resource must match the `source_attribute_on_join_resource` and `destination_attribute_on_join_resource` options on the `many_to_many` relationship. The relationships on the join resource are standard `belongs_to` relationships, and can be configured as such. In this case, we have specified that the `:tweet_id` and `:hashtag_id` attributes form the primary key for the join resource, and that they cannot be `nil`.

Now that we have a resource with the proper attributes, Ash will use this automatically under the hood when performing relationship operations like filtering and loading.

See the docs for more: `Ash.Resource.Dsl.relationships.many_to_many`

###

relationships-across-apis

Relationships across APIs

You will need to specify the `api` option in the relationship if the destination resource is part of a different API:

    many_to_many :authors, MyApp.OtherApi.Resource do
      api MyApp.OtherApi
      ...
    end

##

loading-related-data

Loading related data

There are two ways to load relationships:

- in the query using `Ash.Query.load/2`
- directly on records using `Ash.Api.load/3`

###

on-records

On records

Given a single record or a set of records, it is possible to load their relationships by calling the `load` function on the record's parent API. For example:

    # user = %User{...}
    YourApi.load(user, :tweets)

    # users = [%User{...}, %User{...}, ....]
    YourApi.load(users, :tweets)

This will fetch the tweets for each user, and set them in the corresponding `tweets` key.

    %User{
      ...
      tweets: [
        %Tweet{...},
        %Tweet{...},
        ...
      ]
    }

See `Ash.Api.load/3` for more information.

###

in-the-query

In the query

The following will return a list of users with their tweets loaded identically to the previous example:

    User
    |> Ash.Query.load(:tweets)
    |> YourApi.read()

At present, loading relationships in the query is fundamentally the same as loading on records. Eventually, data layers will be able to optimize these loads (potentially including them as joins in the main query).

See `Ash.Query.load/2` for more information.

###

more-complex-data-loading

More complex data loading

Multiple relationships can be loaded at once, i.e

    YourApi.load(users, [:tweets, :followers])

Nested relationships can be loaded:

    YourApi.load(users, followers: [:tweets, :followers])

The queries used for loading can be customized by providing a query as the value.

    followers = Ash.Query.sort(User, follower_count: :asc)

    YourApi.load(users, followers: followers)

Nested loads will be included in the parent load.

    followers =
      User
      |> Ash.Query.sort(follower_count: :asc)
      |> Ash.Query.load(:followers)

    # Will load followers and followers of those followers
    YourApi.load(users, followers: followers)

← Previous Page Pub Sub
Next Page → Security