AE
Ash Elixir•2y ago
lpmay

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):
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
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
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?
8 Replies
ZachDaniel
ZachDaniel•2y ago
They happen in order 😄
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
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
that should produce an error
lpmay
lpmayOP•2y ago
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 expect
ZachDaniel
ZachDaniel•2y ago
Hmm….yeah something seems up there Does attribute_in not seem like what you want? Or do you want the opposite of that
lpmay
lpmayOP•2y ago
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 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 with
dblack
dblack•2y ago
You can run the validation in a 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 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 :role hasn't yet changed so it always passes
lpmay
lpmayOP•2y ago
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? 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 better
dblack
dblack•2y ago
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 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 action
ZachDaniel
ZachDaniel•2y ago
The 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:
validate fn changeset, _ ->
...
end

# or
validate MyValidation

defmodule Myvalidation do
use Ash.Resource.Validation
end
validate fn changeset, _ ->
...
end

# or
validate MyValidation

defmodule Myvalidation do
use Ash.Resource.Validation
end

Did you find this page helpful?