I have a resource with a bunch of custom, expensive validations that I want to run on :update and :create actions. Everything works as expected, except that I use Ash.can? on the UI for the sole purpose of enabling/disabling/hiding buttons in a table view (which in a sense is a “:read" view) depending on whether the user can :update each record (and also perform other actions on it).
The problem is that Ash.can? internally creates a changeset and validates it, causing my expensive validations to be run multiple times just for showing the table view in the UI. Is there a way to avoid this? I just need to determine if the user is allowed to :update, I don’t want to validate the resource attributes, which I assume to be valid anyways when loaded from the db, and which in my case have nothing to do with the logic behind the authorization of the action.
I was thinking of manually creating the changeset and passing a custom flag in the context before calling Ash.can?on the changeset, so that I can skip the expensive validations when the flag is set. Is this the idiomatic way to achieve this? Although it seems a bit cumbersome, because it introduces an undocumented “accidental language” (to quote @zachdaniel) in my codebase.
Using Ash.can? for UI permission checks can definitely become costly when heavy validations run each time. The common approach is to build a lightweight changeset and pass a context flag so your expensive validations are skipped during pure authorization checks. It’s not the most elegant pattern, but for now it’s the idiomatic way to separate validation from permission logic in Ash.
I deal with similar workflow issues in my bail bonds tampa operations, where systems must run fast and avoid unnecessary validations during routine checks—so skipping heavy rules during “read-only” permission queries is often the only practical solution.
Yes, I saw and tried those options. One would think that :validate? did exactly that, but it does a completely different thing.
I think I will go for the context flag, and maybe a helper module like this:
defmodule MyApp.NotAnAuthCheck do
use Ash.Resource.Validation
@impl true
def supports(_opts), do: [Ash.Changeset]
@impl true
def validate(_changeset, _opts, context) do
if context.source_context[:_authorization_check] do
{:error, message: "This is an authorization check"}
else
:ok
end
end
end