Packages

ash

3.11.1
3.29.3 3.29.2 3.29.1 3.29.0 3.28.0 3.27.8 3.27.7 3.27.6 3.27.5 3.27.4 3.27.3 3.27.2 3.27.1 3.27.0 3.26.0 3.25.2 3.25.1 3.25.0 retired 3.24.7 3.24.6 3.24.5 3.24.4 3.24.3 3.24.2 3.24.1 3.24.0 3.23.1 3.23.0 3.22.2 3.22.1 3.22.0 3.21.3 3.21.2 3.21.1 3.21.0 3.20.0 3.19.3 3.19.2 3.19.1 3.19.0 3.18.0 3.17.1 3.17.0 3.16.0 3.15.0 3.14.1 3.14.0 retired 3.13.2 3.13.1 3.13.0 3.12.0 3.11.3 3.11.2 3.11.1 3.11.0 3.10.1 3.10.0 3.9.0 3.8.0 3.7.6 3.7.5 3.7.4 3.7.3 3.7.2 3.7.1 3.7.0 retired 3.6.3 retired 3.6.2 3.6.1 3.6.0 3.5.43 3.5.42 3.5.41 3.5.40 3.5.39 3.5.38 3.5.37 3.5.36 3.5.35 3.5.34 3.5.33 3.5.32 3.5.31 3.5.30 3.5.29 3.5.28 3.5.27 3.5.26 3.5.25 3.5.24 3.5.23 3.5.22 3.5.21 3.5.20 3.5.19 3.5.18 3.5.17 3.5.16 3.5.15 3.5.14 3.5.13 3.5.12 3.5.11 3.5.10 3.5.9 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.74 retired 3.4.73 3.4.72 3.4.71 3.4.70 3.4.69 3.4.68 3.4.67 3.4.66 3.4.65 3.4.64 3.4.63 3.4.62 3.4.61 3.4.60 3.4.59 3.4.58 3.4.57 3.4.56 3.4.55 3.4.54 3.4.53 3.4.52 3.4.51 3.4.50 3.4.49 3.4.48 3.4.47 3.4.46 3.4.45 3.4.44 3.4.43 3.4.42 3.4.41 3.4.40 3.4.39 3.4.38 3.4.37 3.4.36 3.4.35 3.4.34 3.4.33 3.4.32 3.4.31 3.4.30 3.4.29 3.4.28 3.4.27 3.4.26 3.4.25 3.4.24 3.4.23 3.4.22 3.4.21 3.4.20 3.4.19 3.4.18 3.4.17 3.4.16 3.4.15 3.4.14 3.4.13 3.4.12 3.4.11 3.4.10 3.4.9 3.4.8 3.4.7 3.4.6 3.4.5 3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.3 3.3.2 3.3.1 3.3.0 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 3.2.0 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.16 3.0.15 3.0.14 3.0.13 3.0.12 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.4 3.0.3 3.0.2 3.0.1 3.0.0 3.0.0-rc.46 3.0.0-rc.45 3.0.0-rc.44 3.0.0-rc.43 3.0.0-rc.42 3.0.0-rc.41 3.0.0-rc.40 3.0.0-rc.39 3.0.0-rc.38 3.0.0-rc.37 3.0.0-rc.36 3.0.0-rc.35 3.0.0-rc.34 3.0.0-rc.33 3.0.0-rc.32 3.0.0-rc.31 3.0.0-rc.29 3.0.0-rc.28 3.0.0-rc.27 3.0.0-rc.26 3.0.0-rc.25 3.0.0-rc.24 3.0.0-rc.23 3.0.0-rc.22 3.0.0-rc.21 3.0.0-rc.20 3.0.0-rc.19 3.0.0-rc.18 3.0.0-rc.17 3.0.0-rc.16 3.0.0-rc.15 3.0.0-rc.14 3.0.0-rc.13 3.0.0-rc.12 3.0.0-rc.11 3.0.0-rc.10 3.0.0-rc.9 3.0.0-rc.8 3.0.0-rc.7 3.0.0-rc.6 3.0.0-rc.5 3.0.0-rc.4 3.0.0-rc.3 3.0.0-rc.1 3.0.0-rc.0 2.21.15 2.21.14 2.21.13 2.21.12 2.21.11 2.21.10 2.21.9 2.21.8 2.21.7 2.21.6 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.3 2.20.2 2.20.1 2.20.0 2.19.14 2.19.13 2.19.12 2.19.11 2.19.10 2.19.9 2.19.8 2.19.7 2.19.6 2.19.5 2.19.4 2.19.3 retired 2.19.2 retired 2.19.1 retired 2.19.0 retired 2.18.2 2.18.1 2.18.0 2.17.24 2.17.23 2.17.22 2.17.21 2.17.20 2.17.19 2.17.18 2.17.17 2.17.16 2.17.15 2.17.14 2.17.13 2.17.12 2.17.11 2.17.10 2.17.9 2.17.8 2.17.7 2.17.6 2.17.5 2.17.4 2.17.3 2.17.2 2.17.1 2.17.0 2.16.1 2.16.0 2.15.20 2.15.19 2.15.18 2.15.17 2.15.16 2.15.15 2.15.14 2.15.13 2.15.12 2.15.11 2.15.10 2.15.9 2.15.8 2.15.7 2.15.6 2.15.5 2.15.4 2.15.2 2.15.1 2.15.0 2.14.21 2.14.20 2.14.19 2.14.18 2.14.17 2.14.16 2.14.15 2.14.14 2.14.13 2.14.12 2.14.11 2.14.10 2.14.9 2.14.8 2.14.7 2.14.6 2.14.5 2.14.4 2.14.3 2.14.2 2.14.1 2.14.0 2.13.4 retired 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.11 2.11.10 2.11.9 2.11.8 2.11.7 2.11.6 2.11.5 2.11.4 2.11.3 2.11.2 2.11.1 2.11.0 2.11.0-rc.3 2.11.0-rc.2 2.11.0-rc.1 2.11.0-rc.0 2.10.2 2.10.1 2.10.0 2.9.29 2.9.28 2.9.27 2.9.26 2.9.25 2.9.24 2.9.23 2.9.22 2.9.21 2.9.20 2.9.19 2.9.18 2.9.17 2.9.16 2.9.15 2.9.14 2.9.13 2.9.12 2.9.11 2.9.10 2.9.9 2.9.8 2.9.7 2.9.6 2.9.5 2.9.4 2.9.3 2.9.2 2.9.1 2.9.0 2.8.1 2.8.0 2.7.1 2.7.0 2.6.31 2.6.30 2.6.29 2.6.28 2.6.27 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.16 2.5.15 2.5.14 2.5.13 2.5.12 2.5.11 2.5.10 2.5.9 2.5.8 2.5.7 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.5.0-rc.6 2.5.0-rc.5 2.5.0-rc.4 2.5.0-rc.3 2.5.0-rc.2 2.5.0-rc.1 2.5.0-rc.0 2.4.30 2.4.29 2.4.28 2.4.27 2.4.26 2.4.25 2.4.24 2.4.23 2.4.22 2.4.21 2.4.20 2.4.19 2.4.18 2.4.17 2.4.16 2.4.15 2.4.14 2.4.13 2.4.12 2.4.11 2.4.10 2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 2.3.0 2.2.0 2.1.0 2.0.0 2.0.0-rc.15 2.0.0-rc.14 2.0.0-rc.13 2.0.0-rc.12 2.0.0-rc.11 2.0.0-rc.10 2.0.0-rc.9 2.0.0-rc.8 2.0.0-rc.7 2.0.0-rc.6 2.0.0-rc.5 2.0.0-rc.4 2.0.0-rc.3 2.0.0-rc.2 2.0.0-rc.1 2.0.0-rc.0 2.0.0-pre.8 2.0.0-pre.7 2.0.0-pre.6 2.0.0-pre.5 2.0.0-pre.4 2.0.0-pre.3 2.0.0-pre.2 2.0.0-pre.1 2.0.0-pre.0 1.53.3 1.53.2 1.53.0 1.52.0-rc.22 1.52.0-rc.21 1.52.0-rc.20 1.52.0-rc.19 1.52.0-rc.18 1.52.0-rc.17 1.52.0-rc.16 1.52.0-rc.15 1.52.0-rc.14 1.52.0-rc.13 1.52.0-rc.12 1.52.0-rc.11 1.52.0-rc.10 1.52.0-rc.9 1.52.0-rc.8 1.52.0-rc.7 1.52.0-rc.6 1.52.0-rc.5 1.52.0-rc.4 1.52.0-rc.3 1.52.0-rc.2 1.52.0-rc.1 1.52.0-rc.0 1.51.2 1.51.1 retired 1.51.0 1.50.21 1.50.20 1.50.19 1.50.18 1.50.17 1.50.16 1.50.15 1.50.14 1.50.13 1.50.12 1.50.11 1.50.10 1.50.9 1.50.8 1.50.7 1.50.6 1.50.5 1.50.4 1.50.3 1.50.2 1.50.1 1.50.0 1.49.0 1.48.0-rc.30 1.48.0-rc.29 1.48.0-rc.28 1.48.0-rc.27 1.48.0-rc.26 1.48.0-rc.25 1.48.0-rc.24 1.48.0-rc.23 1.48.0-rc.22 1.48.0-rc.21 1.48.0-rc.20 1.48.0-rc.19 1.48.0-rc.18 1.48.0-rc.17 1.48.0-rc.16 1.48.0-rc.15 1.48.0-rc.14 1.48.0-rc.13 1.48.0-rc.12 1.48.0-rc.11 1.48.0-rc.10 1.48.0-rc.9 1.48.0-rc.8 1.48.0-rc.7 1.48.0-rc.6 1.48.0-rc.5 1.48.0-rc.4 1.48.0-rc.3 1.48.0-rc.2 1.48.0-rc.1 1.48.0-rc.0 1.47.12 1.47.11 1.47.10 1.47.9 1.47.8 1.47.7 1.47.6 1.47.5 1.47.4 1.47.3 1.47.2 1.47.1 1.47.0 1.46.13 1.46.12 1.46.11 1.46.10 1.46.9 1.46.8 1.46.7 1.46.6 1.46.5 1.46.4 1.46.3 1.46.2 1.46.1 1.46.0 1.45.0-rc9 1.45.0-rc8 1.45.0-rc7 1.45.0-rc6 1.45.0-rc5 1.45.0-rc4 1.45.0-rc3 1.45.0-rc20 1.45.0-rc2 1.45.0-rc19 1.45.0-rc18 1.45.0-rc17 1.45.0-rc16 1.45.0-rc15 1.45.0-rc14 1.45.0-rc13 1.45.0-rc12 1.45.0-rc11 1.45.0-rc10 1.45.0-rc1 1.45.0-rc0 1.44.13 1.44.12 1.44.11 1.44.10 1.44.9 1.44.8 1.44.7 1.44.6 1.44.5 1.44.4 1.44.3 1.44.2 1.44.1 1.44.0 1.43.12 1.43.11 1.43.10 1.43.9 1.43.8 1.43.7 1.43.6 1.43.5 1.43.4 1.43.3 1.43.2 1.43.1 1.43.0 1.42.0 1.41.12 1.41.11 1.41.10 1.41.9 1.41.8 1.41.7 1.41.6 1.41.5 1.41.4 1.41.3 1.41.2 1.41.1 1.41.0 1.40.0 1.39.7 1.39.6 1.39.5 1.39.4 1.39.3 1.39.2 1.39.1 1.39.0 1.38.0 1.37.2 1.37.1 1.37.0 1.36.22 1.36.21 1.36.19 1.36.18 1.36.17 1.36.16 1.36.15 1.36.14 1.36.13 1.36.12 1.36.11 1.36.10 1.36.9 1.36.8 1.36.7 1.36.6 1.36.5 1.36.4 1.36.3 1.36.2 1.36.0 1.35.1 1.35.0 1.34.9 1.34.8 1.34.7 1.34.6 1.34.5 1.34.4 1.34.3 1.34.2 1.34.1 1.34.0 1.33.0 1.32.2 1.32.1 1.32.0 1.31.1 1.31.0 1.30.2 1.30.1 1.29.0-rc1 1.29.0-rc0 1.28.1 1.28.0 1.27.1 1.27.0 1.26.13 1.26.12 1.26.11 1.26.10 1.26.9 1.26.8 1.26.7 1.26.6 1.26.5 1.26.4 1.26.2 1.26.1 1.26.0 1.25.8 1.25.7 1.25.6 1.25.5 1.25.4 1.25.3 1.25.2 1.25.1 1.25.0 1.24.2 1.24.1 1.24.0 1.23.3 1.23.2 1.23.1 1.23.0 1.22.1 1.22.0 1.20.1 1.20.0 1.19.1 1.19.0 1.18.1 1.18.0 1.17.1 1.17.0 1.16.2 1.15.1 1.15.0 1.14.0 1.13.4 1.13.3 1.13.2 1.13.1 1.13.0 1.12.0 1.11.1 1.11.0 1.10.0 1.9.0 1.8.0 1.7.0 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.1 1.5.0 1.4.1 1.4.0 1.3.1 1.3.0 1.2.1 1.2.0 1.1.3 1.1.2 1.1.0 1.0.3 1.0.2 1.0.1 1.0.0 0.13.1 0.13.0 0.12.0 0.10.0 0.9.1 0.9.0 0.8.0 0.7.0 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.2 0.5.1 0.5.0 0.4.0 0.3.0 0.2.0 0.1.9 0.1.8 0.1.3 0.1.1 0.1.0

