Confused about `one_of` vs `attribute_equals`
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):
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:
Can someone point out what I am misunderstanding?8 Replies
They happen in order 😄
that should produce an error
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
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?
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
hmm so attribute_in
works as I expect one_of
to work, so clearly there is some distinction I don't quite get yet
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 expectHmm….yeah something seems up there
Does
attribute_in
not seem like what you want?
Or do you want the opposite of thatYea
attribute_in
and attribute_equals
both seem to work exactly as I expect, but one_of
with a list of one element works differently
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
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
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
just as an exercise to kick the tires withYou can run the validation in a Not sure if that's expected or a bug?
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
before_action
hook, something like validate one_of(:role, [:unverified]), before_action?: true
should do it
Bit more info about the validate options are here: https://ash-hq.org/docs/dsl/ash-resource#validations-validate
sorry, re-read your original question. I'm not sure why one_of would act differently to attribute_equals
seems like they both use different functions to get the value from the changeset
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
and attribute_equals uses get_attribute
: https://github.com/ash-project/ash/blob/v2.11.11/lib/ash/resource/validation/attribute_equals.ex#L33
:role
hasn't yet changed so it always passesThanks 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?
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?
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
.
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 betterYeah 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
I think I might have sent you on a bum steer with before_action, that's probably not useful for this example
Not 100% sure but maybe before_action
operates only on the changeset? You'd use it to modify the changeset before running the actionThe
before_action?
will only fail when you attempt to submit the action (not when you validate the changeset)
It definitely seems like the behavior of those builtin validations can be confusing, especially when comparing the two
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 🙂
But, for example: