Is there a way to trigger a before_action without trying to run it?

I am doing some bulk-prevalidation where I am just doing an Ash.Changeset.for_create(..) to run validations. I need to load some related records based on a foregin key id in the inputs. Essentially I am validating if an architectural_element is valid, but it needs to use the study_id to load a study and all its related Outcrop so see that the chosen geology type exists in any of the related Outcrop. In a regular create I would just do a before action and look it up and then add error as needed. However in the bulk pre-validate I am not actually running the create action. Is there a good way to do this or do I just do it the "good old way"? Validate it manually and return errors as needed per item being validated?
4 Replies
ZachDaniel
ZachDaniel4mo ago
there are before batch hooks you can use Although i guess you mean "pre validation" as in before invoking the bulk create?
Oliver
OliverOP4mo ago
no I have an action called bulk_prevalidate where I take a ton of archi telements and do the changeset on each one then I check if the changeset is valid one validation needs to go look in the db, its just not "validation" which works as it should if I had done this one by one and as a before_action but I am not actually passing the changeset to a create or update the code fwiw
action :bulk_validate, {:array, Safari.Types.ArchitecturalElementBulkPreprocessResult} do
argument :elements, {:array, Safari.Types.BulkArchitecturalElementInput}, allow_nil?: false

run fn %{arguments: %{elements: elements}}, %{actor: actor} ->
results =
Enum.map(elements, fn element ->
input = Map.delete(element, :mark_as_trash)

# Try to create the changeset and check if it's valid
changeset = Ash.Changeset.for_create(__MODULE__, :create, input, actor: actor)

if changeset.valid? do
%{
architectural_element: changeset.attributes,
errors: []
}
else
# Convert Ash errors to BulkUploadError format
errors =
Enum.map(changeset.errors, fn error ->
%{
field: to_string(error.field || "unknown"),
error_message: error.message || "Validation error"
}
end)

# Still return a processed element but with errors

%{
architectural_element: changeset.attributes,
errors: errors
}
end
end)

{:ok, results}
end
end
action :bulk_validate, {:array, Safari.Types.ArchitecturalElementBulkPreprocessResult} do
argument :elements, {:array, Safari.Types.BulkArchitecturalElementInput}, allow_nil?: false

run fn %{arguments: %{elements: elements}}, %{actor: actor} ->
results =
Enum.map(elements, fn element ->
input = Map.delete(element, :mark_as_trash)

# Try to create the changeset and check if it's valid
changeset = Ash.Changeset.for_create(__MODULE__, :create, input, actor: actor)

if changeset.valid? do
%{
architectural_element: changeset.attributes,
errors: []
}
else
# Convert Ash errors to BulkUploadError format
errors =
Enum.map(changeset.errors, fn error ->
%{
field: to_string(error.field || "unknown"),
error_message: error.message || "Validation error"
}
end)

# Still return a processed element but with errors

%{
architectural_element: changeset.attributes,
errors: errors
}
end
end)

{:ok, results}
end
end
this works very nicely for anything with regular validations and I can just have another step where I check for the field being in the allowed fields from relations manually just curious if there is a way to "simulate" a full validation including a before_action without actually sending it into Ash.create(..)
ZachDaniel
ZachDaniel4mo ago
you can use Ash.Changeset.with_hooks and remove the after hooks before hand not pretty but doable but that will run all the hooks even ones that do side effects
Oliver
OliverOP4mo ago
only the ones I have actually attached no? I can make a create_for_bulk create or something that has only the one hook I want to run I mean there are no hidden hooks built in to the system? :p reading the docs more closely I have been thinking about this wrong I can just check manually and add_error on the changeset before the if valid and figure out a good way to code share with the actual hook in the :create

Did you find this page helpful?