image by Sarah Kilian
Use a custom validator
Active Model’s validations, used by calling validates
inside your model with various options, can be supplemented with your own custom classes. The Rails Guides contains a section on custom validations.
Instead of…
…using complex code in your validates
calls:
class Invite
validates :invitee_email, format: {
with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i,
message: "is not an email"
}
end
Use…
…a validator class and extract the logic into it:
# app/validators/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Invite
# Rails "magic" infers the name of the validation
validates :invitee_email, email: true
end
Why?
If you have a validation you might reuse, or that contains complex formatting rules, it’s a good idea to break out that functionality into its own object.
Now you can test that logic in isolation and, at the point of use, it’s easier to work out what is going on.
Why not?
For email validation in particular, there’s a little trick I’ve used in the past, which is succinct and doesn’t need a validator, but you need to be using devise
for your application’s authentication:
class Invite
validates :invitee_email, format: {
with: Devise.email_regexp,
allow_blank: false
}
end
This approach still works:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ Devise.email_regexp
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
Sign up to get a nugget of Ruby knowledge every couple of weeks or so.
Last updated on September 8th, 2019