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: