Imagine you're writing an application that allows multiple types of user accounts.
All users have a username, and no user should be allowed to choose a name that contains profanity.
At first, you duplicate the validation across all models:
class User < ActiveRecord::Base
  BAD_WORDS = %w(darn gosh heck golly)
  validate :username_does_not_contain_profanity
  private
  def username_does_not_contain_profanity
    if BAD_WORDS.any? { |word| username.include?(word) }
      errors.add(:username, "cannot contain naughty words!")
    end
  end
end
class Admin < ActiveRecord::Base
  validate :username_does_not_contain_profanity
  private
  def username_does_not_contain_profanity
    if BAD_WORDS.any? { |word| username.include?(word) }
      errors.add(:username, "cannot contain naughty words!")
    end
  end
end
How can we reuse this validation and remove the duplication?
A custom validator is a nice choice here:
# app/validators/safe_for_work_validator.rb
class SafeForWorkValidator < ActiveModel::EachValidator
  BAD_WORDS = %w(darn gosh heck golly)
  def validate_each(record, attribute, value)
    if BAD_WORDS.any? { |word| value.include?(word) }
      record.errors[attribute] << (options[:message] || "is not safe for work!")
    end
  end
end
class User < ActiveRecord::Base
  validates :username, safe_for_work: true
end
class Admin < ActiveRecord::Base
  validates :username, safe_for_work: true
end
This approach has some nice benefits:
User and Admin.BAD_WORDS has a sensible place to live now.Note: simply extracting a module would only have yielded the last benefit.
And, since we used ActiveModel::EachValidator, we can apply this validation to any attribute from any model, not just usernames:
class Team < ActiveRecord::Base
  validates :name, safe_for_work: true
end
  Return to Flashcard Results