Rails validators

Sep 29, 2016

Rails model validations are very important for ensuring data integrity. You usually start with really simple inlinevalidates :name, presence: true.

You can then implement more complex validations for inclusion, format, numericallity. Or add conditional validations. Eventually the logic gets too complex for one line statements and you write custom methods

But then you encounter a situation where you need to perform the same validations in two different models.

# app/models/user.rbclassNameValidator<ActiveModel::Validatordefvalidate(record)unlessrecord.name.starts_with?'X'record.errors[:name]<<'Need a name starting with X please!'endendendclassUser<ApplicationRecordvalidates_withNameValidator...end# app/models/organization.rbclassOrganization<ApplicationRecordvalidates_withUser::NameValidator...end

Plus this approach is just not very clean. Instead, why not create a app/validators folder and put the class there?

# app/validators/name_validator.rbclassNameValidator<ActiveModel::Validatordefvalidate(record)@record=record# => call appropriate method to do validation based on record typesend(record.class.name.downcase)endprivatedefuserunless@record.name.starts_with?'X'@record.errors[:name]<<'Need a name starting with X please!'endenddeforganization# could be slightly different validationendend# app/models/user.rbclassUser<ApplicationRecordvalidates_withNameValidator...end# app/models/organization.rbclassOrganization<ApplicationRecordvalidates_withNameValidator...end

Keep in mind that these validators are POROs so you could create class AppplicationValidator < ActiveModel::Validator to contain common logic and inherit from that. And you can create spec/validators/name_validator_spec.rb and test validation by passing different record types (user or organization).

One useful place for custom validator class is when records can only belong to certain types of records. Let’s say we have Account that belongs_to Organization. User also belongs_to Organization AND has_and_belongs_to_many Accounts. But User cannot have Account that belongs_to different Organization than User.