mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
improvement: change nested field filtering to be done with at_path
This commit is contained in:
parent
9efe456e16
commit
fc3cbc4e2c
2 changed files with 80 additions and 75 deletions
|
@ -3579,27 +3579,43 @@ defmodule Ash.Filter do
|
|||
end
|
||||
end
|
||||
|
||||
defp parse_predicates(value, field, context, inner_expr \\ nil)
|
||||
|
||||
defp parse_predicates(value, field, context, inner_expr)
|
||||
defp parse_predicates(value, field, context)
|
||||
when not is_list(value) and not is_map(value) do
|
||||
parse_predicates([eq: value], field, context, inner_expr)
|
||||
parse_predicates([eq: value], field, context)
|
||||
end
|
||||
|
||||
defp parse_predicates(value, field, context, inner_expr) when value == %{} do
|
||||
parse_predicates([eq: true], field, context, inner_expr)
|
||||
defp parse_predicates(value, field, context) when value == %{} do
|
||||
parse_predicates([eq: true], field, context)
|
||||
end
|
||||
|
||||
defp parse_predicates(%struct{} = value, field, context, inner_expr)
|
||||
defp parse_predicates(%struct{} = value, field, context)
|
||||
when struct not in [Not, BooleanExpression, Ref, Call] do
|
||||
parse_predicates([eq: value], field, context, inner_expr)
|
||||
parse_predicates([eq: value], field, context)
|
||||
end
|
||||
|
||||
defp parse_predicates(values, attr, context, inner_expr) do
|
||||
defp parse_predicates(values, attr, context) do
|
||||
if is_struct(values) && Map.has_key?(values, :__predicate__) do
|
||||
parse_predicates([eq: values], attr, context, inner_expr)
|
||||
parse_predicates([eq: values], attr, context)
|
||||
else
|
||||
if is_map(values) || Keyword.keyword?(values) do
|
||||
at_path =
|
||||
if is_map(values) do
|
||||
Map.get(values, :at_path) || Map.get(values, "at_path")
|
||||
else
|
||||
Keyword.get(values, :at_path)
|
||||
end
|
||||
|
||||
{values, at_path} =
|
||||
if is_list(at_path) do
|
||||
if is_map(values) do
|
||||
{Map.drop(values, [:at_path, "at_path"]), at_path}
|
||||
else
|
||||
{Keyword.delete(values, :at_path), at_path}
|
||||
end
|
||||
else
|
||||
{values, nil}
|
||||
end
|
||||
|
||||
Enum.reduce_while(values, {:ok, true}, fn
|
||||
{key, value}, {:ok, expression} when key in [:not, "not"] ->
|
||||
case parse_predicates(List.wrap(value), attr, context) do
|
||||
|
@ -3615,73 +3631,62 @@ defmodule Ash.Filter do
|
|||
end
|
||||
|
||||
{key, value}, {:ok, expression} ->
|
||||
if !match?({:array, _}, attr.type) &&
|
||||
Ash.Type.embedded_type?(attr.type) && Ash.Resource.Info.attribute(attr.type, key) do
|
||||
get_path = %Call{
|
||||
name: :get_path,
|
||||
args: [
|
||||
%Ref{
|
||||
attribute: attr,
|
||||
relationship_path: context[:relationship_path] || [],
|
||||
resource: context.resource,
|
||||
input?: true
|
||||
},
|
||||
[key]
|
||||
]
|
||||
}
|
||||
case get_operator(key) do
|
||||
nil ->
|
||||
error = NoSuchFilterPredicate.exception(key: key, resource: context.resource)
|
||||
{:halt, {:error, error}}
|
||||
|
||||
{:cont,
|
||||
parse_predicates(
|
||||
value,
|
||||
attr,
|
||||
context,
|
||||
get_path
|
||||
)}
|
||||
else
|
||||
case get_operator(key) do
|
||||
nil ->
|
||||
error = NoSuchFilterPredicate.exception(key: key, resource: context.resource)
|
||||
{:halt, {:error, error}}
|
||||
|
||||
operator_module ->
|
||||
left =
|
||||
inner_expr ||
|
||||
%Ref{
|
||||
attribute: attr,
|
||||
relationship_path: context[:relationship_path] || [],
|
||||
resource: context.resource,
|
||||
input?: true
|
||||
}
|
||||
|
||||
with {:ok, [left, right]} <-
|
||||
hydrate_refs([left, value], context),
|
||||
refs <- list_refs([left, right]),
|
||||
:ok <-
|
||||
validate_refs(
|
||||
refs,
|
||||
context.root_resource,
|
||||
{attr, value}
|
||||
),
|
||||
{:ok, operator} <- Operator.new(operator_module, left, right) do
|
||||
if is_boolean(operator) do
|
||||
{:cont, {:ok, operator}}
|
||||
else
|
||||
if is_nil(context.resource) ||
|
||||
Ash.DataLayer.data_layer_can?(
|
||||
context.resource,
|
||||
{:filter_expr, operator}
|
||||
) do
|
||||
{:cont,
|
||||
{:ok, BooleanExpression.optimized_new(:and, expression, operator)}}
|
||||
else
|
||||
{:halt,
|
||||
{:error, "data layer does not support the operator #{inspect(operator)}"}}
|
||||
end
|
||||
end
|
||||
operator_module ->
|
||||
left =
|
||||
if is_list(at_path) do
|
||||
%Call{
|
||||
name: :get_path,
|
||||
args: [
|
||||
%Ref{
|
||||
attribute: attr,
|
||||
relationship_path: context[:relationship_path] || [],
|
||||
resource: context.resource,
|
||||
input?: true
|
||||
},
|
||||
at_path
|
||||
]
|
||||
}
|
||||
else
|
||||
{:error, error} -> {:halt, {:error, error}}
|
||||
%Ref{
|
||||
attribute: attr,
|
||||
relationship_path: context[:relationship_path] || [],
|
||||
resource: context.resource,
|
||||
input?: true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
with {:ok, [left, right]} <-
|
||||
hydrate_refs([left, value], context),
|
||||
refs <- list_refs([left, right]),
|
||||
:ok <-
|
||||
validate_refs(
|
||||
refs,
|
||||
context.root_resource,
|
||||
{attr, value}
|
||||
),
|
||||
{:ok, operator} <- Operator.new(operator_module, left, right) do
|
||||
if is_boolean(operator) do
|
||||
{:cont, {:ok, operator}}
|
||||
else
|
||||
if is_nil(context.resource) ||
|
||||
Ash.DataLayer.data_layer_can?(
|
||||
context.resource,
|
||||
{:filter_expr, operator}
|
||||
) do
|
||||
{:cont, {:ok, BooleanExpression.optimized_new(:and, expression, operator)}}
|
||||
else
|
||||
{:halt,
|
||||
{:error, "data layer does not support the operator #{inspect(operator)}"}}
|
||||
end
|
||||
end
|
||||
else
|
||||
{:error, error} -> {:halt, {:error, error}}
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
|
|
|
@ -920,7 +920,7 @@ defmodule Ash.Test.Filter.FilterTest do
|
|||
|
||||
assert [%{embedded_bio: %{title: "Dr."}}] =
|
||||
Profile
|
||||
|> Ash.Query.filter_input(%{embedded_bio: %{title: "Dr."}})
|
||||
|> Ash.Query.filter_input(%{embedded_bio: %{at_path: [:title], eq: "Dr."}})
|
||||
|> Api.read!()
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue