How to handle submit-only actions without data layer but with validations?

Hi everyone, I'm just getting started with Ash and ran into a challenge when trying to build a LiveView form that validates input and then sends the data to an external API, without using a data_layer. I'm using a create action with validates and a change/3 callback for the external logic. The issue is: change/3 is called before validations are applied, and changeset.valid? is already true, so my logic executes even when the input is invalid. Since I'm not using a data_layer, before_action and after_action hooks are not triggered — which would have been ideal for running the API call only after validation. I already solved the issue of distinguishing between validate and submit (in LiveView) by passing a form_action argument manually. Now my question is: how can I run logic after validations have been applied, in a non-persistent action? Here’s a simplified version of what I’m doing: In the resource
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]
accept [:name, :document, :mobile, :email]
change App.Reverse.PatientLogic
end
end
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]
accept [:name, :document, :mobile, :email]
change App.Reverse.PatientLogic
end
end
In the LiveView
case Form.submit(socket.assigns.form, params: Map.put(params, "form_action", :submit)) do
{:ok, _patient} -> ...
{:error, form} -> ...
end
case Form.submit(socket.assigns.form, params: Map.put(params, "form_action", :submit)) do
{:ok, _patient} -> ...
{:error, form} -> ...
end
In PatientLogic
def change(changeset, _opts, _ctx) do
if changeset.arguments[:form_action] == :submit and changeset.valid? do
# Call external API...
end

changeset
end
def change(changeset, _opts, _ctx) do
if changeset.arguments[:form_action] == :submit and changeset.valid? do
# Call external API...
end

changeset
end
The issue is that changeset.valid? is already true before validations have actually run. Thanks, folks — really appreciate it in advance!
8 Replies
ZachDaniel
ZachDaniel5mo ago
before_action and after_action hooks are definitely triggered regardless of if you are using a data layer or not Technically, you're always using a data layer, you're just using Ash.DataLayer.Simple
Jhonathas
JhonathasOP5mo ago
This is my resource. Can you spot anything I might be missing that would prevent before_action or after_action from running? They only seem to work if I use change, and not otherwise.
defmodule App.Reverse.Patient do
use Ash.Resource,
otp_app: :app,
domain: App.Reverse,
extensions: [AshPhoenix]

resource do
require_primary_key? false
end

attributes do
attribute :name, :string, allow_nil?: false
attribute :document, :string
attribute :mobile, :string
attribute :email, :string
end

actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

change App.Reverse.PatientLogic
end
end
end
defmodule App.Reverse.Patient do
use Ash.Resource,
otp_app: :app,
domain: App.Reverse,
extensions: [AshPhoenix]

resource do
require_primary_key? false
end

attributes do
attribute :name, :string, allow_nil?: false
attribute :document, :string
attribute :mobile, :string
attribute :email, :string
end

actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

change App.Reverse.PatientLogic
end
end
end
ZachDaniel
ZachDaniel5mo ago
What do you mean by "if you use change"? Can you show how you tried with before action?
Jhonathas
JhonathasOP5mo ago
I tried using change, like below, and I noticed that it correctly calls App.Reverse.PatientLogic/3, but at that point changeset.valid? is still true. I assume that’s because validations haven’t run yet.
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

change App.Reverse.PatientLogic
end
end
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

change App.Reverse.PatientLogic
end
end
Then I tried replacing it with before_action, and later after_action, as shown here — but neither before_action nor after_action were invoked.
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

before_action App.Reverse.PatientLogic
end
end
actions do
create :register do
argument :form_action, :atom, constraints: [one_of: [:validate, :submit]]

accept [:name, :document, :mobile, :email]

before_action App.Reverse.PatientLogic
end
end
I also tried keeping a dummy change (just returning the changeset) alongside an after_action, but still, the after_action was never triggered. About “They only seem to work if I use change, and not otherwise.” What I meant is that the module is indeed executed when I use the change function — but that doesn’t solve my issue, because at that point the changeset is still marked as valid (valid?: true), even though validations haven't been applied yet.
ZachDaniel
ZachDaniel5mo ago
Ah, before_action Module isn't a thing 🙂
Elixord
Elixord5mo ago
Hexdocs Search Results
Searched ash-3.5.15 for multi-step actions
ash-3.5.15 | extras
ZachDaniel
ZachDaniel5mo ago
See the top guide for examples of using hooks
Jhonathas
JhonathasOP5mo ago
Ah, I see. One thing I realized now is that since I wasn't using validate inside the action, so I was using allow_nil? false, the validation in change wasn't working, and now that I've added it, the validation inside the change is working. I think this solves my problem. Thank you very much for all your attention, Zach.

Did you find this page helpful?