Are `^actor(:role)` checks more performant than custom checks?

I have a use case that, basically:

  1. Forbids if some role conditions are true for the actor,
  2. 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()).

Yes, those checks above the authorize_if would make the ActorIsTeamLead check not run, but I’d likely just bake that into the check:

  def match?(actor, %{subject: %Ash.Changeset{attributes: %{team_id: team_id}}}, _opts) do
    actor.role == :team_lead && 
    WasteWalk.Teams.get_by_id!(team_id, authorize?: false, load: :is_team_lead, actor: actor).is_team_lead
  end
1 Like
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

Also there isn’t any current way to have a custom error message from policies right now. Its something we intend to add before long but those name things

1 Like