Clean Validations with Custom Contexts

spidergears profile image Deepak Singh ・2 min read

Active Record validations are a well-known and widely used in Rails.

class User < ApplicationRecord
  validates :name, presence: { message: "must be given please" }

This runs the validation on save, both when creating a new record or when updating an existing record.

on option allows control over when to run the validation, commonly used with value of create or update

class User < ApplicationRecord
  belongs_to :club, optional: true 
  validates :name, presence: { message: "must be given please" }, on: :create
  validates :club, presence: { message: "must be given please" }, on: :update  

This allows creating users without associating them with a Club, but enforces presence of Club on subsequent updates. This pattern is commonly used to allow users to signup with bare minimum form fields and then forcing them to update their profiles with more information on subsequent visits.

Value for the on option is not limited to create and update, we can have our own custom contexts. Like in a multistep form, we can have validations for each of the steps. on options makes this really easy to do

class User < ApplicationRecord
  validate :basic_info, on: :basic_info
  validate :education_details, on: :education_details
  validate :professional_info, on: :professional_info

  def basic_info
    # Validation for basic info, first_name, last_name, email

  def education_details
    # Validation for education_details

  def professional_info
    # Validation for professional_info

In the controller

class UsersController < ApplicationController

  def update_basic_info

  def update_education_details

  def update_professional_info

  def basic_info_params
    # strong params

  def education_details_params
    # strong params

  def professional_info_params
    # strong params

With Rails 5 adding support for multiple contexts, we can use multiple context together

@user.save(:basic_info, :professional_info)

This seems pretty neat, let's go a step further and do this with update_attributes. In current implementation of Rails,
update_attributes does not support validation contexts. We can get around this by defining our own custom method

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  def update_attibutes_with_context(attributes, *contexts)
    with_transaction_returning_status do
      save(context: contexts)

In the controller

@user.update_attibutes_with_context({first_name: 'fname'}, :basic_info)

Lastly, we can use with_options to group multiple validation within a context

  with_options on: :member do |member_user|
    member_user.validates :club_name, presence: true
    member_user.validates :membership_id, presence: true

This makes really easy to write readable and maintainable code, with a good separation of concerns.

Read my last post on ActiveRecord Validations here https://dev.to/spidergears/rails-active-record-validation-messages-4dcf

Posted on by:

spidergears profile

Deepak Singh


Director, Eloquent Studio Pvt. Ltd. | Ruby on Rails, Go, React Developer


markdown guide

Thanks for the article!
I have not used on option with custom context, it is interesting approach. But I think it should be used very rarely, because of otherwise we are about to get bloated models with many responsibilities. In most cases such a things would be better to incapsulate out of models, e.g. by using form objects..


Absolutely agree with you, should be used only when it fits the case.

Example is for demonstration and to hint at capabilities of the framework, form object are much more suitable for the scenario.

Thanks for going through, and putting out your thoughts. :)