mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
docs: consolidate guides into relationships.md
improvement: add `Ash.Query.apply_to/3`
This commit is contained in:
parent
773e9cea91
commit
d153b40dea
13 changed files with 206 additions and 101 deletions
|
@ -362,7 +362,7 @@ A section for declaring relationships on the resource.
|
|||
Relationships are a core component of resource oriented design. Many components of Ash
|
||||
will use these relationships. A simple use case is loading relationships (done via the `Ash.Query.load/2`).
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
|
||||
### Nested DSLs
|
||||
|
@ -429,7 +429,7 @@ Declares a `has_one` relationship. In a relational database, the foreign key wou
|
|||
|
||||
Generally speaking, a `has_one` also implies that the destination table is unique on that foreign key.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
|
||||
### Nested DSLs
|
||||
|
@ -459,7 +459,7 @@ end
|
|||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`manual`](#relationships-has_one-manual){: #relationships-has_one-manual } | `(any, any -> any) \| module` | | A module that implements `Ash.Resource.ManualRelationship`. Also accepts a 2 argument function that takes the source records and the context. |
|
||||
| [`no_attributes?`](#relationships-has_one-no_attributes?){: #relationships-has_one-no_attributes? } | `boolean` | | All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/relationships.md) for more. |
|
||||
| [`no_attributes?`](#relationships-has_one-no_attributes?){: #relationships-has_one-no_attributes? } | `boolean` | | All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/resources/relationships.md) for more. |
|
||||
| [`allow_nil?`](#relationships-has_one-allow_nil?){: #relationships-has_one-allow_nil? } | `boolean` | `true` | Marks the relationship as required. Has no effect on validations, but can inform extensions that there will always be a related entity. |
|
||||
| [`from_many?`](#relationships-has_one-from_many?){: #relationships-has_one-from_many? } | `boolean` | `false` | Signal that this relationship is actually a `has_many` where the first record is given via the `sort`. This will allow data layers to properly deduplicate when necessary. |
|
||||
| [`description`](#relationships-has_one-description){: #relationships-has_one-description } | `String.t` | | An optional description for the relationship |
|
||||
|
@ -528,7 +528,7 @@ has_many name, destination
|
|||
|
||||
Declares a `has_many` relationship. There can be any number of related entities.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
|
||||
### Nested DSLs
|
||||
|
@ -558,7 +558,7 @@ end
|
|||
| Name | Type | Default | Docs |
|
||||
|------|------|---------|------|
|
||||
| [`manual`](#relationships-has_many-manual){: #relationships-has_many-manual } | `(any, any -> any) \| module` | | A module that implements `Ash.Resource.ManualRelationship`. Also accepts a 2 argument function that takes the source records and the context. |
|
||||
| [`no_attributes?`](#relationships-has_many-no_attributes?){: #relationships-has_many-no_attributes? } | `boolean` | | All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/relationships.md) for more. |
|
||||
| [`no_attributes?`](#relationships-has_many-no_attributes?){: #relationships-has_many-no_attributes? } | `boolean` | | All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/resources/relationships.md) for more. |
|
||||
| [`description`](#relationships-has_many-description){: #relationships-has_many-description } | `String.t` | | An optional description for the relationship |
|
||||
| [`destination_attribute`](#relationships-has_many-destination_attribute){: #relationships-has_many-destination_attribute } | `atom` | | The attribute on the related resource that should match the `source_attribute` configured on this resource. |
|
||||
| [`validate_destination_attribute?`](#relationships-has_many-validate_destination_attribute?){: #relationships-has_many-validate_destination_attribute? } | `boolean` | `true` | Whether or not to validate that the destination field exists on the destination resource |
|
||||
|
@ -627,7 +627,7 @@ Declares a `many_to_many` relationship. Many to many relationships require a joi
|
|||
|
||||
A join resource is a resource that consists of a relationship to the source and destination of the many to many.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
|
||||
### Nested DSLs
|
||||
|
@ -735,7 +735,7 @@ Declares a `belongs_to` relationship. In a relational database, the foreign key
|
|||
|
||||
This creates a field on the resource with the corresponding name and type, unless `define_attribute?: false` is provided.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
|
||||
### Nested DSLs
|
||||
|
|
|
@ -37,6 +37,7 @@ Welcome to the Ash Framework documentation! Here you will find everything you ne
|
|||
- [Calculations](documentation/topics/resources/calculations.md)
|
||||
- [Aggregates](documentation/topics/resources/aggregates.md)
|
||||
- [Code Interfaces](documentation/topics/resources/code-interfaces.md)
|
||||
- [Relationships](documentation/topics/resources/relationships.md)
|
||||
|
||||
### Actions
|
||||
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
# Define Manual Relationships
|
||||
|
||||
Manual relationships allow you to express complex or non-typical relationships between resources in a standard way.
|
||||
Individual data layers may interact with manual relationships in their own way, so see their corresponding guides.
|
||||
|
||||
By default, the only thing manual relationships support is being loaded.
|
||||
|
||||
## Example
|
||||
|
||||
In our Helpdesk example, we'd like to have a way to find tickets
|
||||
|
||||
In the `Rep?` resource, define a `has_many` relationship as `manual` and point to the module where
|
||||
it will be implemented.
|
||||
|
||||
```elixir
|
||||
relationships do
|
||||
has_many :tickets_above_threshold, Helpdesk.Support.Ticket do
|
||||
manual Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Using Ash to get the destination records is ideal, so you can authorize access like normal
|
||||
but if you need to use a raw ecto query here, you can. As long as you return the right structure.
|
||||
|
||||
The `TicketsAboveThreshold` module is implemented as follows.
|
||||
|
||||
```elixir
|
||||
defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do
|
||||
use Ash.Resource.ManualRelationship
|
||||
require Ash.Query
|
||||
|
||||
def load(records, _opts, %{query: query, actor: actor, authorize?: authorize?}) do
|
||||
# Use existing records to limit resultds
|
||||
rep_ids = Enum.map(records, & &1.id)
|
||||
|
||||
{:ok,
|
||||
query
|
||||
|> Ash.Query.filter(representative_id in ^rep_ids)
|
||||
|> Ash.Query.filter(priority > representative.priority_threshold)
|
||||
|> Helpdesk.Support.read!(actor: actor, authorize?: authorize?)
|
||||
# Return the items grouped by the primary key of the source, i.e representative.id => [...tickets above threshold]
|
||||
|> Enum.group_by(& &1.representative_id)}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Using the Query
|
||||
|
||||
Since you likely want to support things like filtering your relationship when being loaded, you will want to make sure that you use the query being provided. However, depending on how you're loading the relationship, you may need to do things like fetch extra records. To do this, you might do things like
|
||||
|
||||
```elixir
|
||||
def load(records, _opts, %{query: query, ..}) do
|
||||
# unset some fields
|
||||
fetch_query = Ash.Query.unset(query, [:limit, :offset])
|
||||
|
||||
# or, to be more safe/explicit, you might make a new query, explicitly setting only a few fields
|
||||
fetch_query = query.resource |> Ash.Query.filter(^query.filter) |> Ash.Query.sort(query.sort)
|
||||
|
||||
...
|
||||
end
|
||||
```
|
|
@ -14,7 +14,7 @@ Each action has its own set of options, ways of calling it, and ways of customiz
|
|||
|
||||
Primary actions are a way to inform the framework which actions should be used in certain "automated" circumstances, or in cases where an action has not been specified. If a primary action is attempted to be used but does not exist, you will get an error about it at runtime.
|
||||
|
||||
The place you typically need primary actions is when [Managing Relationships](/documentation/topics/managing-relationships.md). When using the `defaults` option to add default actions, they are marked as primary.
|
||||
The place you typically need primary actions is when [Managing Relationships](/documentation/topics/resources/relationships.md#managing-relationships.md). When using the `defaults` option to add default actions, they are marked as primary.
|
||||
|
||||
A simple example where a primary action would be used:
|
||||
|
||||
|
@ -43,7 +43,7 @@ actions do
|
|||
end
|
||||
```
|
||||
|
||||
But that is just a simple way to get started, or to create resources that really don't do anything beyond those four operations. You can have _as many actions as you want_. The best designed Ash applications will have numerous actions, named after the intent behind how they are used. They won't have all reads going through a single read action, and the same goes for the other action types. The richer the actions on the resource, the better interface you can have. With that said, many resources may only have those four basic actions, especially those that are "managed" through some parent resource. See the guide on [Managing Relationships](/documentation/topics/managing-relationships.md) for more.
|
||||
But that is just a simple way to get started, or to create resources that really don't do anything beyond those four operations. You can have _as many actions as you want_. The best designed Ash applications will have numerous actions, named after the intent behind how they are used. They won't have all reads going through a single read action, and the same goes for the other action types. The richer the actions on the resource, the better interface you can have. With that said, many resources may only have those four basic actions, especially those that are "managed" through some parent resource. See the guide on [Managing Relationships](/documentation/topics/resources/relationships.md#managing-relationships.md) for more.
|
||||
|
||||
### Put everything inside the action
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ The underlying record can be retrieved from `changeset.data` for update and dest
|
|||
|
||||
## Manual Read Actions
|
||||
|
||||
Manual read actions work the same, except the will also get the "data layer query". For AshPostgres, this means you get the ecto query that would have been run.
|
||||
Manual read actions work the same, except the will also get the "data layer query". For AshPostgres, this means you get the ecto query that would have been run. You can use `Ash.Query.apply_to/3` to apply a query to records in memory. This allows you to fetch the data in a way that is not possible with the data layer, but still honor the query that was provided to.
|
||||
|
||||
```elixir
|
||||
# in the resource
|
||||
|
|
|
@ -83,13 +83,15 @@ Using this, you might make requests like the following:
|
|||
```elixir
|
||||
# Get the first ten records
|
||||
Ash.read(Resource, page: [limit: 10])
|
||||
# or by using an action named `read` directly
|
||||
Resource.read(page: [limit: 10])
|
||||
# or by using an action named `read` directly through a
|
||||
# code interface on the domain
|
||||
Domain.read(page: [limit: 10])
|
||||
|
||||
# Get the next ten records
|
||||
Ash.read(Resource, page: [limit: 10, offset: 10])
|
||||
# or by using an action named `read` directly
|
||||
Resource.read(page: [limit: 10, offset: 10])
|
||||
# or by using an action named `read` directly through a
|
||||
# code interface on the domain
|
||||
Domain.read(page: [limit: 10, offset: 10])
|
||||
```
|
||||
|
||||
Next/previous page requests can also be made in memory, using an existing page of search results:
|
||||
|
|
|
@ -107,7 +107,7 @@ See `Ash.Query` for more.
|
|||
|
||||
Relationships are named links between resources, that define how they relate to each other. Relationships can be used to signify ownership of a record, membership of a group, or can be used in filtering and querying data.
|
||||
|
||||
See the [Relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [Relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
|
||||
## Resource
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ end
|
|||
|
||||
## Managing related data
|
||||
|
||||
See [Managing Relationships](/documentation/topics/managing-relationships.md) for more information.
|
||||
See [Managing Relationships](/documentation/topics/resources/relationships.md#managing-relationships.md) for more information.
|
||||
|
||||
Your data layer may enforce foreign key constraints, see the following guides for more information:
|
||||
|
||||
|
@ -304,17 +304,159 @@ Ash.load(users, followers: followers)
|
|||
|
||||
## no_attributes? true
|
||||
|
||||
This can be very useful when combined with multitenancy. Specifically, if you have a tenant resource like `Organization`,
|
||||
This is really useful when creating customized relationships that aren't joined with simple attribute matches. For example:
|
||||
|
||||
```elixir
|
||||
has_many :higher_priority_tickets, __MODULE__ do
|
||||
no_attributes? true
|
||||
# parent/1 in this case puts the expression on this current resource
|
||||
# so this is "tickets with priority higher than this ticket"
|
||||
filter expr(priority > parent(priority))
|
||||
end
|
||||
```
|
||||
|
||||
This can also be very useful when combined with multitenancy. Specifically, if you have a tenant resource like `Organization`,
|
||||
you can use `no_attributes?` to do things like `has_many :employees, Employee, no_attributes?: true`, which lets you avoid having an
|
||||
unnecessary `organization_id` field on `Employee`. The same works in reverse: `has_one :organization, Organization, no_attributes?: true`
|
||||
allows relating the employee to their organization.
|
||||
|
||||
Some important caveats here:
|
||||
|
||||
1. You can still manage relationships from one to the other, but "relate" and "unrelate"
|
||||
will have no effect, because there are no fields to change.
|
||||
> ### caveats for using `no_attributes?` {: .warning}
|
||||
>
|
||||
> 1. You can still manage relationships from one to the other, but "relate" and "unrelate" will have no effect, because there are no fields to change.
|
||||
> 2. Loading the relationship on a list of resources will not behave as expected in all circumstances involving multitenancy. For example, if you get a list of `Organization` and then try to load `employees`, you would need to set a single tenant on the load query, meaning you'll get all organizations back with the set of employees from one tenant. This could eventually be solved, but for now it is considered an edge case.
|
||||
|
||||
2. Loading the relationship on a list of resources will not behave as expected in all circumstances involving multitenancy. For example,
|
||||
if you get a list of `Organization` and then try to load `employees`, you would need to set a single tenant on the load query, meaning
|
||||
you'll get all organizations back with the set of employees from one tenant. This could eventually be solved, but for now it is considered an
|
||||
edge case.
|
||||
|
||||
## Manual Relationships
|
||||
|
||||
Manual relationships allow you to express complex or non-typical relationships between resources in a standard way. Individual data layers may interact with manual relationships in their own way, so see their corresponding guides. In general, you should try to use manual relationships sparingly, as you can do *a lot* with filters on relationships, and the `no_attributes?` flag.
|
||||
|
||||
### Example
|
||||
|
||||
In our Helpdesk example, we'd like to have a way to find tickets
|
||||
|
||||
In the `Rep?` resource, define a `has_many` relationship as `manual` and point to the module where
|
||||
it will be implemented.
|
||||
|
||||
```elixir
|
||||
relationships do
|
||||
has_many :tickets_above_threshold, Helpdesk.Support.Ticket do
|
||||
manual Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Using Ash to get the destination records is ideal, so you can authorize access like normal
|
||||
but if you need to use a raw ecto query here, you can. As long as you return the right structure.
|
||||
|
||||
The `TicketsAboveThreshold` module is implemented as follows.
|
||||
|
||||
```elixir
|
||||
defmodule Helpdesk.Support.Ticket.Relationships.TicketsAboveThreshold do
|
||||
use Ash.Resource.ManualRelationship
|
||||
require Ash.Query
|
||||
|
||||
def load(records, _opts, %{query: query, actor: actor, authorize?: authorize?}) do
|
||||
# Use existing records to limit resultds
|
||||
rep_ids = Enum.map(records, & &1.id)
|
||||
|
||||
{:ok,
|
||||
query
|
||||
|> Ash.Query.filter(representative_id in ^rep_ids)
|
||||
|> Ash.Query.filter(priority > representative.priority_threshold)
|
||||
|> Helpdesk.Support.read!(actor: actor, authorize?: authorize?)
|
||||
# Return the items grouped by the primary key of the source, i.e representative.id => [...tickets above threshold]
|
||||
|> Enum.group_by(& &1.representative_id)}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Reusing the Query
|
||||
|
||||
Since you likely want to support things like filtering your relationship when being loaded, you will want to make sure that you use the query being provided. However, depending on how you're loading the relationship, you may need to do things like fetch extra records. To do this, you might do things like
|
||||
|
||||
```elixir
|
||||
def load(records, _opts, %{query: query, ..}) do
|
||||
# unset some fields
|
||||
fetch_query = Ash.Query.unset(query, [:limit, :offset])
|
||||
|
||||
# or, to be more safe/explicit, you might make a new query, explicitly setting only a few fields
|
||||
fetch_query = query.resource |> Ash.Query.filter(^query.filter) |> Ash.Query.sort(query.sort)
|
||||
|
||||
...
|
||||
end
|
||||
```
|
||||
|
||||
### Fetching the records and then applying a query
|
||||
|
||||
Lets say the records come from some totally unrelated source, or you can't just modify the query to fetch the records you need. You can fetch the records you need and then apply the query to them in memory.
|
||||
|
||||
```elixir
|
||||
def load(records, _opts, %{query: query, ..}) do
|
||||
# fetch the data from the other source, which is capabale of sorting
|
||||
data = get_other_data(data, query.sort)
|
||||
|
||||
query
|
||||
# unset the limit and offset since we already applied that
|
||||
|> Ash.Query.unset([:sort])
|
||||
# apply the query in memory (filtering, distinct, limit, offset)
|
||||
|> Ash.Query.apply_to(data)
|
||||
end
|
||||
```
|
||||
|
||||
## Managing Relationships
|
||||
|
||||
In Ash, managing related data is done via `Ash.Changeset.manage_relationship/4`. There are various ways to leverage the functionality expressed there. If you are working with changesets directly, you can call that function. However, if you want that logic to be portable (e.g available in `ash_graphql` mutations and `ash_json_api` actions), then you want to use the following `argument` + `change` pattern:
|
||||
|
||||
```elixir
|
||||
actions do
|
||||
update :update do
|
||||
argument :add_comment, :map do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
argument :tags, {:array, :uuid} do
|
||||
allow_nil? false
|
||||
end
|
||||
|
||||
# First argument is the name of the action argument to use
|
||||
# Second argument is the relationship to be managed
|
||||
# Third argument is options. For more, see `Ash.Changeset.manage_relationship/4`. This accepts the same options.
|
||||
change manage_relationship(:add_comment, :comments, type: :create)
|
||||
|
||||
# Second argument can be omitted, as the argument name is the same as the relationship
|
||||
change manage_relationship(:tags, type: :append_and_remove)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
With this, those arguments can be used in action input:
|
||||
|
||||
```elixir
|
||||
post
|
||||
|> Ash.Changeset.for_update(:update, tags: [tag1.id, tag2.id], add_comment: %{text: "comment text"})
|
||||
|> Ash.update!()
|
||||
```
|
||||
|
||||
### Argument Types
|
||||
|
||||
Notice how we provided a map as input to `add_comment`, and a list of UUIDs as an input to `manage_relationship`. When providing maps or lists of maps, you are generally just providing input that will eventually be passed into actions on the destination resource. However, you can also provide individual values or lists of values. By default, we assume that value maps to the primary key of the destination resource, but you can use the `value_is_key` option to modify that behavior. For example, if you wanted adding a comment to take a list of strings, you could say:
|
||||
|
||||
```elixir
|
||||
argument :add_comment, :string
|
||||
|
||||
...
|
||||
change manage_relationship(:add_comment, :comments, type: :create, value_is_key: :text)
|
||||
```
|
||||
|
||||
And then you could use it like so:
|
||||
|
||||
```elixir
|
||||
post
|
||||
|> Ash.Changeset.for_update(:update, tags: [tag1.id, tag2.id], add_comment: "comment text")
|
||||
|> Ash.update!()
|
||||
```
|
||||
|
||||
### Derived behavior
|
||||
|
||||
Determining what will happen when managing related data can be complicated, as the nature of the problem itself is quite complicated. In some simple cases, like `type: :create`, there may be only one action that will be called. But in order to support all of the various ways that related resources may need to be managed, Ash provides a very rich set of options to determine what happens with the provided input. Tools like `AshPhoenix.Form` can look at your arguments that have a corresponding `manage_relationship` change, and derive the structure of those nested forms. Tools like `AshGraphql` can derive complex input objects to allow manipulating those relationships over a graphql Api. This all works because the options are, ultimately, quite explicit. It can be determined exactly what actions might be called, and therefore what input could be needed.
|
|
@ -519,7 +519,7 @@ You may notice that if you don't add the resource to your domain, or if you don'
|
|||
|
||||
## Working with relationships
|
||||
|
||||
There are a wide array of options when managing relationships, and we won't cover all of them here. See the guide on [Managing Relationships](/documentation/topics/managing-relationships.md) for a full explanation.
|
||||
There are a wide array of options when managing relationships, and we won't cover all of them here. See the guide on [Managing Relationships](/documentation/topics/resources/relationships.md#managing-relationships.md) for a full explanation.
|
||||
|
||||
In this example we'll demonstrate the use of action arguments, the method by which you can accept additional input to an action.
|
||||
|
||||
|
|
|
@ -2732,6 +2732,32 @@ defmodule Ash.Query do
|
|||
end
|
||||
end
|
||||
|
||||
@spec apply_to(t(), records :: list(Ash.Resource.record()), opts :: Keyword.t()) ::
|
||||
{:ok, list(Ash.Resource.record())}
|
||||
def apply_to(query, records, opts \\ []) do
|
||||
domain =
|
||||
query.domain || Ash.Resource.Info.domain(query.resource) || opts[:domain] ||
|
||||
raise ArgumentError,
|
||||
"Could not determine domain for #{inspect(query)}, please provide the `:domain` option."
|
||||
|
||||
with {:ok, records} <-
|
||||
Ash.Filter.Runtime.filter_matches(domain, records, query.filter, parent: opts[:parent]),
|
||||
records <- Sort.runtime_sort(records, query.distinct_sort || query.sort, domain: domain),
|
||||
records <- Sort.runtime_distinct(records, query.distinct, domain: domain),
|
||||
records <- Sort.runtime_sort(records, query.sort, domain: domain),
|
||||
records <- Enum.drop(records, query.offset),
|
||||
records <- do_limit(records, query.limit),
|
||||
{:ok, records} <- Ash.load(records, query, domain: domain) do
|
||||
{:ok, records}
|
||||
else
|
||||
{:error, error} ->
|
||||
{:error, Ash.Error.to_ash_error(error)}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_limit(records, nil), do: records
|
||||
defp do_limit(records, limit), do: Enum.take(records, limit)
|
||||
|
||||
@spec unset(Ash.Resource.t() | t(), atom | [atom]) :: t()
|
||||
def unset(query, keys) when is_list(keys) do
|
||||
query = new(query)
|
||||
|
|
|
@ -199,7 +199,7 @@ defmodule Ash.Resource.Dsl do
|
|||
|
||||
Generally speaking, a `has_one` also implies that the destination table is unique on that foreign key.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
|
@ -225,7 +225,7 @@ defmodule Ash.Resource.Dsl do
|
|||
describe: """
|
||||
Declares a `has_many` relationship. There can be any number of related entities.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
|
@ -253,7 +253,7 @@ defmodule Ash.Resource.Dsl do
|
|||
|
||||
A join resource is a resource that consists of a relationship to the source and destination of the many to many.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
|
@ -288,7 +288,7 @@ defmodule Ash.Resource.Dsl do
|
|||
|
||||
This creates a field on the resource with the corresponding name and type, unless `define_attribute?: false` is provided.
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
|
@ -317,7 +317,7 @@ defmodule Ash.Resource.Dsl do
|
|||
Relationships are a core component of resource oriented design. Many components of Ash
|
||||
will use these relationships. A simple use case is loading relationships (done via the `Ash.Query.load/2`).
|
||||
|
||||
See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
""",
|
||||
examples: [
|
||||
"""
|
||||
|
|
|
@ -109,7 +109,7 @@ defmodule Ash.Resource.Relationships.SharedOptions do
|
|||
[
|
||||
type: :boolean,
|
||||
doc: """
|
||||
All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/relationships.md) for more.
|
||||
All existing entities are considered related, i.e this relationship is not based on any fields, and `source_attribute` and `destination_attribute` are ignored. See the See the [relationships guide](/documentation/topics/resources/relationships.md) for more.
|
||||
"""
|
||||
]}
|
||||
end
|
||||
|
|
12
mix.exs
12
mix.exs
|
@ -50,6 +50,7 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/resources/embedded-resources.md",
|
||||
"documentation/topics/resources/code-interfaces.md",
|
||||
"documentation/topics/resources/identities.md",
|
||||
"documentation/topics/resources/relationships.md",
|
||||
"documentation/topics/actions/actions.md",
|
||||
"documentation/topics/actions/read-actions.md",
|
||||
"documentation/topics/actions/create-actions.md",
|
||||
|
@ -63,7 +64,6 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/security/sensitive-data.md",
|
||||
"CHANGELOG.md",
|
||||
# below this line under review
|
||||
"documentation/how_to/defining-manual-relationships.md",
|
||||
"documentation/how_to/handle-errors.md",
|
||||
"documentation/how_to/structure-your-project.md",
|
||||
"documentation/how_to/use-without-data-layers.md",
|
||||
|
@ -73,14 +73,12 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/extending-resources.md",
|
||||
"documentation/topics/expressions.md",
|
||||
"documentation/topics/reference/glossary.md",
|
||||
"documentation/topics/managing-relationships.md",
|
||||
"documentation/topics/monitoring.md",
|
||||
"documentation/topics/multitenancy.md",
|
||||
"documentation/topics/notifiers.md",
|
||||
"documentation/topics/security/policies.md",
|
||||
"documentation/topics/pub_sub.md",
|
||||
"documentation/topics/reactor.md",
|
||||
"documentation/topics/relationships.md",
|
||||
"documentation/topics/testing.md",
|
||||
"documentation/topics/timeouts.md",
|
||||
"documentation/topics/validations.md",
|
||||
|
@ -105,7 +103,8 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/resources/aggregates.md",
|
||||
"documentation/topics/resources/embedded-resources.md",
|
||||
"documentation/topics/resources/code-interfaces.md",
|
||||
"documentation/topics/resources/identities.md"
|
||||
"documentation/topics/resources/identities.md",
|
||||
"documentation/topics/resources/relationships.md"
|
||||
],
|
||||
Actions: [
|
||||
"documentation/topics/actions/actions.md",
|
||||
|
@ -114,7 +113,7 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/actions/update-actions.md",
|
||||
"documentation/topics/actions/destroy-actions.md",
|
||||
"documentation/topics/actions/generic-actions.md",
|
||||
"documentation/topics/actions/manual-actions.md",
|
||||
"documentation/topics/actions/manual-actions.md"
|
||||
],
|
||||
Security: [
|
||||
"documentation/topics/security/actors-and-authorization.md",
|
||||
|
@ -146,7 +145,6 @@ defmodule Ash.MixProject do
|
|||
"Under Review": [
|
||||
# Documentation below this line is pending review
|
||||
"documentation/topics/domains.md",
|
||||
"documentation/how_to/defining-manual-relationships.md",
|
||||
"documentation/how_to/handle-errors.md",
|
||||
"documentation/how_to/structure-your-project.md",
|
||||
"documentation/how_to/use-without-data-layers.md",
|
||||
|
@ -154,13 +152,11 @@ defmodule Ash.MixProject do
|
|||
"documentation/topics/code-interface.md",
|
||||
"documentation/topics/extending-resources.md",
|
||||
"documentation/topics/expressions.md",
|
||||
"documentation/topics/managing-relationships.md",
|
||||
"documentation/topics/monitoring.md",
|
||||
"documentation/topics/multitenancy.md",
|
||||
"documentation/topics/notifiers.md",
|
||||
"documentation/topics/pub_sub.md",
|
||||
"documentation/topics/reactor.md",
|
||||
"documentation/topics/relationships.md",
|
||||
"documentation/topics/testing.md",
|
||||
"documentation/topics/timeouts.md",
|
||||
"documentation/topics/validations.md"
|
||||
|
|
Loading…
Reference in a new issue