Clean Validations with Custom Contexts
Tags: rails, activerecord, validations, context
Published: 2018-08-01 10:29:50

---
title: Clean Validations with Custom Contexts
published: true
description: Rails Active Record Validations with custom contexts
tags: #rails #activerecord #validations #context
---

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

```
class User < ApplicationRecord
  validates :name, presence: { message: "must be given please" }
end
```
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  
end
```
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

  private
  def basic_info
    # Validation for basic info, first_name, last_name, email
  end
  
  def education_details
    # Validation for education_details
  end

  def professional_info
    # Validation for professional_info
  end
end
```
In the controller 
```
class UsersController < ApplicationController
  ...
  
  def update_basic_info
    @user.assign_attributes(basic_info_params)
    @user.save(:basic_info)
  end

  def update_education_details
    @user.assign_attributes(education_details_params)
    @user.save(:education_details)
  end

  def update_professional_info
    @user.assign_attributes(professional_info_params)
    @user.save(:professional_info)
  end

  private
  def basic_info_params
    # strong params
  end

  def education_details_params
    # strong params
  end

  def professional_info_params
    # strong params
  end
end
```

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
      assign_attributes(attributes)
      save(context: contexts)
    end
  end
end
```
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
  end
```

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