Packages
ash
0.1.8
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
Current section
Files
lib/ash/actions/side_load.ex
defmodule Ash.Actions.SideLoad do
@moduledoc false
alias Ash.Actions.PrimaryKeyHelpers
alias Ash.Engine
alias Ash.Engine.Request
def requests(
query,
use_data_for_filter? \\ true,
root_data \\ [],
root_query \\ nil,
path \\ []
)
def requests(nil, _, _, _, _), do: []
def requests(%{side_load: []}, _, _, _root_query, _), do: []
def requests(
%{side_load: side_loads} = query,
use_data_for_filter?,
root_data,
root_query,
path
) do
root_query = root_query || query
side_loads
|> List.wrap()
|> Enum.flat_map(fn {relationship, further} ->
relationship = Ash.relationship(query.resource, relationship)
related_query =
case further do
%Ash.Query{} = query ->
query
further ->
root_query.api
|> Ash.Query.new(relationship.destination)
|> Ash.Query.side_load(further)
end
new_path = [relationship | path]
requests(related_query, use_data_for_filter?, root_data, root_query, new_path) ++
do_requests(
relationship,
related_query,
path,
root_query,
root_data,
use_data_for_filter?
)
end)
end
def side_load(data, query, opts \\ [])
def side_load([], _query, _opts), do: {:ok, []}
def side_load(nil, _query, _opts), do: {:ok, nil}
def side_load(data, %{side_load: []}, _opts), do: {:ok, data}
def side_load(data, query, opts) when not is_list(data) do
data
|> List.wrap()
|> side_load(query, opts)
|> case do
{:ok, [record]} -> {:ok, record}
{:error, error} -> {:error, error}
end
end
def side_load([%resource{} | _] = data, side_load_query, opts) do
api = side_load_query.api
{:ok, pkey_filters} = PrimaryKeyHelpers.values_to_primary_key_filters(resource, data)
new_query = Ash.Query.filter(side_load_query, or: pkey_filters)
requests = requests(new_query, false, data)
case Engine.run(requests, api, opts) do
%{data: %{side_load: _} = state, errors: errors} when errors == [] ->
{:ok, attach_side_loads(data, state)}
%{errors: errors} ->
{:error, errors}
end
end
def attach_side_loads([%resource{} | _] = data, %{side_load: side_loads})
when is_list(data) do
side_loads
|> Enum.sort_by(fn {key, _value} ->
last_relationship = last_relationship!(resource, key)
# We want to do `many_to_many` last, so we know we've
# done their join assocs first. Pretty hacky
{length(key), last_relationship.type == :many_to_many}
end)
|> Enum.reduce(data, fn {key, value}, data ->
last_relationship = last_relationship!(resource, key)
lead_path = :lists.droplast(key)
case last_relationship do
%{type: :many_to_many, name: name} ->
attach_many_to_many_side_loads(
data,
lead_path,
last_relationship,
name,
side_loads,
value
)
%{cardinality: :many} ->
attach_to_many_side_loads(value, last_relationship, data, lead_path)
%{cardinality: :one} ->
attach_to_one_side_loads(value, last_relationship, data, lead_path)
end
end)
end
def attach_side_loads(data, state) when not is_list(data) do
[data]
|> attach_side_loads(state)
|> List.first()
end
def attach_side_loads(data, _) do
data
end
defp attach_to_many_side_loads(value, last_relationship, data, lead_path) do
values = Enum.group_by(value, &Map.get(&1, last_relationship.destination_field))
map_or_update(data, lead_path, fn record ->
source_key = Map.get(record, last_relationship.source_field)
related_records = Map.get(values, source_key, [])
Map.put(record, last_relationship.name, related_records)
end)
end
defp attach_to_one_side_loads(value, last_relationship, data, lead_path) do
values =
Enum.into(value, %{}, fn item ->
{Map.get(item, last_relationship.destination_field), item}
end)
map_or_update(data, lead_path, fn record ->
source_key = Map.get(record, last_relationship.source_field)
related_record = Map.get(values, source_key)
Map.put(record, last_relationship.name, related_record)
end)
end
defp attach_many_to_many_side_loads(data, lead_path, last_relationship, name, side_loads, value) do
join_association = String.to_existing_atom(to_string(name) <> "_join_assoc")
join_path = lead_path ++ [join_association]
join_data =
side_loads
|> Map.get(join_path, [])
map_or_update(data, lead_path, fn record ->
source_value = Map.get(record, last_relationship.source_field)
join_values =
join_data
|> Enum.filter(fn join_row ->
Map.get(join_row, last_relationship.source_field_on_join_table) ==
source_value
end)
|> Enum.map(&Map.get(&1, last_relationship.destination_field_on_join_table))
related_records =
value
|> Enum.filter(fn value ->
destination_value = Map.get(value, last_relationship.destination_field)
destination_value in join_values
end)
Map.put(record, last_relationship.name, related_records)
end)
end
defp map_or_update(nil, _, _), do: nil
defp map_or_update(record, [], func) when not is_list(record), do: func.(record)
defp map_or_update(records, [], func) do
Enum.map(records, fn record ->
if record do
func.(record)
else
nil
end
end)
end
defp map_or_update(records, [path | tail], func) do
map_or_update(records, [], fn record ->
Map.update!(record, path, &map_or_update(&1, tail, func))
end)
end
defp last_relationship!(resource, [last]) do
Ash.relationship(resource, last) || raise "Assumption Failed"
end
defp last_relationship!(resource, [first | rest]) do
relationship = Ash.relationship(resource, first) || raise "Assumption Failed"
last_relationship!(relationship.destination, rest)
end
defp do_requests(relationship, related_query, path, root_query, root_data, use_data_for_filter?) do
side_load_request =
side_load_request(
relationship,
related_query,
root_query,
path,
root_data,
use_data_for_filter?
)
case relationship.type do
:many_to_many ->
case join_assoc_request(
relationship,
related_query,
root_query,
path,
root_data,
use_data_for_filter?
) do
nil ->
[side_load_request]
request ->
[side_load_request, request]
end
_ ->
[side_load_request]
end
end
defp side_load_request(
relationship,
related_query,
root_query,
path,
root_data,
use_data_for_filter?
) do
dependencies =
case path do
[] ->
[]
dependent_path ->
[[:side_load, Enum.reverse(Enum.map(dependent_path, &Map.get(&1, :name))), :data]]
end
request_path = [
:side_load,
Enum.reverse(Enum.map([relationship | path], &Map.get(&1, :name)))
]
dependencies =
if use_data_for_filter? do
[[:data, :data] | dependencies]
else
[request_path ++ [:query] | dependencies]
end
dependencies =
if relationship.type == :many_to_many do
join_relationship = join_relationship(relationship)
[[:side_load, join_relationship_path(path, join_relationship), :data] | dependencies]
else
dependencies
end
source =
[relationship | path]
|> Enum.reverse()
|> Enum.map_join(".", &Map.get(&1, :name))
Engine.Request.new(
action: Ash.primary_action!(relationship.destination, :read),
resource: relationship.destination,
name: "side_load #{source}",
api: related_query.api,
path: request_path,
query:
side_load_query(
relationship,
related_query,
path,
root_query,
use_data_for_filter?
),
data:
Request.resolve(dependencies, fn data ->
new_query =
true_side_load_query(
relationship,
related_query,
data,
path,
root_data,
use_data_for_filter?
)
with {:ok, query} <- new_query,
{:ok, results} <- related_query.api.read(query) do
{:ok, results}
else
{:error, error} -> {:error, error}
end
end)
)
end
defp join_relationship(relationship) do
Ash.relationship(
relationship.source,
String.to_existing_atom(to_string(relationship.name) <> "_join_assoc")
)
end
defp join_relationship_path(path, join_relationship) do
Enum.reverse(Enum.map([join_relationship | path], & &1.name))
end
defp join_assoc_request(
relationship,
related_query,
root_query,
path,
root_data,
use_data_for_filter?
) do
join_relationship = join_relationship(relationship)
join_relationship_name = join_relationship.name
case path do
[%{name: ^join_relationship_name} | _] ->
nil
path ->
join_relationship_path = join_relationship_path(path, join_relationship)
dependencies =
if path == [] do
[]
else
[[:side_load, Enum.reverse(Enum.map(path, &Map.get(&1, :name))), :data]]
end
dependencies =
if use_data_for_filter? do
[[:data, :data] | dependencies]
else
[[:side_load, join_relationship_path, :query] | dependencies]
end
related_query = related_query.api.query(join_relationship.destination)
Request.new(
action: Ash.primary_action!(relationship.destination, :read),
resource: relationship.through,
name: "side_load join #{join_relationship.name}",
api: related_query.api,
path: [:side_load, join_relationship_path],
query:
side_load_query(
join_relationship,
related_query,
join_relationship_path,
root_query,
use_data_for_filter?
),
data:
Request.resolve(dependencies, fn data ->
new_query =
true_side_load_query(
join_relationship,
related_query,
data,
path,
root_data,
use_data_for_filter?
)
with {:ok, query} <- new_query,
{:ok, results} <- related_query.api.read(query) do
{:ok, results}
else
{:error, error} -> {:error, error}
end
end)
)
end
end
defp side_load_query(
%{reverse_relationship: nil, type: :many_to_many} = relationship,
_related_query,
_path,
_root_query,
_use_data_for_filter?
) do
Request.resolve(fn _ ->
{:error, "Required reverse relationship for #{inspect(relationship)}"}
end)
end
defp side_load_query(
relationship,
related_query,
path,
_root_query,
true
) do
Request.resolve([[:data, :data]], fn %{data: %{data: data}} ->
root_filter =
case data do
[] ->
[__impossible__: true]
[%resource{} = item] ->
item
|> Map.take(Ash.primary_key(resource))
|> Enum.to_list()
[%resource{} | _] = items ->
pkey = Ash.primary_key(resource)
[or: Enum.map(items, fn item -> item |> Map.take(pkey) |> Enum.to_list() end)]
end
case reverse_relationship_path(relationship, path) do
{:ok, reverse_path} ->
related_query
|> Ash.Query.unset(:side_load)
|> Ash.Query.filter(
put_nested_relationship(root_filter, reverse_path, root_filter, false)
)
|> extract_errors()
_ ->
relationship.destination
|> related_query.api.query()
|> Ash.Query.filter(related_query.filter)
|> extract_errors()
end
end)
end
defp side_load_query(
relationship,
related_query,
path,
root_query,
false
) do
case reverse_relationship_path(relationship, path) do
{:ok, reverse_path} ->
related_query
|> Ash.Query.filter(put_nested_relationship([], reverse_path, root_query.filter, false))
|> Ash.Query.unset(:side_load)
:error ->
related_query
|> Ash.Query.unset(:side_load)
end
end
defp extract_errors(%{errors: []} = item), do: {:ok, item}
defp extract_errors(%{errors: errors}), do: {:error, errors}
defp true_side_load_query(
%{type: :many_to_many, reverse_relationship: nil} = relationship,
_filter,
_data,
_path,
_root_data,
_use_data?
) do
{:error, "Required reverse relationship for #{inspect(relationship)}"}
end
defp true_side_load_query(
%{reverse_relationship: reverse_relationship, source: source} = relationship,
query,
data,
path,
root_data,
use_data?
) do
pkey = Ash.primary_key(source)
source_data =
case path do
[] when use_data? ->
Map.get(data, :data)
[] ->
%{data: root_data}
path ->
path_names = path |> Enum.reverse() |> Enum.map(& &1.name)
data
|> Map.get(:side_load, %{})
|> Map.get(path_names, %{})
end
related_data = Map.get(source_data || %{}, :data, [])
if reverse_relationship do
values = get_fields(related_data, pkey)
new_query =
Ash.Query.filter(query, put_nested_relationship([], [reverse_relationship], values))
{:ok, Ash.Query.unset(new_query, :side_load)}
else
ids =
Enum.flat_map(related_data, fn data ->
data
|> Map.get(relationship.source_field)
|> List.wrap()
end)
filter_value =
case ids do
[id] ->
id
ids ->
[in: ids]
end
new_query =
query
|> Ash.Query.filter([{relationship.destination_field, filter_value}])
|> Ash.Query.unset(:side_load)
{:ok, new_query}
end
end
defp reverse_relationship_path(relationship, prior_path, acc \\ [])
defp reverse_relationship_path(relationship, [], acc) do
case relationship.reverse_relationship do
nil ->
:error
reverse ->
{:ok, Enum.reverse([reverse | acc])}
end
end
defp reverse_relationship_path(relationship, [next_relationship | rest], acc) do
case relationship.reverse_relationship do
nil ->
:error
reverse ->
reverse_relationship_path(next_relationship, rest, [reverse | acc])
end
end
defp get_fields(data, fields)
defp get_fields(data, fields) do
data
|> List.wrap()
|> Enum.map(&Map.take(&1, fields))
|> Enum.uniq()
end
defp put_nested_relationship(request_filter, path, value, records? \\ true)
defp put_nested_relationship(_, _, [], true), do: [__impossible__: true]
defp put_nested_relationship(_, _, nil, true), do: [__impossible__: true]
defp put_nested_relationship(_, _, [], false), do: []
defp put_nested_relationship(_, _, nil, false), do: []
defp put_nested_relationship(request_filter, path, value, records?) when not is_list(value) do
put_nested_relationship(request_filter, path, [value], records?)
end
defp put_nested_relationship(request_filter, [rel | rest], values, records?) do
[
{rel, put_nested_relationship(request_filter, rest, values, records?)}
]
end
defp put_nested_relationship(request_filter, [], [{field, value}], _) do
[{field, value} | request_filter]
end
defp put_nested_relationship(request_filter, [], [{field, _} | _] = keys, _) do
[{field, [{:in, Enum.map(keys, &elem(&1, 1))}]} | request_filter]
end
defp put_nested_relationship(request_filter, [], [values], _) do
request_filter ++ values
end
defp put_nested_relationship(request_filter, [], values, _) do
Keyword.update(request_filter, :or, values, &Kernel.++(&1, values))
end
end