Skip to content

Instantly share code, notes, and snippets.

@coreyhaines
Created August 9, 2012 15:52
Show Gist options
  • Save coreyhaines/3305349 to your computer and use it in GitHub Desktop.
Save coreyhaines/3305349 to your computer and use it in GitHub Desktop.
Thoughts on ActiveModel::Validator as a validation service
# I love the helpers, they help to make the intention clear.
# I want to be able to do this. That is, using the validation helpers inside a validator object,
# rather than having to do manual validation.
class EmployeeValidator < ActiveModel::Validator
def validate(record)
record.validates_presence_of :email
end
end
# After some reading, I realize I can do this
class EmployeeValidator < ActiveModel::Validator
def setup(employee_class)
user_class.validates_presence_of :email
end
def validate(record)
end
end
# This provides the ability to use the helpers from a validator object.
# Now, I'm working on a way to bypass the validations from examples
# that aren't concerned with the validity of an object, such scope examples.
# Unfortunately, the setup method is called once when validates_with is called
# since ActiveRecord caches the validation objects. So, it has already been called
# and configured with the validation before a non-validation-oriented example
# can use it. So, no over-riding.
# Looking at the way validation helpers work, they all are backed by a subclass of
# Validator (or EachValidator), so you should be able to instantiate one and use it.
class EmployeeValidator < ActiveModel::Validator
def validate(record)
ActiveModel::Validations::PresenceValidator.new(attributes: [:first_name]).validate record
end
end
# This works for doing the validation.
# Yes, this is ugly, but we can wrap it in a helper, perhaps even having Active Record models
# expose the helpers on an instance level.
# However, when our example runs that wants to bypass them, it keeps using the old version.
# I want to be able to do this at the top of my validation-less example.
class EmployeeValidator < ActiveModel::Validator
def validate(record); end
end
# But, because the instance of the validator is cached when validates_with EmployeeValidator is
# called, my over-ride does not cut in. Boo!
# ActiveModel::Validations uses hooks to call the validations.
# Is there a way to disable the hook for a single example?
@coreyhaines
Copy link
Author

My goal is to use ActiveModel::Validators as my validation services, so I can keep these concerns separate from the other parts of my model concerns.

@solnic
Copy link

solnic commented Aug 9, 2012

FYI that's how it's gonna work in DM2. We're moving away from validations being mixed into domain models.

@coreyhaines
Copy link
Author

Cool. ActiveModel::Validator looks nice, and I've got this working, now just figuring out how to disable them if I don't want them.

@codesoda
Copy link

codesoda commented Aug 9, 2012

What do you think about the idea that one of a number of User validation classes could be used in different contexts.
I'm starting to think it could be an anti-pattern. If there are different validation rules based on the context, then the User model should probably be broken up.

@coreyhaines
Copy link
Author

@codesoda Yeah, it does feel like an anti-pattern to have different validations based on different contexts of use. In production, you want the validations to run, it is just in the example (e.g. scope specs) that I don't want them to run.

@biscuitvile
Copy link

@coreyhaines I knew if I creeped up in your gists I'd find something like this. Did you figure out how to disable in a test context?

I like the helpers as well but am thinking a PORO approach may be easier. Inject a NilValidator in some kind of test context. Currently working on this so I'll post whatever I come up with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment