mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
improvement: add no_fields?
relationships
This commit is contained in:
parent
98a7ac06b8
commit
904968b936
10 changed files with 163 additions and 258 deletions
|
@ -97,6 +97,7 @@ locals_without_parens = [
|
|||
modify_query: 1,
|
||||
module: 1,
|
||||
name: 1,
|
||||
no_fields?: 1,
|
||||
not_found_message: 1,
|
||||
on: 1,
|
||||
only_when_valid?: 1,
|
||||
|
|
|
@ -74,8 +74,15 @@ defmodule Ash.Actions.Load do
|
|||
related_query.tenant
|
||||
)
|
||||
|
||||
query =
|
||||
if Map.get(relationship, :no_fields?) do
|
||||
query
|
||||
else
|
||||
Ash.Query.ensure_selected(query, relationship.source_field)
|
||||
end
|
||||
|
||||
{
|
||||
Ash.Query.ensure_selected(query, relationship.source_field),
|
||||
query,
|
||||
requests ++
|
||||
further_requests ++
|
||||
do_requests(
|
||||
|
@ -132,6 +139,12 @@ defmodule Ash.Actions.Load do
|
|||
data
|
||||
end
|
||||
|
||||
defp attach_to_many_loads(value, %{name: name, no_fields?: true}, data, lead_path) do
|
||||
map_or_update(data, lead_path, fn record ->
|
||||
Map.put(record, name, List.wrap(value))
|
||||
end)
|
||||
end
|
||||
|
||||
defp attach_to_many_loads(value, last_relationship, data, lead_path) when is_map(value) do
|
||||
primary_key = Ash.Resource.Info.primary_key(last_relationship.source)
|
||||
|
||||
|
@ -150,6 +163,12 @@ defmodule Ash.Actions.Load do
|
|||
end)
|
||||
end
|
||||
|
||||
defp attach_to_one_loads(value, %{name: name, no_fields?: true}, data, lead_path) do
|
||||
map_or_update(data, lead_path, fn record ->
|
||||
Map.put(record, name, value |> List.wrap() |> Enum.at(0))
|
||||
end)
|
||||
end
|
||||
|
||||
defp attach_to_one_loads(value, last_relationship, data, lead_path) when is_map(value) do
|
||||
primary_key = Ash.Resource.Info.primary_key(last_relationship.source)
|
||||
|
||||
|
@ -340,10 +359,7 @@ defmodule Ash.Actions.Load do
|
|||
query:
|
||||
load_query(
|
||||
relationship,
|
||||
related_query,
|
||||
path,
|
||||
root_query,
|
||||
request_path
|
||||
related_query
|
||||
),
|
||||
data:
|
||||
data(
|
||||
|
@ -601,10 +617,7 @@ defmodule Ash.Actions.Load do
|
|||
query:
|
||||
load_query(
|
||||
join_relationship,
|
||||
related_query,
|
||||
Enum.reverse(join_relationship_path),
|
||||
root_query,
|
||||
request_path
|
||||
related_query
|
||||
),
|
||||
data:
|
||||
Request.resolve(dependencies, fn
|
||||
|
@ -966,106 +979,25 @@ defmodule Ash.Actions.Load do
|
|||
end
|
||||
end
|
||||
|
||||
defp load_query_with_reverse_path(
|
||||
root_query,
|
||||
related_query,
|
||||
reverse_path,
|
||||
root_data_filter
|
||||
) do
|
||||
case Ash.Filter.parse(root_query.resource, root_query.filter, %{}, %{}) do
|
||||
{:ok, nil} ->
|
||||
related_query
|
||||
|> Ash.Query.unset(:load)
|
||||
|> Ash.Query.filter(
|
||||
^put_nested_relationship(
|
||||
[],
|
||||
reverse_path,
|
||||
root_data_filter,
|
||||
false
|
||||
)
|
||||
)
|
||||
|> Ash.Query.filter(^related_query.filter)
|
||||
|> extract_errors()
|
||||
|
||||
{:ok, parsed} ->
|
||||
filter =
|
||||
put_nested_relationship(
|
||||
[],
|
||||
reverse_path,
|
||||
root_data_filter,
|
||||
false
|
||||
)
|
||||
|
||||
related_query
|
||||
|> Ash.Query.unset(:load)
|
||||
|> Ash.Query.filter(^filter)
|
||||
|> Ash.Query.filter(^put_nested_relationship([], reverse_path, parsed, false))
|
||||
|> Ash.Query.filter(^related_query.filter)
|
||||
|> extract_errors()
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp load_query(%{manual: manual}, related_query, _path, _root_query, _request_path)
|
||||
defp load_query(%{manual: manual}, related_query)
|
||||
when not is_nil(manual) do
|
||||
related_query
|
||||
end
|
||||
|
||||
defp load_query(
|
||||
relationship,
|
||||
related_query,
|
||||
path,
|
||||
root_query,
|
||||
request_path
|
||||
related_query
|
||||
) do
|
||||
Request.resolve([request_path ++ [:data]], fn context ->
|
||||
data =
|
||||
case get_in(context, request_path ++ [:data, :results]) do
|
||||
%page{results: results} when page in [Ash.Page.Keyset, Ash.Page.Offset] ->
|
||||
results
|
||||
|
||||
data ->
|
||||
data
|
||||
end
|
||||
|
||||
root_data_filter =
|
||||
case data do
|
||||
[] ->
|
||||
false
|
||||
|
||||
[%resource{} | _] = items ->
|
||||
pkey = Ash.Resource.Info.primary_key(resource)
|
||||
[or: Enum.map(items, fn item -> item |> Map.take(pkey) |> Enum.to_list() end)]
|
||||
end
|
||||
|
||||
path = Enum.reverse([relationship.name | Enum.map(path, & &1.name)])
|
||||
|
||||
case Ash.Resource.Info.reverse_relationship(
|
||||
root_query.resource,
|
||||
path
|
||||
) do
|
||||
nil ->
|
||||
relationship.destination
|
||||
|> Ash.Query.new(related_query.api)
|
||||
|> Ash.Query.filter(^related_query.filter)
|
||||
|> extract_errors()
|
||||
|
||||
reverse_path ->
|
||||
load_query_with_reverse_path(
|
||||
root_query,
|
||||
related_query,
|
||||
reverse_path,
|
||||
root_data_filter
|
||||
)
|
||||
end
|
||||
end)
|
||||
if Map.get(relationship, :no_fields?) do
|
||||
relationship.destination
|
||||
|> Ash.Query.new(related_query.api)
|
||||
else
|
||||
relationship.destination
|
||||
|> Ash.Query.new(related_query.api)
|
||||
|> Ash.Query.filter(^related_query.filter)
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_errors(%{errors: []} = item), do: {:ok, item}
|
||||
defp extract_errors(%{errors: errors}), do: {:error, errors}
|
||||
|
||||
defp true_load_query(relationship, query, data, path, request_path) do
|
||||
{source_field, path} =
|
||||
if relationship.type == :many_to_many do
|
||||
|
@ -1106,47 +1038,52 @@ defmodule Ash.Actions.Load do
|
|||
defp get_query(query, relationship, source_data, source_field) do
|
||||
{offset, limit} = offset_and_limit(query)
|
||||
|
||||
if lateral_join?(query, relationship, source_data) do
|
||||
{:ok, Ash.Query.unset(query, :load)}
|
||||
else
|
||||
query =
|
||||
if limit || offset do
|
||||
Ash.Query.unset(query, [:limit, :offset])
|
||||
else
|
||||
query
|
||||
end
|
||||
cond do
|
||||
lateral_join?(query, relationship, source_data) ->
|
||||
{:ok, Ash.Query.unset(query, :load)}
|
||||
|
||||
related_data =
|
||||
case source_data do
|
||||
%page{results: results} when page in [Ash.Page.Keyset, Ash.Page.Offset] ->
|
||||
results
|
||||
Map.get(relationship, :no_fields?) ->
|
||||
{:ok, query}
|
||||
|
||||
data ->
|
||||
true ->
|
||||
query =
|
||||
if limit || offset do
|
||||
Ash.Query.unset(query, [:limit, :offset])
|
||||
else
|
||||
query
|
||||
end
|
||||
|
||||
related_data =
|
||||
case source_data do
|
||||
%page{results: results} when page in [Ash.Page.Keyset, Ash.Page.Offset] ->
|
||||
results
|
||||
|
||||
data ->
|
||||
data
|
||||
end
|
||||
|
||||
ids =
|
||||
Enum.flat_map(related_data, fn data ->
|
||||
data
|
||||
end
|
||||
|> Map.get(source_field)
|
||||
|> List.wrap()
|
||||
end)
|
||||
|
||||
ids =
|
||||
Enum.flat_map(related_data, fn data ->
|
||||
data
|
||||
|> Map.get(source_field)
|
||||
|> List.wrap()
|
||||
end)
|
||||
filter_value =
|
||||
case ids do
|
||||
[id] ->
|
||||
id
|
||||
|
||||
filter_value =
|
||||
case ids do
|
||||
[id] ->
|
||||
id
|
||||
ids ->
|
||||
[in: ids]
|
||||
end
|
||||
|
||||
ids ->
|
||||
[in: ids]
|
||||
end
|
||||
new_query =
|
||||
query
|
||||
|> Ash.Query.filter(^[{relationship.destination_field, filter_value}])
|
||||
|> Ash.Query.unset(:load)
|
||||
|
||||
new_query =
|
||||
query
|
||||
|> Ash.Query.filter(^[{relationship.destination_field, filter_value}])
|
||||
|> Ash.Query.unset(:load)
|
||||
|
||||
{:ok, new_query}
|
||||
{:ok, new_query}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1238,30 +1175,4 @@ defmodule Ash.Actions.Load do
|
|||
is_nil(destination_rel.context) &&
|
||||
is_nil(rel.context)
|
||||
end
|
||||
|
||||
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
|
||||
List.wrap(request_filter) ++ List.wrap(values)
|
||||
end
|
||||
|
||||
defp put_nested_relationship(request_filter, [], values, _) do
|
||||
Keyword.update(request_filter, :or, values, &Kernel.++(&1, values))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -102,7 +102,8 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
changeset =
|
||||
if input in [nil, []] && opts[:on_missing] != :ignore do
|
||||
Ash.Changeset.force_change_attribute(changeset, relationship.source_field, nil)
|
||||
changeset
|
||||
|> maybe_force_change_attribute(relationship, :source_field, nil)
|
||||
|> Ash.Changeset.after_action(fn _changeset, result ->
|
||||
{:ok, Map.put(result, relationship.name, nil)}
|
||||
end)
|
||||
|
@ -173,8 +174,9 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
belongs_to_manage_found: %{relationship.name => %{index => input}}
|
||||
}
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.source_field,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:source_field,
|
||||
Map.get(input, relationship.destination_field)
|
||||
)
|
||||
|
||||
|
@ -216,8 +218,9 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
}
|
||||
}
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.source_field,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:source_field,
|
||||
Map.get(found, relationship.destination_field)
|
||||
)
|
||||
|
||||
|
@ -247,9 +250,10 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
_value ->
|
||||
if opts[:on_match] == :destroy do
|
||||
changeset =
|
||||
Ash.Changeset.force_change_attribute(
|
||||
maybe_force_change_attribute(
|
||||
changeset,
|
||||
relationship.source_field,
|
||||
relationship,
|
||||
:source_field,
|
||||
nil
|
||||
)
|
||||
|
||||
|
@ -287,6 +291,16 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
relationship.api || changeset.api
|
||||
end
|
||||
|
||||
defp maybe_force_change_attribute(changeset, %{no_fields?: true}, _, _), do: changeset
|
||||
|
||||
defp maybe_force_change_attribute(changeset, relationship, key, value) do
|
||||
Ash.Changeset.force_change_attribute(
|
||||
changeset,
|
||||
Map.get(relationship, key),
|
||||
value
|
||||
)
|
||||
end
|
||||
|
||||
defp validate_required_belongs_to({:error, error}), do: {:error, error}
|
||||
|
||||
defp validate_required_belongs_to({changeset, instructions}) do
|
||||
|
@ -413,8 +427,9 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
belongs_to_manage_created: %{relationship.name => %{index => created}}
|
||||
}
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.source_field,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:source_field,
|
||||
Map.get(created, relationship.destination_field)
|
||||
)
|
||||
|
||||
|
@ -859,12 +874,14 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
relationship.through
|
||||
|> Ash.Changeset.new()
|
||||
|> Ash.Changeset.for_create(create_or_update, join_input, actor: actor)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.source_field_on_join_table,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:source_field_on_join_table,
|
||||
Map.get(record, relationship.source_field)
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.destination_field_on_join_table,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:destination_field_on_join_table,
|
||||
Map.get(found, relationship.destination_field)
|
||||
)
|
||||
|> Ash.Changeset.set_context(join_relationship.context)
|
||||
|
@ -913,13 +930,13 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
found
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_update(create_or_update, input,
|
||||
relationships: opts[:relationships] || [],
|
||||
actor: actor
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.destination_field,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:destination_field,
|
||||
Map.get(record, relationship.source_field)
|
||||
)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|
@ -968,14 +985,14 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
else
|
||||
relationship.destination
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_create(action_name, input,
|
||||
require?: false,
|
||||
actor: actor,
|
||||
relationships: opts[:relationships]
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.destination_field,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:destination_field,
|
||||
Map.get(record, relationship.source_field)
|
||||
)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|
@ -1017,7 +1034,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
else
|
||||
relationship.destination
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_create(action_name, regular_params,
|
||||
require?: false,
|
||||
relationships: opts[:relationships],
|
||||
|
@ -1043,12 +1059,14 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
require?: false,
|
||||
actor: actor
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.source_field_on_join_table,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:source_field_on_join_table,
|
||||
Map.get(record, relationship.source_field)
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(
|
||||
relationship.destination_field_on_join_table,
|
||||
|> maybe_force_change_attribute(
|
||||
relationship,
|
||||
:destination_field_on_join_table,
|
||||
Map.get(created, relationship.destination_field)
|
||||
)
|
||||
|> Ash.Changeset.set_context(join_relationship.context)
|
||||
|
@ -1114,8 +1132,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
changeset.tenant,
|
||||
relationship,
|
||||
changeset
|
||||
relationship
|
||||
) do
|
||||
{:ok, notifications} ->
|
||||
{:ok, current_value, notifications, []}
|
||||
|
@ -1133,8 +1150,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
changeset.tenant,
|
||||
relationship,
|
||||
changeset
|
||||
relationship
|
||||
) do
|
||||
{:ok, notifications} ->
|
||||
{:ok, current_value, notifications, []}
|
||||
|
@ -1153,7 +1169,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
match
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_update(action_name, input,
|
||||
actor: actor,
|
||||
relationships: opts[:relationships] || []
|
||||
|
@ -1184,7 +1199,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
match
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_update(action_name, regular_params,
|
||||
actor: actor,
|
||||
relationships: opts[:relationships]
|
||||
|
@ -1327,32 +1341,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
end
|
||||
end
|
||||
|
||||
defp set_source_context(changeset, {relationship, original_changeset}) do
|
||||
case changeset.data.__metadata__[:manage_relationship_source] ||
|
||||
original_changeset.context[:manage_relationship_source] do
|
||||
nil ->
|
||||
Ash.Changeset.set_context(changeset, %{
|
||||
manage_relationship_source: [
|
||||
{relationship.source, relationship.name, original_changeset}
|
||||
]
|
||||
})
|
||||
|
||||
value ->
|
||||
Ash.Changeset.set_context(changeset, %{
|
||||
manage_relationship_source:
|
||||
value ++ [{relationship.source, relationship.name, original_changeset}]
|
||||
})
|
||||
end
|
||||
|> Ash.Changeset.after_action(fn changeset, record ->
|
||||
{:ok,
|
||||
Ash.Resource.Info.put_metadata(
|
||||
record,
|
||||
:manage_relationship_source,
|
||||
changeset.context[:manage_relationship_source]
|
||||
)}
|
||||
end)
|
||||
end
|
||||
|
||||
defp delete_unused(
|
||||
source_record,
|
||||
original_value,
|
||||
|
@ -1424,7 +1412,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
record
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_destroy(action_name, %{}, actor: actor)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|> Ash.Changeset.set_tenant(changeset.tenant)
|
||||
|
@ -1456,7 +1443,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
{:destroy, action_name} ->
|
||||
record
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_destroy(action_name, %{}, actor: actor)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|> Ash.Changeset.set_tenant(changeset.tenant)
|
||||
|
@ -1490,8 +1476,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
changeset.tenant,
|
||||
relationship,
|
||||
changeset
|
||||
relationship
|
||||
) do
|
||||
{:ok, notifications} ->
|
||||
{:cont, {:ok, current_value, notifications}}
|
||||
|
@ -1512,8 +1497,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
tenant,
|
||||
%{type: :many_to_many} = relationship,
|
||||
changeset
|
||||
%{type: :many_to_many} = relationship
|
||||
) do
|
||||
action_name =
|
||||
action_name || Ash.Resource.Info.primary_action(relationship.through, :destroy).name
|
||||
|
@ -1531,7 +1515,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
{:ok, result} ->
|
||||
result
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_destroy(action_name, %{}, actor: actor)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|> Ash.Changeset.set_tenant(tenant)
|
||||
|
@ -1561,8 +1544,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
tenant,
|
||||
%{type: type} = relationship,
|
||||
changeset
|
||||
%{type: type} = relationship
|
||||
)
|
||||
when type in [:has_many, :has_one] do
|
||||
action_name =
|
||||
|
@ -1570,12 +1552,11 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
|
||||
record
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_update(action_name, %{},
|
||||
relationships: opts[:relationships] || [],
|
||||
actor: actor
|
||||
)
|
||||
|> Ash.Changeset.force_change_attribute(relationship.destination_field, nil)
|
||||
|> maybe_force_change_attribute(relationship, :destination_field, nil)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|> Ash.Changeset.set_tenant(tenant)
|
||||
|> api.update(return_notifications?: true, actor: actor, authorize?: opts[:authorize?])
|
||||
|
@ -1596,8 +1577,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
_opts,
|
||||
_action_name,
|
||||
_tenant,
|
||||
%{type: :belongs_to},
|
||||
_changeset
|
||||
%{type: :belongs_to}
|
||||
) do
|
||||
{:ok, []}
|
||||
end
|
||||
|
@ -1610,8 +1590,7 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
tenant,
|
||||
%{type: :many_to_many} = relationship,
|
||||
changeset
|
||||
%{type: :many_to_many} = relationship
|
||||
) do
|
||||
action_name =
|
||||
action_name || Ash.Resource.Info.primary_action(relationship.through, :destroy).name
|
||||
|
@ -1629,7 +1608,6 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
{:ok, result} ->
|
||||
result
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_destroy(action_name, %{}, actor: actor)
|
||||
|> Ash.Changeset.set_context(relationship.context)
|
||||
|> Ash.Changeset.set_tenant(tenant)
|
||||
|
@ -1659,15 +1637,13 @@ defmodule Ash.Actions.ManagedRelationships do
|
|||
opts,
|
||||
action_name,
|
||||
tenant,
|
||||
relationship,
|
||||
changeset
|
||||
relationship
|
||||
) do
|
||||
action_name =
|
||||
action_name || Ash.Resource.Info.primary_action(relationship.destination, :update).name
|
||||
|
||||
record
|
||||
|> Ash.Changeset.new()
|
||||
|> set_source_context({relationship, changeset})
|
||||
|> Ash.Changeset.for_destroy(action_name, %{},
|
||||
relationships: opts[:relationships] || [],
|
||||
actor: actor
|
||||
|
|
|
@ -74,7 +74,7 @@ defmodule Ash.Changeset do
|
|||
if context == %{} do
|
||||
empty()
|
||||
else
|
||||
concat("context: ", to_doc(sanitize_context(context), opts))
|
||||
concat("context: ", to_doc(context, opts))
|
||||
end
|
||||
|
||||
tenant =
|
||||
|
@ -112,23 +112,6 @@ defmodule Ash.Changeset do
|
|||
)
|
||||
end
|
||||
|
||||
defp sanitize_context(%{manage_relationship_source: manage_relationship_source} = context) do
|
||||
sanitized_managed_relationship_source =
|
||||
manage_relationship_source
|
||||
|> Enum.reverse()
|
||||
|> Enum.map_join(" -> ", fn {resource, rel, _} ->
|
||||
"#{inspect(resource)}.#{rel}"
|
||||
end)
|
||||
|
||||
%{
|
||||
context
|
||||
| manage_relationship_source:
|
||||
"#manage_relationship_source<#{sanitized_managed_relationship_source}>"
|
||||
}
|
||||
end
|
||||
|
||||
defp sanitize_context(context), do: context
|
||||
|
||||
defp arguments(changeset, opts) do
|
||||
if changeset.action do
|
||||
if Enum.empty?(changeset.action.arguments) do
|
||||
|
|
|
@ -153,7 +153,8 @@ defmodule Ash.Engine.Request do
|
|||
nil
|
||||
|
||||
other ->
|
||||
raise "Got a weird thing #{inspect(other)}"
|
||||
raise ArgumentError,
|
||||
message: "Unexpected value passed to `Ash.Query.new/1`: #{inspect(other)}"
|
||||
end
|
||||
|
||||
data =
|
||||
|
|
|
@ -17,6 +17,7 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
:violation_message,
|
||||
:manual,
|
||||
:api,
|
||||
no_fields?: false,
|
||||
could_be_related_at_creation?: false,
|
||||
validate_destination_field?: true,
|
||||
cardinality: :many,
|
||||
|
@ -30,6 +31,7 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
writable?: boolean,
|
||||
read_action: atom,
|
||||
filter: Ash.Filter.t() | nil,
|
||||
no_fields?: boolean,
|
||||
name: atom,
|
||||
type: Ash.Type.t(),
|
||||
destination: Ash.Resource.t(),
|
||||
|
@ -48,7 +50,8 @@ defmodule Ash.Resource.Relationships.HasMany do
|
|||
|
||||
@opt_schema Ash.OptionsHelpers.merge_schemas(
|
||||
[
|
||||
manual()
|
||||
manual(),
|
||||
no_fields()
|
||||
],
|
||||
@global_opts,
|
||||
"Relationship Options"
|
||||
|
|
|
@ -19,6 +19,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
:not_found_message,
|
||||
:violation_message,
|
||||
:manual,
|
||||
no_fields?: false,
|
||||
could_be_related_at_creation?: false,
|
||||
validate_destination_field?: true,
|
||||
cardinality: :one,
|
||||
|
@ -33,6 +34,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
writable?: boolean,
|
||||
name: atom,
|
||||
read_action: atom,
|
||||
no_fields?: boolean,
|
||||
type: Ash.Type.t(),
|
||||
filter: Ash.Filter.t() | nil,
|
||||
destination: Ash.Resource.t(),
|
||||
|
@ -51,7 +53,7 @@ defmodule Ash.Resource.Relationships.HasOne do
|
|||
|> OptionsHelpers.set_default!(:source_field, :id)
|
||||
|
||||
@opt_schema Ash.OptionsHelpers.merge_schemas(
|
||||
[manual()] ++
|
||||
[manual(), no_fields()] ++
|
||||
[
|
||||
required?: [
|
||||
type: :boolean,
|
||||
|
|
|
@ -109,6 +109,32 @@ defmodule Ash.Resource.Relationships.SharedOptions do
|
|||
@shared_options
|
||||
end
|
||||
|
||||
def no_fields do
|
||||
{:no_fields?,
|
||||
[
|
||||
type: :boolean,
|
||||
doc: """
|
||||
If true, all existing entities are considered related, i.e this relationship is not based on any fields, and `source_field` and
|
||||
`destination_field` are ignored.
|
||||
|
||||
This can be very useful when combined with multitenancy. Specifically, if you have a tenant resource like `Organization`,
|
||||
you can use `no_fields?` to do things like `has_many :employees, Employee, no_fields?: true`, which lets you avoid having an
|
||||
unnecessary `organization_id` field on `Employee`. The same works in reverse: `has_one :organization, Organization, no_fields?: 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.
|
||||
|
||||
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.
|
||||
"""
|
||||
]}
|
||||
end
|
||||
|
||||
def manual do
|
||||
{:manual,
|
||||
type: {:ash_behaviour, Ash.Resource.ManualRelationship},
|
||||
|
|
|
@ -16,7 +16,9 @@ defmodule Ash.Resource.Transformers.ValidateRelationshipAttributes do
|
|||
|
||||
resource
|
||||
|> Ash.Resource.Info.relationships()
|
||||
|> Enum.reject(&Map.get(&1, :manual))
|
||||
|> Enum.reject(fn relationship ->
|
||||
Map.get(relationship, :manual) || Map.get(relationship, :no_fields?)
|
||||
end)
|
||||
|> Enum.filter(& &1.validate_destination_field?)
|
||||
|> Enum.each(&validate_relationship(&1, attribute_names, resource))
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ defmodule Ash.Test.CalculationTest do
|
|||
defmodule BestFriendsName do
|
||||
use Ash.Calculation
|
||||
|
||||
def load(_query, opts, _) do
|
||||
def load(_query, _opts, _) do
|
||||
[best_friend: :full_name]
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue