Building Custom Validators in Ruby on Rails

Bhavesh Saluja
3 min readMar 21, 2024

--

Custom Validators in Ruby on Rails

Imagine your Rails application humming along, processing user data. But what happens when the data doesn’t meet your specific requirements? Built-in validations offer a good starting point, but sometimes, you need more. Enter custom validators, valiant defenders ensuring data adheres to your unique criteria. This blog equips you to create custom validators effectively in your Ruby on Rails applications, safeguarding data integrity and enhancing user experience.

Exposing the Core Need:

  • The Limits of Built-ins: While Rails provides a robust set of built-in validators, specific business logic or complex validation rules might necessitate custom solutions.
  • The Power of Customization: Custom validators allow you to tailor data validation to your application’s unique needs, ensuring data adheres to your specific criteria.

Crafting Custom Validators:

  1. Inheritance: Inherit from the ActiveModel::EachValidator class to create your custom validator.
  2. validate_each Method: Implement the validate_each method within your validator class. This method receives the record instance and the attribute being validated as arguments.
class UrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ URI::regexp(%w(http https))
record.errors.add(attribute, :invalid_url, message: "Please enter a valid URL")
end
end
end

Error Messages: Utilize the record.errors.add method within validate_each to define custom error messages for failed validations.

  • Options and Attributes: Define custom options for your validator using the attr_accessor method and access them within validate_each.

Validating Complex Password Strength:

class PasswordStrengthValidator < ActiveModel::EachValidator
def initialize(options)
@minimum_length = options[:minimum_length] || 8
super
end

def validate_each(record, attribute, value)
unless value =~ /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^\da-zA-Z])(#{minimum_length},)/
record.errors.add(attribute, :weak_password, message: "Password must be at least #{@minimum_length} characters and include a number, lowercase letter, uppercase letter, and special character")
end
end
end

Building Two Custom Validators:

Here, we’ll create two custom validators for a User model:

  • SocialSecurityNumberValidator: Validates the format of a social security number (example format: XXX-XX-XXXX).
  • UniqueUsernameValidator: Ensures usernames are unique across all users.
class SocialSecurityNumberValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\d{3}-\d{2}-\d{4}/
record.errors.add(attribute, :invalid_format, message: "Please enter a valid social security number (XXX-XX-XXXX)")
end
end
end

class UniqueUsernameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
existing_user = User.find_by(username: value)
if existing_user && existing_user != record
record.errors.add(attribute, :taken, message: "Username is already taken")
end
end
end

Using Validators in a Model:

Now, let’s integrate these validators into our User model:

class User < ApplicationRecord
validates :social_security_number, presence: true, with: SocialSecurityNumberValidator
validates :username, presence: true, uniqueness: true, with: UniqueUsernameValidator
end

Advanced Techniques:

  • Context-Aware Validation: Access other attributes of the record within validate_each to perform validations based on their values.
  • Custom Validation Classes: Consider creating separate validator classes for complex validation logic, promoting code organization and reusability.
  • Leveraging Gems: Explore gems like validates_email_format or validates_timeliness for pre-built validation functionalities.

Conclusion:

By mastering custom validators in Ruby on Rails, you empower your applications to enforce strict data validation rules. You can ensure data adheres to specific formats, enforce complex business logic, and provide informative error messages to users. Remember, the journey of crafting custom validators is ongoing. Explore advanced techniques, consider using gems for common validation needs, and continuously adapt your approach based on your application’s unique requirements.

--

--