A declarative, extensible framework for building Elixir applications.

Security advisory: This version has known vulnerabilities. View advisories

Current section

Files

Jump to
ash lib ash resource change cascade_destroy.ex
Raw

lib/ash/resource/change/cascade_destroy.ex

# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs.contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Resource.Change.CascadeDestroy do
@option_schema [
relationship: [
type: :atom,
doc: "The name of the relationship to work on",
required: true
],
action: [
type: :atom,
doc:
"The name of the destroy action to call on the related resource. Uses the primary destroy by default.",
required: false
],
read_action: [
type: :atom,
doc:
"The name of the read action to call on the related resource to find results to be destroyed",
required: false
],
return_notifications?: [
type: :boolean,
doc: "Return notifications for all destroyed records?",
required: false,
default: false
],
after_action?: [
type: :boolean,
doc:
"If true, cascade destroys are done in after_action hooks. If false, they run as before_action hooks. Defaults to true for atomic action compatibility",
required: false,
default: true
],
domain: [
type: {:spark, Ash.Domain},
private?: true
]
]
option_schema = @option_schema
defmodule Opts do
@moduledoc false
use Spark.Options.Validator, schema: option_schema
end
@moduledoc """
Cascade a resource's destroy action to a related resource's destroy action.
## Timing Control
The `after_action?` option controls when the cascade destroy occurs:
- `after_action?: true` (default) - Runs as an after-action hook, making it safe for atomic actions
- `after_action?: false` - Runs as a before-action hook
When `after_action?: true`, the change adds an after-action hook that explicitly
calls destroy on any records related via the named relationship. It will optimise
for bulk destroys where possible. This makes it safe to use in atomic actions, but
might not be possible depending on the data layer setup (see warning below).
When `after_action?: false`, the change simply runs as a before_action.
Requires keyset pagination on the primary read action of the targeted relation.
> #### Beware database constraints {: .warning}
>
> Think carefully before using this change with data layers which enforce
> referential integrity (ie PostgreSQL and SQLite) and you may need to defer
> constraints for the relationship in question.
>
> See also:
> 1. [`postgres.references.reference.deferrable` DSL](https://hexdocs.pm/ash_postgres/dsl-ashpostgres-datalayer.html#postgres-references-reference-deferrable)
> 2. [`sqlite.references.reference.deferrable` DSL](https://hexdocs.pm/ash_sqlite/dsl-ashsqlite-datalayer.html#sqlite-references-reference-deferrable)
> 3. [PostgreSQL's `SET CONSTRAINTS` documentation](https://www.postgresql.org/docs/current/sql-set-constraints.html)
> 4. [SQLite's `PRAGMA defer_foreign_keys` documentation](https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys)
> #### Cascading notifications {: .tip}
>
> By default notifications are disabled for the related destroy. This is to avoid potentially sending a **lot** of notifications for high-cardinality relationships.
## Options
#{Opts.docs()}
## Example
change {Ash.Resource.Change.CascadeDestroy, relationship: :comments, action: :destroy}
or, equivalently using `Ash.Resource.Change.Builtins.cascade_destroy/2`:
change cascade_destroy(:comments, action: :destroy)
"""
use Ash.Resource.Change
require Ash.Query
@doc false
@impl true
def change(changeset, opts, context) do
with {:ok, %Opts{} = opts} <- Opts.validate(opts),
{:ok, opts} <- validate_relationship_and_action(opts, changeset.resource) do
if not opts.after_action? and
opts.relationship.type in [:many_to_many, :has_many, :has_one] do
Ash.Changeset.before_action(changeset, fn changeset ->
# can't produce `{:error, error}` because `changeset.phase` is before_action
case {destroy_related([changeset.data], opts, context, changeset),
opts.return_notifications?} do
{_, false} ->
changeset
{%{notifications: []}, true} ->
changeset
{%{notifications: notifications}, true} ->
{changeset, %{notifications: notifications}}
end
end)
else
Ash.Changeset.after_action(changeset, fn changeset, result ->
case {destroy_related([result], opts, context, changeset), opts.return_notifications?} do
{{:error, error}, _} ->
{:error, error}
{_, false} ->
{:ok, result}
{%{notifications: []}, true} ->
{:ok, result}
{%{notifications: notifications}, true} ->
{:ok, result, notifications}
end
end)
end
else
{:error, reason} ->
Ash.Changeset.add_error(changeset, reason)
end
end
@doc false
@impl true
def atomic(%{resource: resource}, opts, _) do
with {:ok, %Opts{} = opts} <- Opts.validate(opts),
{:ok, opts} <- validate_relationship_and_action(opts, resource) do
if not opts.after_action? and
opts.relationship.type in [:many_to_many, :has_many, :has_one] do
{:not_atomic, "Need to destroy relationships in a before batch"}
else
:ok
end
else
{:error, error} ->
{:error, error}
end
end
@doc false
@impl true
def before_batch([%{resource: resource} = changeset | _] = changesets, opts, context) do
with {:ok, %Opts{} = opts} <- Opts.validate(opts),
{:ok, opts} <- validate_relationship_and_action(opts, resource) do
records = Enum.map(changesets, & &1.data)
if not opts.after_action? and
opts.relationship.type in [:many_to_many, :has_many, :has_one] do
case {destroy_related(records, opts, context, changeset), opts.return_notifications?} do
{{:error, error}, _} ->
Enum.map(changesets, &Ash.Changeset.add_error(&1, error))
{_, false} ->
changesets
{%{notifications: empty}, true} when empty in [nil, []] ->
changesets
{%{notifications: notifications}, true} ->
Enum.concat(changesets, notifications)
{:ok, _} ->
changesets
end
else
changesets
end
else
{:error, reason} -> [{:error, reason}]
end
end
@doc false
@impl true
def after_batch(
[{%{resource: resource} = changeset, _} | _] = changesets_and_results,
opts,
context
) do
with {:ok, %Opts{} = opts} <- Opts.validate(opts),
{:ok, opts} <- validate_relationship_and_action(opts, resource) do
records = Enum.map(changesets_and_results, &elem(&1, 1))
result = Enum.map(records, &{:ok, &1})
if not opts.after_action? and
opts.relationship.type in [:many_to_many, :has_many, :has_one] do
result
else
case {destroy_related(records, opts, context, changeset), opts.return_notifications?} do
{{:error, error}, _} ->
Enum.map(result, fn _ -> {:error, error} end)
{_, false} ->
result
{%{notifications: empty}, true} when empty in [nil, []] ->
result
{%{notifications: notifications}, true} ->
Enum.concat(result, notifications)
end
end
else
{:error, reason} -> [{:error, reason}]
end
end
# @impl true
# def batch_change(changesets, _opts, _context) do
# changesets
# end
@doc false
@impl true
def batch_callbacks?([], _, _), do: false
def batch_callbacks?(_, _, _), do: true
@doc false
def opt_schema, do: @option_schema
defp validate_relationship_and_action(opts, resource) do
case Ash.Resource.Info.relationship(resource, opts.relationship) do
nil ->
{:error,
Ash.Error.Changes.InvalidRelationship.exception(
relationship: opts.relationship,
message: "Relationship doesn't exist."
)}
relationship ->
if opts.action do
case Ash.Resource.Info.action(relationship.destination, opts.action) do
action when action.type == :destroy ->
{:ok,
%{
opts
| action: action,
relationship: relationship,
domain:
relationship.domain || Ash.Resource.Info.domain(relationship.destination)
}}
_ ->
{:error,
Ash.Error.Invalid.NoSuchAction.exception(
resource: relationship.destination,
action: opts.action,
destroy: :destroy
)}
end
else
{:ok,
%{
opts
| action: Ash.Resource.Info.primary_action!(relationship.destination, :destroy),
relationship: relationship,
domain: relationship.domain || Ash.Resource.Info.domain(relationship.destination)
}}
end
end
end
defp destroy_related([], _, _, _), do: :ok
defp destroy_related(data, opts, %{tenant: tenant} = context, changeset) do
action = opts.action
relationship = opts.relationship
context_opts =
context
|> Ash.Context.to_opts(
domain: opts.domain,
return_errors?: true,
strategy: [:stream, :atomic, :atomic_batches],
return_notifications?: opts.return_notifications?
)
context =
Map.merge(relationship.context || %{}, %{
cascade_destroy: true,
accessing_from: %{source: relationship.source, name: relationship.name}
})
context_opts =
Keyword.update(
context_opts,
:context,
context,
&Map.merge(&1, context)
)
case related_query(data, opts, context_opts) do
{:ok, query} ->
Ash.bulk_destroy!(
query,
action.name,
%{},
context_opts
)
:error ->
if changeset.phase == :after_action and changeset.action_type == :destroy do
{:error,
"Unable to cascade destroy for #{relationship.name} relationship in after_action"}
else
data
|> List.wrap()
|> Ash.load!(
[
{relationship.name,
Ash.Query.set_context(relationship.destination, %{
cascade_destroy: true,
accessing_from: %{source: relationship.source, name: relationship.name}
})}
],
tenant: tenant
)
|> Enum.flat_map(fn record ->
record
|> Map.get(relationship.name)
|> List.wrap()
end)
|> Ash.bulk_destroy!(
action.name,
%{},
Keyword.update(
context_opts,
:context,
context,
&Map.merge(&1, context)
)
)
end
end
end
defp related_query(_records, opts, _) when opts.relationship.type == :many_to_many, do: :error
defp related_query(records, opts, context_opts) do
read_action_name =
opts.read_action || opts.relationship.read_action || opts.action.atomic_upgrade_with
read_action =
if read_action_name do
Ash.Resource.Info.action(opts.relationship.destination, read_action_name)
else
Ash.Resource.Info.primary_action!(opts.relationship.destination, :read)
end
related_query =
Ash.Query.for_read(
opts.relationship.destination,
read_action.name,
%{},
context_opts
)
related_query =
Ash.Actions.Read.Relationships.related_query(
opts.relationship.name,
records,
related_query,
Ash.Query.new(opts.relationship.source)
)
|> elem(1)
|> filter_by_keys(opts.relationship, records)
if Ash.Actions.Read.Relationships.has_parent_expr?(
opts.relationship,
related_query.context,
related_query.domain
) do
:error
else
{:ok, related_query}
end
end
defp filter_by_keys(query, %{no_attributes?: true}, _records) do
query
end
defp filter_by_keys(
query,
%{source_attribute: source_attribute, destination_attribute: destination_attribute},
records
) do
source_values = Enum.map(records, &Map.get(&1, source_attribute))
Ash.Query.filter(query, ^ref(destination_attribute) in ^source_values)
end
end