improvement: optimize for relates_to_actor_via

This commit is contained in:
Zach Daniel 2022-09-30 15:13:04 -04:00
parent a4b3d36ca9
commit 95a4aa3708
4 changed files with 64 additions and 78 deletions

View file

@ -3,6 +3,7 @@ defmodule Ash.Policy.Check.RelatesToActorVia do
use Ash.Policy.FilterCheck
require Ash.Expr
import Ash.Filter.TemplateHelpers
@impl true
def describe(opts) do
@ -12,80 +13,60 @@ defmodule Ash.Policy.Check.RelatesToActorVia do
@impl true
def filter(opts) do
opts = Keyword.update!(opts, :relationship_path, &List.wrap/1)
{last_relationship, to_many?} = relationship_info(opts[:resource], opts[:relationship_path])
pkey =
opts[:resource]
|> Ash.Resource.Info.related(opts[:relationship_path])
|> Kernel.||(raise "Must be able to determine related resource for `relates_to_actor_via`")
last_relationship.destination
|> Ash.Resource.Info.primary_key()
Ash.Expr.expr(exists(^opts[:relationship_path], ^Enum.map(pkey, &{&1, {:_actor, &1}})))
if to_many? do
Ash.Expr.expr(exists(^opts[:relationship_path], ^Enum.map(pkey, &{&1, {:_actor, &1}})))
else
Enum.reduce(pkey, nil, fn field, expr ->
if expr do
Ash.Expr.expr(^expr and ^ref(opts[:relationship_path], field) == ^actor(field))
else
Ash.Expr.expr(^ref(opts[:relationship_path], field) == ^actor(field))
end
end)
end
end
@impl true
def reject(opts) do
opts = Keyword.update!(opts, :relationship_path, &List.wrap/1)
{last_relationship, to_many?} = relationship_info(opts[:resource], opts[:relationship_path])
pkey =
last_relationship.destination
|> Ash.Resource.Info.primary_key()
if to_many? do
Ash.Expr.expr(not (^filter(opts)))
else
expr =
Enum.reduce(pkey, nil, fn field, expr ->
if expr do
Ash.Expr.expr(^expr and is_nil(^ref(opts[:relationship_path], field)))
else
Ash.Expr.expr(is_nil(^ref(opts[:relationship_path], field)))
end
end)
Ash.Expr.expr(not (^filter(opts)) or ^expr)
end
end
defp relationship_info(resource, path, to_many? \\ false)
defp relationship_info(resource, [rel], to_many?) do
rel = Ash.Resource.Info.relationship(resource, rel)
{rel, to_many? || rel.cardinality == :many}
end
defp relationship_info(resource, [rel | rest], to_many?) do
rel = Ash.Resource.Info.relationship(resource, rel)
relationship_info(rel.destination, rest, to_many? || rel.cardinality == :many)
end
end
# defmodule Ash.Policy.Check.RelatesToActorVia do
# @moduledoc false
# use Ash.Policy.FilterCheck
# require Ash.Expr
# @impl true
# def describe(opts) do
# path = Enum.join(opts[:relationship_path], ".")
# "record.#{path} == actor"
# end
# @impl true
# def filter(opts) do
# {last_relationship, to_many?} = relationship_info(opts[:resource], opts[:relationship_path])
# pkey =
# last_relationship.destination
# |> Ash.Resource.Info.primary_key()
# if to_many? do
# Ash.Expr.expr(exists(^opts[:relationship_path], ^Enum.map(pkey, &{&1, {:_actor, &1}})))
# else
# put_in_path(opts[:relationship_path], Enum.map(pkey, &{&1, {:_actor, &1}}))
# end
# end
# @impl true
# def reject(opts) do
# {last_relationship, to_many?} = relationship_info(opts[:resource], opts[:relationship_path])
# pkey =
# last_relationship.destination
# |> Ash.Resource.Info.primary_key()
# if to_many? do
# Ash.Expr.expr(not exists(^opts[:relationship_path], ^Enum.map(pkey, &{&1, {:_actor, &1}})))
# else
# [
# or: [
# [not: filter(opts)],
# [put_in_path(opts[:relationship_path], Enum.map(pkey, &{:is_nil, &1}))]
# ]
# ]
# end
# end
# defp relationship_info(resource, path, to_many? \\ false)
# defp relationship_info(resource, [rel], to_many?) do
# rel = Ash.Resource.Info.relationship(resource, rel)
# {rel, to_many? || rel.cardinality == :many}
# end
# defp relationship_info(resource, [rel | rest], to_many?) do
# rel = Ash.Resource.Info.relationship(resource, rel)
# relationship_info(rel.destination, rest, to_many? || rel.cardinality == :many)
# end
# defp put_in_path([], value) do
# value
# end
# defp put_in_path([key | rest], value) do
# [{key, put_in_path(rest, value)}]
# end
# end

View file

@ -10,9 +10,13 @@ defmodule Ash.Test.Support.PolicyComplex.Comment do
# you can't see comments on your post by people who aren't your friend
# for testing purposes :)
policy action_type(:read) do
authorize_if expr(author == ^actor(:id))
authorize_if expr(post.author == ^actor(:id))
authorize_if expr(exists(author.friends, id == ^actor(:id)))
# authorize_if expr(author == ^actor(:id))
# authorize_if expr(post.author == ^actor(:id))
# authorize_if expr(exists(author.friends, id == ^actor(:id)))
authorize_if relates_to_actor_via(:author)
authorize_if relates_to_actor_via([:post, :author])
authorize_if relates_to_actor_via([:author, :friends])
end
policy action_type(:create) do

View file

@ -16,7 +16,8 @@ defmodule Ash.Test.Support.PolicyComplex.FriendLink do
# note to reader: in real life you'd want some kind of invite/approval system
policy action_type(:read) do
authorize_if expr(source_id == ^actor(:id) or destination_id == ^actor(:id))
authorize_if relates_to_actor_via(:source)
authorize_if relates_to_actor_via(:destination)
end
end

View file

@ -8,8 +8,8 @@ defmodule Ash.Test.Support.PolicyComplex.Post do
policies do
policy action_type(:read) do
authorize_if expr(author == ^actor(:id))
authorize_if expr(exists(author.friends, id == ^actor(:id)))
authorize_if relates_to_actor_via(:author)
authorize_if relates_to_actor_via([:author, :friends])
end
policy action_type(:create) do