Calling policy modules "directly"

Is there a way to invoke a policy module on an "ad-hoc" manner, similar to doing Resource.my_calculation? I have this:
defmodule MyApp.Ash.Accounts.User.Authorizers.UserIsMemberOfOrganization do
@moduledoc false
use Ash.Policy.FilterCheck

def describe(_opts),
do: "Ensure user is a member of the organization before updating their current dashboard organization"

def filter(actor, authorizer, _opts) do
organization_id = authorizer.changeset.data.current_dashboard_organization_id
user = Ash.load!(actor, organization_hierarchy: [id: actor.id, format: :list])

Enum.any?(user.organization_hierarchy, &(&1.id == organization_id))
end
end
defmodule MyApp.Ash.Accounts.User.Authorizers.UserIsMemberOfOrganization do
@moduledoc false
use Ash.Policy.FilterCheck

def describe(_opts),
do: "Ensure user is a member of the organization before updating their current dashboard organization"

def filter(actor, authorizer, _opts) do
organization_id = authorizer.changeset.data.current_dashboard_organization_id
user = Ash.load!(actor, organization_hierarchy: [id: actor.id, format: :list])

Enum.any?(user.organization_hierarchy, &(&1.id == organization_id))
end
end
Currently tied to an action:
policies do
policy action(:update_current_dashboard_organization) do
description "Current dashboard organization can only be updated if provided organization is one that the user is a member of"
authorize_if UserIsMemberOfOrganization
end

...
end
policies do
policy action(:update_current_dashboard_organization) do
description "Current dashboard organization can only be updated if provided organization is one that the user is a member of"
authorize_if UserIsMemberOfOrganization
end

...
end
But now I want to show/hide some UI elements based on the logic inside the policy module, so I'm wondering if I can call it "directly" if that makes sense.
14 Replies
frankdugan3
frankdugan36d ago
You want to use Ash.can?/3. There are a lot of options that can cover pretty much any scenario: https://hexdocs.pm/ash/Ash.html#can?/3
ZachDaniel
ZachDaniel6d ago
Ash.can? or for your code interfaces there is a generated can_ function i.e define :create_post gets you can_create_post?(....)
Ege
EgeOP6d ago
So would this be called like:
if AshUser.can_update_current_dashboard_organization?(current_user, %{current_dashboard_organization_id: organization_id}, actor: current_user) do
if AshUser.can_update_current_dashboard_organization?(current_user, %{current_dashboard_organization_id: organization_id}, actor: current_user) do
? It didn't like that:
10:43:33.689 [error] GenServer #PID<0.3578.0> terminating
** (Spark.Options.ValidationError) unknown options [:actor], valid options are: [:maybe_is, :filter_with, :validate?, :reuse_values?, :pre_flight?, :run_queries?, :data, :tenant, :alter_source?, :base_query, :no_check?, :on_must_pass_strict_check, :atomic_changeset, :return_forbidden_error?]
(ash 3.5.11) lib/ash/resource/interface.ex:42: anonymous fn/2 in Ash.Resource.Interface.CanOpts.validate!/1
10:43:33.689 [error] GenServer #PID<0.3578.0> terminating
** (Spark.Options.ValidationError) unknown options [:actor], valid options are: [:maybe_is, :filter_with, :validate?, :reuse_values?, :pre_flight?, :run_queries?, :data, :tenant, :alter_source?, :base_query, :no_check?, :on_must_pass_strict_check, :atomic_changeset, :return_forbidden_error?]
(ash 3.5.11) lib/ash/resource/interface.ex:42: anonymous fn/2 in Ash.Resource.Interface.CanOpts.validate!/1
ZachDaniel
ZachDaniel6d ago
It depends on the interface. If using Ash.can the arguments are different. See h AshUser.can_update_current_dashboard_organization? The actor is passed in as a positional argument
frankdugan3
frankdugan36d ago
Ooh right, I forgot about those new ones. Still haven't gotten around to using them. 😄
Ege
EgeOP6d ago
Doing h AshUser.can_update_current_dashboard_organization? doesn't say anything useful. It just repeats the Ash docs. I'm trying to figure out how to invoke the function. I posted the policy above. The docs are quite sparse. There are no "real life" examples of usage, just stuff like
# Can this user run this action with this input.
{Ash.Resource.t(), :atom, %{...input}}

# Can this user run this action with this input.
{Ash.Resource.t(), %Action{}, %{...input}}
# Can this user run this action with this input.
{Ash.Resource.t(), :atom, %{...input}}

# Can this user run this action with this input.
{Ash.Resource.t(), %Action{}, %{...input}}
Where the third argument isn't even valid Elixir syntax.
ZachDaniel
ZachDaniel6d ago
h AshUser.can_update_current_dashboard_organization? should at least show you the arguments doesn't it? I'm adding some examples
Ege
EgeOP6d ago
Doing this seems to have worked:
if AshUser.can_update_current_dashboard_organization?(current_user, current_user, %{current_dashboard_organization_id: organization_id}) do
if AshUser.can_update_current_dashboard_organization?(current_user, current_user, %{current_dashboard_organization_id: organization_id}) do
ZachDaniel
ZachDaniel6d ago
So that is an update action on users? If so yes that makes sense
Ege
EgeOP6d ago
Yeah, the user is attempting to switch to another org view basically The auto-generated can? stuff is pretty useful Thanks for updating the docs I think more broadly, me and the team would appreciate it if the docs were treated more as a first-class citizen. There are a LOT of gaps. It seems like the dev team is prioritizing adding more features over writing thorough documentation with real examples, guides, tutorials, etc. I point it out when I can, but don't exactly have the kind of spare time where I could do PRs myself (especially since my own knowledge of the framework is minimal)
ZachDaniel
ZachDaniel6d ago
We're doing both. I know its frustrating, but its also hard for me to hear that we aren't treating docs as a first class citizen when I spend multiple hours a week working on docs, reference material, guides etc. Every day I'm committing improvements to the docs, working on new guides (like this one: https://hexdocs.pm/ash/multi-step-actions.html) addressing these concerns. A lot of it isn't about the docs not being first class, its that all the questions you asked here are technically documented, just not where someone would have expected to find them, and its hard to understand what makes these things accessible or know up front what these deficiencies are. This is ultimately a community project, but at the end of the day, it ends up just feeling like a me project, despite the hundreds of companies using Ash in production on money-making ventures 🤷‍♂️
Failz
Failz6d ago
Are you using multitenancy?
Ege
EgeOP6d ago
I can't comment on the you-project vs. community-project aspect, Zach. But I'll post a specific recent example in the #documentation channel and propose a solution.

Did you find this page helpful?