Most ORMs out there include model validations as part of the features they provide. I think that’s kind of cool and very useful, but sometimes it’s better to abstract that responsibility away from the model, especially in cases where validations are tightly tied to other factors or flows.
A great little gem for doing that is scrivener, which facilitates creating these kind of “validators”. From its README:
Scrivener removes the validation responsibility from models and acts as a filter for whitelisted attributes.
A model may expose different APIs to satisfy different purposes For example, the set of validations for a User in a Sign up process may not be the same as the one exposed to an Admin when editing a user profile While you want the User to provide an email, a password and a password confirmation, you probably don’t want the admin to mess with those attributes at all.
In a wizard, different model states ask for different validations, and a single set of validations for the whole process is not the best solution.
I think the example of a user flow is very clear, even with very basic cases. So let’s look at it in more detail. For example, let’s say that when signing up, users must provide a username, but when editing their account, we don’t want to allow them to change it.
The same goes for passwords. We need to require one when signing up, but let’s say that you can’t change it directly when editing your account, and instead we have a different password-reset flow for that.
That’s a trivial example and yet it provides a jarringly strong contrast between the kind of validations that we need to do for the same model in each situation.
This is because those validations are not specifically about data integrity, but rather about user input. We usually take these two to be the same, but I’ve found that things are simpler from the latter perspective. With that in mind, this is an example of how these 2 different set of validations could look like:
Again, this is a rather trivial example, but it’s easy to see how
extracting and isolating the responsibility of validating
user input can be useful. A side benefit of this is that we don’t have to worry
about whitelisting attributes in the model either,
as instead of saving user input directly, the
AccountUpdate entities implicitly define a set of attributes available and
we ensure the model always receives valid attributes and data.
Scrivener will throw an exception if you try assigning attributes to it that are not defined in your validator, so you can treat that case anyway you like to whitelist what parameters you want to allow validating in the first place.
Scrivener provides a way of further filtering these attributes with slices, so if you need to use a specific subset of the attributes defined, you can, but that’s just the cherry on top.
We use this gem regularly and it’s worked great for us, but I also want to encourage you to build your own. While this gem is very small (< 300 LOC) it could be interesting and fun to write your own assertions and behaviours. And you can’t put a price on fun.