Confused about `one_of` vs `attribute_equals`
lpmay:
Still messing around and encountered a confusing behavior with validations. I have a
:verify
action on a resource, which I only want to run on resources which have
:role
set to
:unverified
The below works as expected (produces valid/invalid changesets as expected):
update :verify do #custom action to verify an unverified user
validate attribute_equals(:role, :unverified) #only verify unverified users
change set_attribute(:role, :verified)
accept []
end
What confuses me is that the below version using
one_of
instead of
attribute_equals
seems to me like it should do the same thing, but it never seems to produce an invalid changeset:
update :verify do #custom action to verify an unverified user
validate one_of(:role, [:unverified]) #only verify unverified users
change set_attribute(:role, :verified)
accept []
end
Can someone point out what I am misunderstanding?
zachdaniel:
They happen in order š
zachdaniel:
update :verify do #custom action to verify an unverified user
change set_attribute(:role, :verified)
validate one_of(:role, [:unverified]) #only verify unverified users
accept []
end
zachdaniel:
that should produce an error
lpmay:
Hmm Ok the in order thing is good to know - but I think Iāve tried it with the order the same but the behavior of
attribute_equals
and
one_of
with a list of one is not the same
lpmay:
Am I maybe completely misusing this feature? Are validations just to make sure the attributes are consistent after the action, but Iām trying to use them to gate which resources the action can run on in the first place?
lpmay:
yea ok I just did it again - exact same order, but if the validate clause uses
attribute_equals
I can generate invalid changesets, but not with
one_of
lpmay:
hmm so
attribute_in
works as I expect
one_of
to work, so clearly there is some distinction I donāt quite get yet
lpmay:
The distinction here is still confusing:
attribute_in
āValidates that an attribute is being changed to one of a set of specific values, or is in the the given list if it is not being changed. ā which seems ill-defined for my case where I am changing the attribute, but not
to
one of the specified values
one_of
ā Validates that an attributeās value is in a given listā - seems more like what Iām going for but obviously doesnāt work how I expect
zachdaniel:
Hmmā¦.yeah something seems up there
zachdaniel:
Does
attribute_in
not seem like what you want?
zachdaniel:
Or do you want the opposite of that
lpmay:
Yea
attribute_in
and
attribute_equals
both seem to work exactly as I expect, but
one_of
with a list of one element works differently
lpmay:
Iām just getting started with Ash so my question is mostly around how to understand the difference, thereās either something wrong with
one_of
or more likely my mental model for how I should use these validations is wrong
lpmay:
I realize what I said is kind of confusing -
one_of
just seems to work differently in general, Iāve tried with different sized lists too
lpmay:
Sorry for the walls of text, but I also realize I couldāve been clearer about what Iām doing in the first place:
I have a
User
resource with a
:role
attribute constrained to be
one_of: [:admin, :verified, :unverified]
. I want my
verify
action to move an
unverified
user to
verified
but be invalid for any other type of user
lpmay:
just as an exercise to kick the tires with
dblack1:
You can run the validation in a
before_action
hook, something like
validate one_of(:role, [:unverified]), before_action?: true
should do it
dblack1:
Bit more info about the validate options are here: https://ash-hq.org/docs/dsl/ash-resource#validations-validate
dblack1:
sorry, re-read your original question. Iām not sure why one_of would act differently to attribute_equals
dblack1:
seems like they both use different functions to get the value from the changeset
dblack1:
one_of uses
fetch_argument_or_change
:
https://github.com/ash-project/ash/blob/v2.11.11/lib/ash/resource/validation/one_of.ex#L36C24-L36C24
dblack1:
and attribute_equals uses
get_attribute
:
https://github.com/ash-project/ash/blob/v2.11.11/lib/ash/resource/validation/attribute_equals.ex#L33
dblack1:
Not sure if thatās expected or a bug?
dblack1:
So yeah the difference between attribute_equals and one_of is subtle⦠one_of seems to check for changes to an attribute or argument already passed to the changeset, and passes if that attribute hasnāt yet changed. In your example above
:role
hasnāt yet changed so it always passes
lpmay:
Thanks for the clarification, I think I get it. Is it correct to think of it as
one_of
is validating the
changeset
while
attribute_equals
is validating against the
resource
? My
verify
action is adding the change to the
role
attribute, so the changeset
one_of
validates against does not have a
role
field to fail on?
lpmay:
Thanks for pointing out the
before_action?
option. Probably not a cut and dry answer, but is using
before_action
generally the more idiomatic approach, or is it better to rely on the order inside the action?
lpmay:
Ok - Iām even more confused now.
validate attribute_equals(:role, :unverified), before_action?: false
creates failed changests for user records with
:role != :unverified
as expected.
validate attribute_equals(:role, :unverified), before_action?: true
does
not
create a failed changset for user records with
:role != :unverified
.
lpmay:
Iāll have to spend some more time with the docs tomorrow and see if I can start to wrap my head around this a little better
dblack1:
Yeah I believe thatās the case for
one_of
⦠The doco indicates
attribute_equals
validates the changeset
then
the resource if that field isnāt getting changed in the changeset
dblack1:
I think I might have sent you on a bum steer with before_action, thatās probably not useful for this example
dblack1:
Not 100% sure but maybe
before_action
operates only on the changeset? Youād use it to modify the changeset before running the action
zachdaniel:
The
before_action?
will only fail when you attempt to submit the action (not when you validate the changeset)
zachdaniel:
It definitely seems like the behavior of those builtin validations can be confusing, especially when comparing the two
zachdaniel:
what Iād say is that a lot of those are just recomendations, and by design you can write your own validations if there is ever confusion/you arenāt getting behavior that you want. Of couse we should fix any issues with the builtin ones though š
zachdaniel:
But, for example:
validate fn changeset, _ ->
...
end
# or
validate MyValidation
defmodule Myvalidation do
use Ash.Resource.Validation
end