I have a use case that, basically:
- Forbids if some role conditions are true for the actor,
- Authorizes if the actor is a team lead of the specific team.
To figure out if the actor is a team lead (of a given sprint), I have to load the team, load the team’s members, and check to see if the actor is current a member and has a “team lead” role on the User
. It’s mostly done in a calculation, and a small custom check:
# ActorIsTeamLead
def match?(actor, %{subject: %Ash.Changeset{attributes: %{team_id: team_id}}}, _opts) do
WasteWalk.Teams.get_by_id!(team_id, authorize?: false)
|> Ash.calculate!(:is_team_lead, actor: actor)
end
Technically, to decide if an actor can create a new Sprint
, all I need to do is this:
# Sprint.ex
policy action_type([:create]) do
authorize_if WasteWalk.Checks.Sprint.ActorIsTeamLead
end
My question is, would it be potentially more performant to weed out users based on role first, like so?
# Sprint.ex
policy action_type([:create]) do
forbid_if expr(^actor(:role) == :user), name: "team members (non-team leads) cannot create sprints"
forbid_if expr(^actor(:role) != :team_lead), name: "only team leads can create sprints"
authorize_if WasteWalk.Checks.Sprint.ActorIsTeamLead
end
My thinking is that the actor is already in memory – so, potentially a decision could be made without resorting to a query almost all of the time.
But I don’t know if that’s how Ash policies work – will the query be run, no matter what, so having the extra checks is pointless… or, does it optimize to avoid unnecessary queries like this?
(Separately, I was trying to see if the DSL could do this without a custom check, e.g., by using something like relates_to_actor_via(:team, field: :is_team_lead)
but I don’t think so… because it’s not an actor relationship, it’s sprint.team_id->team.is_team_lead()
).