sheerlox
Ash 3.0 - adding errors in `before_transaction` hook does not abort further hooks execution
Currently migrating from 2.x to 3.x!
My test suite detected a behavior change that I couldn’t explain by looking at the upgrade doc or the new documentation, so I’m really not sure how I should handle this.
I got an update action that starts the email change process, which is basically just my Ash version of what is found in the mix phx.gen.auth generator.
If the email is already taken, I add an error with Changeset.add_error/2 in the first before_transaction hook:
update :request_email_change do
description """
Create a user token for changing the credential's email and send a confirmation email.
"""
accept [:email]
argument :current_password, :string, allow_nil?: false, sensitive?: true
argument :update_email_url_fun, :function,
allow_nil?: false,
description: """
A function taking the confirmation token as its only argument and returning the URL to send to the user.
"""
validate changing(:email)
change fn changeset, %{actor: actor} ->
changeset
|> Changeset.before_transaction(fn changeset ->
case changeset
|> Changeset.get_attribute(:email)
|> UserCredential.get_by_email(actor: actor) do
{:ok, _} ->
Changeset.add_error(
changeset,
Ash.Error.Changes.InvalidArgument.exception(
field: :email,
message: "has already been taken"
)
)
{:error, _} ->
changeset
end
end)
|> Changeset.before_transaction(&validate_current_password/1)
|> Changeset.before_action(fn changeset ->
current_email = Changeset.get_data(changeset, :email)
update_email_url_fun = Changeset.get_argument(changeset, :update_email_url_fun)
{:ok, applied_credential} = Ash.Changeset.apply_attributes(changeset)
Accounts.deliver_credential_update_email_instructions(
Ash.load!(applied_credential, [:user], actor: actor),
current_email,
update_email_url_fun
)
# clear the change so no update happens yet
Changeset.clear_change(changeset, :email)
end)
end
end
I’m almost 100% positive that previously this prevented the execution of the next hooks.
The following test makes sure the email validation uniqueness works, and was passing before upgrading to 3.0:
test "validates email uniqueness", %{user: user, credential: credential} do
%{email: email} = user_registration_fixture().credential
assert_raise Ash.Error.Invalid,
~r/email: has already been taken/,
fn ->
UserCredential.request_email_change!(
credential,
email,
valid_user_credential_password(),
&update_email_url_fun/1,
actor: user
)
end
end
Instead of raising the expected Ash.Error.Invalid as before, I know get a Ash.Error.Unknown because there is a match error in the before_action hook (when matching {:ok, applied_credential}), despite my InvalidArgument error being present in the changeset:
%Ash.Error.Unknown{
changeset: "#Changeset<>",
errors: [
%Ash.Error.Unknown.UnknownError{
error: %MatchError{
term: {:error,
#Ash.Changeset<
domain: TalentIdeal.Accounts,
action_type: :update,
action: :request_email_change,
attributes: %{
email: #Ash.CiString<"user-576460752303423358@example.com">
},
relationships: %{},
arguments: %{ ... },
errors: [
%Ash.Error.Changes.InvalidArgument{
field: :email,
message: "has already been taken",
value: nil,
splode: nil,
bread_crumbs: [],
vars: [],
path: [],
stacktrace: #Splode.Stacktrace<>,
class: :invalid
}
],
data: #TalentIdeal.Accounts.UserCredential< ... >,
valid?: false
>}
},
field: nil,
splode: Ash.Error,
bread_crumbs: [],
vars: [],
path: [],
stacktrace: #Splode.Stacktrace<>,
class: :unknown
}
]
}
Am I missing something or is there indeed a behavior change, and is it expected? Is it documented somewhere I didn’t find?
Thanks in advance!
Marked As Solved
zachdaniel
Alright, try main now, I think I fixed the actual issue ![]()
Also Liked
sheerlox
The first two solutions didn’t change anything, but your latest 2316b7b commit fixed the issue.
Thanks again Zach, you rock!
P.S. I’m still having issues believing you’re not a super-AI from the future, how do you do it ![]()
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










