Dropped Ice Cream

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.

RailsConf 2024

I'm co-chairing RailsConf 2024 in Detroit May 7–9. Come and join us 

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
Brighton Ruby 2024

Still running UK’s friendliest, Ruby event on Friday 28th June. Ice cream + Ruby 


Last updated on September 8th, 2019 by @andycroll

An email newsletter, with one Ruby/Rails technique delivered with a ‘why?’ and a ‘how?’ every two weeks. It’s deliberately brief, focussed & opinionated.