mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
WIP
This commit is contained in:
parent
8170efca8a
commit
24d1bd03c4
14 changed files with 91 additions and 134 deletions
|
@ -170,3 +170,4 @@ end
|
||||||
- check if preparations have been done on a superset filter of a request and, if so, use it
|
- check if preparations have been done on a superset filter of a request and, if so, use it
|
||||||
- without transactions, we can't ensure that all changes are rolled back in the case that relationship updates are included. Don't think there is really anything to do about that, but something worth considering.
|
- without transactions, we can't ensure that all changes are rolled back in the case that relationship updates are included. Don't think there is really anything to do about that, but something worth considering.
|
||||||
- perhaps have auth steps express which fields need to be present, so we can avoid loading things unnecessarily
|
- perhaps have auth steps express which fields need to be present, so we can avoid loading things unnecessarily
|
||||||
|
- lift `or` filters over the same field equaling a value into a single `in` filter, for performance (potentially)
|
||||||
|
|
|
@ -3,21 +3,7 @@ defmodule Ash.Actions.Create do
|
||||||
alias Ash.Actions.{Attributes, Relationships, SideLoad}
|
alias Ash.Actions.{Attributes, Relationships, SideLoad}
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@spec run(Ash.api(), Ash.resource(), Ash.action(), Ash.params()) ::
|
|
||||||
{:ok, Ash.record()} | {:error, Ecto.Changeset.t()} | {:error, Ash.error()}
|
|
||||||
def run(api, resource, action, params) do
|
def run(api, resource, action, params) do
|
||||||
transaction_result =
|
|
||||||
Ash.DataLayer.transact(resource, fn ->
|
|
||||||
do_run(api, resource, action, params)
|
|
||||||
end)
|
|
||||||
|
|
||||||
case transaction_result do
|
|
||||||
{:ok, value} -> value
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_run(api, resource, action, params) do
|
|
||||||
attributes = Keyword.get(params, :attributes, %{})
|
attributes = Keyword.get(params, :attributes, %{})
|
||||||
side_loads = Keyword.get(params, :side_load, [])
|
side_loads = Keyword.get(params, :side_load, [])
|
||||||
side_load_filter = Keyword.get(params, :side_load_filter)
|
side_load_filter = Keyword.get(params, :side_load_filter)
|
||||||
|
|
|
@ -4,18 +4,6 @@ defmodule Ash.Actions.Destroy do
|
||||||
@spec run(Ash.api(), Ash.record(), Ash.action(), Ash.params()) ::
|
@spec run(Ash.api(), Ash.record(), Ash.action(), Ash.params()) ::
|
||||||
{:ok, Ash.record()} | {:error, Ecto.Changeset.t()} | {:error, Ash.error()}
|
{:ok, Ash.record()} | {:error, Ecto.Changeset.t()} | {:error, Ash.error()}
|
||||||
def run(api, %resource{} = record, action, params) do
|
def run(api, %resource{} = record, action, params) do
|
||||||
transaction_result =
|
|
||||||
Ash.DataLayer.transact(resource, fn ->
|
|
||||||
do_authorized(api, params, action, record)
|
|
||||||
end)
|
|
||||||
|
|
||||||
case transaction_result do
|
|
||||||
{:ok, value} -> value
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_authorized(api, params, action, %resource{} = record) do
|
|
||||||
auth_request =
|
auth_request =
|
||||||
Ash.Engine.Request.new(
|
Ash.Engine.Request.new(
|
||||||
resource: resource,
|
resource: resource,
|
||||||
|
@ -24,7 +12,7 @@ defmodule Ash.Actions.Destroy do
|
||||||
strict_access: false,
|
strict_access: false,
|
||||||
path: [:data],
|
path: [:data],
|
||||||
data:
|
data:
|
||||||
Ash.Engine.Request.UnresolvedField.data([], fn _request, _ ->
|
Ash.Engine.Request.UnresolvedField.data([], fn _ ->
|
||||||
case Ash.data_layer(resource).destroy(record) do
|
case Ash.data_layer(resource).destroy(record) do
|
||||||
:ok -> {:ok, record}
|
:ok -> {:ok, record}
|
||||||
{:error, error} -> {:error, error}
|
{:error, error} -> {:error, error}
|
||||||
|
@ -34,15 +22,21 @@ defmodule Ash.Actions.Destroy do
|
||||||
resolve_when_fetch_only?: true
|
resolve_when_fetch_only?: true
|
||||||
)
|
)
|
||||||
|
|
||||||
if params[:authorization] do
|
result =
|
||||||
Engine.run(
|
if params[:authorization] do
|
||||||
[auth_request],
|
Engine.run(
|
||||||
api,
|
[auth_request],
|
||||||
user: params[:authorization][:user],
|
api,
|
||||||
log_final_report?: params[:authorization][:log_final_report?]
|
user: params[:authorization][:user],
|
||||||
)
|
log_final_report?: params[:authorization][:log_final_report?]
|
||||||
else
|
)
|
||||||
Engine.run([auth_request], api, fetch_only?: true)
|
else
|
||||||
|
Engine.run([auth_request], api, fetch_only?: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
case result do
|
||||||
|
%{errors: errors} when errors == %{} -> :ok
|
||||||
|
%{errors: errors} -> {:error, errors}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,18 +5,6 @@ defmodule Ash.Actions.Read do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def run(api, resource, action, params) do
|
def run(api, resource, action, params) do
|
||||||
transaction_result =
|
|
||||||
Ash.DataLayer.transact(resource, fn ->
|
|
||||||
do_run(api, resource, action, params)
|
|
||||||
end)
|
|
||||||
|
|
||||||
case transaction_result do
|
|
||||||
{:ok, value} -> value
|
|
||||||
{:error, error} -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_run(api, resource, action, params) do
|
|
||||||
filter = Keyword.get(params, :filter, [])
|
filter = Keyword.get(params, :filter, [])
|
||||||
sort = Keyword.get(params, :sort, [])
|
sort = Keyword.get(params, :sort, [])
|
||||||
side_loads = Keyword.get(params, :side_load, [])
|
side_loads = Keyword.get(params, :side_load, [])
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
# defmodule Ash.Actions.Relationships.Create do
|
|
||||||
# alias Ash.Actions.Relationships.Change
|
|
||||||
|
|
||||||
# def changeset(changeset, api, relationships) do
|
|
||||||
# relationship_changes = relationship_changes(relationships)
|
|
||||||
|
|
||||||
# changeset
|
|
||||||
# end
|
|
||||||
|
|
||||||
# defp relationship_changes(relationships) do
|
|
||||||
# Enum.into(relationships, %{}, fn {key, value} ->
|
|
||||||
# {key, Change.from(value, :create)}
|
|
||||||
# end)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # def changeset(changeset, api, relationships) do
|
|
||||||
# # if relationships == %{} do
|
|
||||||
# # changeset
|
|
||||||
# # else
|
|
||||||
# # dependencies = Map.get(changeset, :__changes_depend_on__, [])
|
|
||||||
|
|
||||||
# # Ash.Engine.Request.UnresolvedField.field(dependencies, fn data ->
|
|
||||||
# # new_changeset =
|
|
||||||
# # data
|
|
||||||
# # |> Map.get(:relationships, %{})
|
|
||||||
# # |> Enum.reduce(changeset, fn {relationship, relationship_data}, changeset ->
|
|
||||||
# # relationship = Ash.relationship(changeset.data.__struct__, relationship)
|
|
||||||
|
|
||||||
# # relationship_data =
|
|
||||||
# # relationship_data
|
|
||||||
# # |> Enum.into(%{}, fn {key, value} ->
|
|
||||||
# # {key, value.data}
|
|
||||||
# # end)
|
|
||||||
# # |> Map.put_new(:current, [])
|
|
||||||
|
|
||||||
# # add_relationship_to_changeset(changeset, api, relationship, relationship_data)
|
|
||||||
# # end)
|
|
||||||
|
|
||||||
# # {:ok, new_changeset}
|
|
||||||
# # end)
|
|
||||||
# # end
|
|
||||||
# # end
|
|
||||||
# end
|
|
|
@ -1,5 +0,0 @@
|
||||||
# defmodule Ash.Actions.Relationships do
|
|
||||||
# defmodule Change do
|
|
||||||
# defstruct [:add, :remove, :current]
|
|
||||||
# end
|
|
||||||
# end
|
|
|
@ -91,16 +91,18 @@ defmodule Ash.Actions.SideLoad do
|
||||||
end)
|
end)
|
||||||
|> Enum.reduce(data, fn {key, %{data: value}}, data ->
|
|> Enum.reduce(data, fn {key, %{data: value}}, data ->
|
||||||
last_relationship = last_relationship!(resource, key)
|
last_relationship = last_relationship!(resource, key)
|
||||||
|
lead_path = :lists.droplast(key)
|
||||||
|
|
||||||
case last_relationship do
|
case last_relationship do
|
||||||
%{type: :many_to_many, name: name} ->
|
%{type: :many_to_many, name: name} ->
|
||||||
# TODO: If we sort the relationships as we do them (doing the join assoc first)
|
# TODO: If we sort the relationships as we do them (doing the join assoc first)
|
||||||
# then we can just use those linked assocs (maybe)
|
# then we can just use those linked assocs (maybe)
|
||||||
join_association = String.to_existing_atom(to_string(name) <> "_join_assoc")
|
join_association = String.to_existing_atom(to_string(name) <> "_join_assoc")
|
||||||
join_path = :lists.droplast(key) ++ [join_association]
|
|
||||||
|
join_path = lead_path ++ [join_association]
|
||||||
join_data = Map.get(includes, join_path, [])
|
join_data = Map.get(includes, join_path, [])
|
||||||
|
|
||||||
map_or_update(data, fn record ->
|
map_or_update(data, lead_path, fn record ->
|
||||||
source_value = Map.get(record, last_relationship.source_field)
|
source_value = Map.get(record, last_relationship.source_field)
|
||||||
|
|
||||||
join_values =
|
join_values =
|
||||||
|
@ -125,7 +127,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
%{cardinality: :many} ->
|
%{cardinality: :many} ->
|
||||||
values = Enum.group_by(value, &Map.get(&1, last_relationship.destination_field))
|
values = Enum.group_by(value, &Map.get(&1, last_relationship.destination_field))
|
||||||
|
|
||||||
map_or_update(data, fn record ->
|
map_or_update(data, lead_path, fn record ->
|
||||||
source_key = Map.get(record, last_relationship.source_field)
|
source_key = Map.get(record, last_relationship.source_field)
|
||||||
related_records = Map.get(values, source_key, [])
|
related_records = Map.get(values, source_key, [])
|
||||||
Map.put(record, last_relationship.name, related_records)
|
Map.put(record, last_relationship.name, related_records)
|
||||||
|
@ -137,7 +139,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
{Map.get(item, last_relationship.destination_field), item}
|
{Map.get(item, last_relationship.destination_field), item}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
map_or_update(data, fn record ->
|
map_or_update(data, lead_path, fn record ->
|
||||||
source_key = Map.get(record, last_relationship.source_field)
|
source_key = Map.get(record, last_relationship.source_field)
|
||||||
related_record = Map.get(values, source_key)
|
related_record = Map.get(values, source_key)
|
||||||
Map.put(record, last_relationship.name, related_record)
|
Map.put(record, last_relationship.name, related_record)
|
||||||
|
@ -156,12 +158,18 @@ defmodule Ash.Actions.SideLoad do
|
||||||
data
|
data
|
||||||
end
|
end
|
||||||
|
|
||||||
defp map_or_update(record, func) when not is_list(record), do: func.(record)
|
defp map_or_update(record, [], func) when not is_list(record), do: func.(record)
|
||||||
|
|
||||||
defp map_or_update(records, func) do
|
defp map_or_update(records, [], func) do
|
||||||
Enum.map(records, func)
|
Enum.map(records, func)
|
||||||
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
|
defp last_relationship!(resource, [last]) do
|
||||||
Ash.relationship(resource, last) || raise "Assumption Failed"
|
Ash.relationship(resource, last) || raise "Assumption Failed"
|
||||||
end
|
end
|
||||||
|
@ -177,7 +185,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
{:rel, Ash.relationship(resource, key)},
|
{:rel, Ash.relationship(resource, key)},
|
||||||
nested_path <- path ++ [relationship],
|
nested_path <- path ++ [relationship],
|
||||||
{:ok, requests} <-
|
{:ok, requests} <-
|
||||||
requests(api, relationship.destination, further, filters, nested_path) do
|
requests(api, relationship.destination, further, filters, root_filter, nested_path) do
|
||||||
default_read =
|
default_read =
|
||||||
Ash.primary_action(relationship.destination, :read) ||
|
Ash.primary_action(relationship.destination, :read) ||
|
||||||
raise "Must set default read for #{inspect(resource)}"
|
raise "Must set default read for #{inspect(resource)}"
|
||||||
|
@ -211,7 +219,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
path: [:include, Enum.map(nested_path, &Map.get(&1, :name))],
|
path: [:include, Enum.map(nested_path, &Map.get(&1, :name))],
|
||||||
resolve_when_fetch_only?: true,
|
resolve_when_fetch_only?: true,
|
||||||
filter:
|
filter:
|
||||||
side_load_filter2(
|
side_load_filter(
|
||||||
relationship,
|
relationship,
|
||||||
Map.get(filters || %{}, source, []),
|
Map.get(filters || %{}, source, []),
|
||||||
nested_path,
|
nested_path,
|
||||||
|
@ -237,6 +245,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
# or for doing many to many joins, but can be slower.
|
# or for doing many to many joins, but can be slower.
|
||||||
# If the relationship is already loaded, we should consider doing an in-memory filtering
|
# If the relationship is already loaded, we should consider doing an in-memory filtering
|
||||||
# Right now, we just use the original query
|
# Right now, we just use the original query
|
||||||
|
|
||||||
with {:ok, filter} <-
|
with {:ok, filter} <-
|
||||||
true_side_load_filter(
|
true_side_load_filter(
|
||||||
relationship,
|
relationship,
|
||||||
|
@ -275,7 +284,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
strict_access?: root_filter not in [:create, :update],
|
strict_access?: root_filter not in [:create, :update],
|
||||||
resolve_when_fetch_only?: true,
|
resolve_when_fetch_only?: true,
|
||||||
filter:
|
filter:
|
||||||
side_load_filter2(
|
side_load_filter(
|
||||||
Ash.relationship(resource, join_relationship.name),
|
Ash.relationship(resource, join_relationship.name),
|
||||||
[],
|
[],
|
||||||
nested_path,
|
nested_path,
|
||||||
|
@ -325,7 +334,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp side_load_filter2(
|
defp side_load_filter(
|
||||||
%{reverse_relationship: nil, type: :many_to_many} = relationship,
|
%{reverse_relationship: nil, type: :many_to_many} = relationship,
|
||||||
_request_filter,
|
_request_filter,
|
||||||
_prior_path,
|
_prior_path,
|
||||||
|
@ -338,7 +347,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp side_load_filter2(
|
defp side_load_filter(
|
||||||
relationship,
|
relationship,
|
||||||
request_filter,
|
request_filter,
|
||||||
prior_path,
|
prior_path,
|
||||||
|
@ -372,13 +381,13 @@ defmodule Ash.Actions.SideLoad do
|
||||||
{:ok, reverse_path} ->
|
{:ok, reverse_path} ->
|
||||||
Ash.Filter.parse(
|
Ash.Filter.parse(
|
||||||
relationship.destination,
|
relationship.destination,
|
||||||
put_nested_relationship(request_filter, reverse_path, root_filter)
|
put_nested_relationship(request_filter, reverse_path, root_filter, false)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp side_load_filter2(
|
defp side_load_filter(
|
||||||
relationship,
|
relationship,
|
||||||
request_filter,
|
request_filter,
|
||||||
prior_path,
|
prior_path,
|
||||||
|
@ -416,7 +425,7 @@ defmodule Ash.Actions.SideLoad do
|
||||||
{:ok, reverse_path} ->
|
{:ok, reverse_path} ->
|
||||||
Ash.Filter.parse(
|
Ash.Filter.parse(
|
||||||
relationship.destination,
|
relationship.destination,
|
||||||
put_nested_relationship(request_filter, reverse_path, root_filter)
|
put_nested_relationship(request_filter, reverse_path, root_filter, false)
|
||||||
)
|
)
|
||||||
|
|
||||||
:error ->
|
:error ->
|
||||||
|
@ -450,17 +459,22 @@ defmodule Ash.Actions.SideLoad do
|
||||||
Map.get(data, :data)
|
Map.get(data, :data)
|
||||||
|
|
||||||
path ->
|
path ->
|
||||||
Map.get(data, [:include, Enum.reverse(path)])
|
path_names = path |> Enum.reverse() |> Enum.map(& &1.name)
|
||||||
|
|
||||||
|
data
|
||||||
|
|> Map.get(:include, %{})
|
||||||
|
|> Map.get(path_names, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
values = get_fields(source_data.data, pkey)
|
related_data = Map.get(source_data || %{}, :data, [])
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
reverse_relationship ->
|
reverse_relationship ->
|
||||||
|
values = get_fields(related_data, pkey)
|
||||||
{:ok, put_nested_relationship(filter, [reverse_relationship], values)}
|
{:ok, put_nested_relationship(filter, [reverse_relationship], values)}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
ids = Enum.map(source_data.data, &Map.get(&1, relationship.source_field))
|
ids = Enum.map(related_data, &Map.get(&1, relationship.source_field))
|
||||||
|
|
||||||
filter_value =
|
filter_value =
|
||||||
case ids do
|
case ids do
|
||||||
|
@ -588,30 +602,33 @@ defmodule Ash.Actions.SideLoad do
|
||||||
# |> get_field(name, rest)
|
# |> get_field(name, rest)
|
||||||
# end
|
# end
|
||||||
|
|
||||||
defp put_nested_relationship(_, _, []), do: [__impossible__: true]
|
defp put_nested_relationship(request_filter, path, value, records? \\ true)
|
||||||
defp put_nested_relationship(_, _, nil), do: [__impossible__: 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) when not is_list(value) do
|
defp put_nested_relationship(request_filter, path, value, records?) when not is_list(value) do
|
||||||
put_nested_relationship(request_filter, path, [value])
|
put_nested_relationship(request_filter, path, [value], records?)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_nested_relationship(request_filter, [rel | rest], values) do
|
defp put_nested_relationship(request_filter, [rel | rest], values, records?) do
|
||||||
[
|
[
|
||||||
{rel, put_nested_relationship(request_filter, rest, values)}
|
{rel, put_nested_relationship(request_filter, rest, values, records?)}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_nested_relationship(request_filter, [], [[{field, _}] | _] = keys) do
|
defp put_nested_relationship(request_filter, [], [[{field, _}] | _] = keys, _) do
|
||||||
add_relationship_id_filter(request_filter, field, Enum.map(keys, &elem(&1, 1)))
|
add_relationship_id_filter(request_filter, field, Enum.map(keys, &elem(&1, 1)))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_nested_relationship(request_filter, [], [values]) do
|
defp put_nested_relationship(request_filter, [], [values], _) do
|
||||||
Enum.reduce(values, request_filter, fn {field, value}, filter ->
|
Enum.reduce(values, request_filter, fn {field, value}, filter ->
|
||||||
add_relationship_id_filter(filter, field, [value])
|
add_relationship_id_filter(filter, field, [value])
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_nested_relationship(request_filter, [], values) do
|
defp put_nested_relationship(request_filter, [], values, _) do
|
||||||
Keyword.update(request_filter, :or, values, &Kernel.++(&1, values))
|
Keyword.update(request_filter, :or, values, &Kernel.++(&1, values))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -467,6 +467,7 @@ defmodule Ash.Api.Interface do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp unwrap_or_raise!(:ok), do: :ok
|
||||||
defp unwrap_or_raise!({:ok, result}), do: result
|
defp unwrap_or_raise!({:ok, result}), do: result
|
||||||
|
|
||||||
defp unwrap_or_raise!({:error, error}) when is_bitstring(error) do
|
defp unwrap_or_raise!({:error, error}) when is_bitstring(error) do
|
||||||
|
|
|
@ -69,6 +69,9 @@ defmodule Ash.DataLayer.Ets do
|
||||||
{:ok, %{query | sort: sort}}
|
{:ok, %{query | sort: sort}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def run_query(%Query{filter: %Ash.Filter{impossible?: true}}, _), do: {:ok, []}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def run_query(
|
def run_query(
|
||||||
%Query{resource: resource, filter: filter, offset: offset, limit: limit, sort: sort},
|
%Query{resource: resource, filter: filter, offset: offset, limit: limit, sort: sort},
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Ash.Filter do
|
||||||
requests: [],
|
requests: [],
|
||||||
path: [],
|
path: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
impossible: false
|
impossible?: false
|
||||||
]
|
]
|
||||||
|
|
||||||
alias Ash.Engine.Request
|
alias Ash.Engine.Request
|
||||||
|
@ -23,7 +23,7 @@ defmodule Ash.Filter do
|
||||||
attributes: Keyword.t(),
|
attributes: Keyword.t(),
|
||||||
relationships: Map.t(),
|
relationships: Map.t(),
|
||||||
path: list(atom),
|
path: list(atom),
|
||||||
impossible: boolean,
|
impossible?: boolean,
|
||||||
errors: list(String.t()),
|
errors: list(String.t()),
|
||||||
requests: list(Ash.Engine.Request.t())
|
requests: list(Ash.Engine.Request.t())
|
||||||
}
|
}
|
||||||
|
@ -258,7 +258,7 @@ defmodule Ash.Filter do
|
||||||
# TODO: We should probably include some kind of filter that *makes* it immediately impossible
|
# TODO: We should probably include some kind of filter that *makes* it immediately impossible
|
||||||
# that way, if the data layer doesn't check impossibility they will run the simpler query,
|
# that way, if the data layer doesn't check impossibility they will run the simpler query,
|
||||||
# like for each pkey field say `[field: [in: []]]`
|
# like for each pkey field say `[field: [in: []]]`
|
||||||
%{filter | impossible: true}
|
%{filter | impossible?: true}
|
||||||
else
|
else
|
||||||
filter
|
filter
|
||||||
end
|
end
|
||||||
|
@ -293,14 +293,14 @@ defmodule Ash.Filter do
|
||||||
|
|
||||||
defp lift_impossibility(filter) do
|
defp lift_impossibility(filter) do
|
||||||
with_related_impossibility =
|
with_related_impossibility =
|
||||||
if Enum.any?(filter.relationships || %{}, fn {_, val} -> Map.get(val, :impossible) end) do
|
if Enum.any?(filter.relationships || %{}, fn {_, val} -> Map.get(val, :impossible?) end) do
|
||||||
Map.put(filter, :impossible, true)
|
Map.put(filter, :impossible?, true)
|
||||||
else
|
else
|
||||||
filter
|
filter
|
||||||
end
|
end
|
||||||
|
|
||||||
Map.update!(with_related_impossibility, :ors, fn ors ->
|
Map.update!(with_related_impossibility, :ors, fn ors ->
|
||||||
Enum.reject(ors, &Map.get(&1, :impossible))
|
Enum.reject(ors, &Map.get(&1, :impossible?))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -407,6 +407,9 @@ defmodule Ash.Filter do
|
||||||
Enum.reduce(filter_statement, filter, fn
|
Enum.reduce(filter_statement, filter, fn
|
||||||
{key, value}, filter ->
|
{key, value}, filter ->
|
||||||
cond do
|
cond do
|
||||||
|
key == :__impossible__ && value == true ->
|
||||||
|
%{filter | impossible?: true}
|
||||||
|
|
||||||
key in [:or, :and, :not] ->
|
key in [:or, :and, :not] ->
|
||||||
new_filter = add_expression_level_boolean_filter(filter, resource, key, value)
|
new_filter = add_expression_level_boolean_filter(filter, resource, key, value)
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ defimpl Inspect, for: Ash.Filter do
|
||||||
ors: ors,
|
ors: ors,
|
||||||
relationships: relationships,
|
relationships: relationships,
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
impossible: impossible
|
impossible?: impossible
|
||||||
},
|
},
|
||||||
opts
|
opts
|
||||||
)
|
)
|
||||||
|
@ -89,7 +89,7 @@ defimpl Inspect, for: Ash.Filter do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def inspect(%{impossible: impossible} = filter, opts) do
|
def inspect(%{impossible?: impossible} = filter, opts) do
|
||||||
rels =
|
rels =
|
||||||
filter
|
filter
|
||||||
|> Map.get(:relationships)
|
|> Map.get(:relationships)
|
||||||
|
|
|
@ -243,7 +243,7 @@ defmodule Ash.Resource.Relationships do
|
||||||
has_many_name,
|
has_many_name,
|
||||||
unquote(config)[:through],
|
unquote(config)[:through],
|
||||||
destination_field: unquote(config)[:source_field_on_join_table],
|
destination_field: unquote(config)[:source_field_on_join_table],
|
||||||
source_field: unquote(config)[:source_field]
|
source_field: unquote(config)[:source_field] || :id
|
||||||
)
|
)
|
||||||
|
|
||||||
with {:many_to_many, {:ok, many_to_many}} <- {:many_to_many, many_to_many},
|
with {:many_to_many, {:ok, many_to_many}} <- {:many_to_many, many_to_many},
|
||||||
|
|
|
@ -82,7 +82,7 @@ defmodule Ash.Test.Actions.DestroyTest do
|
||||||
test "allows destroying a record" do
|
test "allows destroying a record" do
|
||||||
post = Api.create!(Post, attributes: %{title: "foo", contents: "bar"})
|
post = Api.create!(Post, attributes: %{title: "foo", contents: "bar"})
|
||||||
|
|
||||||
assert Api.destroy!(post) == post
|
assert Api.destroy!(post) == :ok
|
||||||
|
|
||||||
refute Api.get!(Post, post.id)
|
refute Api.get!(Post, post.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,7 @@ defmodule Ash.Test.Resource.Relationships.ManyToManyTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "representation" do
|
describe "representation" do
|
||||||
test "it creates a relationship" do
|
test "it creates a relationship and a join relationship" do
|
||||||
defposts do
|
defposts do
|
||||||
relationships do
|
relationships do
|
||||||
many_to_many :foobars, Foobar, through: SomeResource
|
many_to_many :foobars, Foobar, through: SomeResource
|
||||||
|
@ -20,16 +20,28 @@ defmodule Ash.Test.Resource.Relationships.ManyToManyTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
|
%Ash.Resource.Relationships.HasMany{
|
||||||
|
cardinality: :many,
|
||||||
|
destination: SomeResource,
|
||||||
|
destination_field: :posts_id,
|
||||||
|
name: :foobars_join_assoc,
|
||||||
|
source: Ash.Test.Resource.Relationships.ManyToManyTest.Post,
|
||||||
|
source_field: :id,
|
||||||
|
type: :has_many,
|
||||||
|
write_rules: []
|
||||||
|
},
|
||||||
%Ash.Resource.Relationships.ManyToMany{
|
%Ash.Resource.Relationships.ManyToMany{
|
||||||
cardinality: :many,
|
cardinality: :many,
|
||||||
destination: Foobar,
|
destination: Foobar,
|
||||||
destination_field: :id,
|
destination_field: :id,
|
||||||
destination_field_on_join_table: :foobars_id,
|
destination_field_on_join_table: :foobars_id,
|
||||||
name: :foobars,
|
name: :foobars,
|
||||||
|
source: Ash.Test.Resource.Relationships.ManyToManyTest.Post,
|
||||||
source_field: :id,
|
source_field: :id,
|
||||||
source_field_on_join_table: :posts_id,
|
source_field_on_join_table: :posts_id,
|
||||||
through: SomeResource,
|
through: SomeResource,
|
||||||
type: :many_to_many
|
type: :many_to_many,
|
||||||
|
write_rules: []
|
||||||
}
|
}
|
||||||
] = Ash.relationships(Post)
|
] = Ash.relationships(Post)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue