---
title: "Validate multiple contexts together in Rails 5"
description:
  "Rails 5 enhancement to validate a model on multiple contexts at once"
canonical_url: "https://www.bigbinary.com/blog/validate-multiple-contexts-in-rails-5"
markdown_url: "https://www.bigbinary.com/blog/validate-multiple-contexts-in-rails-5.md"
---

# Validate multiple contexts together in Rails 5

Rails 5 enhancement to validate a model on multiple contexts at once

- Author: Vijay Kumar Agrawal
- Published: April 8, 2016
- Categories: Rails 5, Rails

Active Record
[validation](http://guides.rubyonrails.org/active_record_validations.html) is a
well-known and widely used functionality of Rails. Slightly lesser popular is
Rails's ability to validate on custom context.

If used properly, contextual validations can result in much cleaner code. To
understand validation context, we will take example of a form which is submitted
in multiple steps:

```ruby
class MultiStepForm < ActiveRecord::Base
  validate :personal_info
  validate :education, on: :create
  validate :work_experience, on: :update
  validate :final_step, on: :submission

  def personal_info
    # validation logic goes here..
  end

  # Smiliary all the validation methods go here.
end
```

Let's go through all the four validations one-by-one.

_1. personal_info_ validation has no context defined (notice the absence of
`on:`). Validations with no context are executed every time a model save is
triggered. Please go through all the triggers
[here](http://guides.rubyonrails.org/active_record_validations.html).

_2. education_ validation has context of `:create`. It is executed _only_ when a
new object is created.

_3. work_experience_ validation is in `:update` context and gets triggered for
updates _only_. `:create` and `:update` are the only two pre-defined contexts.

_4. final_step_ is validated using a custom context named `:submission`. Unlike
above scenarios, it needs to be explicitly triggered like this:

```ruby
form = MultiStepForm.new

# Either
form.valid?(:submission)

# Or
form.save(context: :submission)
```

`valid?` runs the validation in given context and populates `errors`. `save`
would first call `valid?` in the given context and persist the changes if
validations pass. Otherwise populates `errors`.

One thing to note here is that when we validate using an explicit context, Rails
bypasses all other _contexts_ including `:create` and `:update`.

Now that we understand validation context, we can switch our focus to _validate
multiple context together_
[enhancement](https://github.com/rails/rails/pull/21535) in Rails 5.

Let's change our contexts from above example to

```ruby
class MultiStepForm < ActiveRecord::Base
  validate :personal_info, on: :personal_submission
  validate :education, on: :education_submission
  validate :work_experience, on: :work_ex_submission
  validate :final_step, on: :final_submission

  def personal_info
    # code goes here..
  end

  # Smiliary all the validation methods go here.
end
```

For each step, we would want to validate the model with all previous steps and
avoid all future steps. Prior to Rails 5, this can be achieved like this:

```ruby
class MultiStepForm < ActiveRecord::Base
  #...

  def save_personal_info
    self.save if self.valid?(:personal_submission)
  end

  def save_education
    self.save if self.valid?(:personal_submission)
              && self.valid?(:education_submission)
  end

  def save_work_experience
    self.save if self.valid?(:personal_submission)
              && self.valid?(:education_submission)
              && self.valid?(:work_ex_submission)
  end

  # And so on...
end
```

Notice that `valid?` takes only one context at a time. So we have to repeatedly
call `valid?` for each context.

This gets simplified in Rails 5 by enhancing `valid?` and `invalid?` to accept
an array. Our code changes to:

```ruby
class MultiStepForm < ActiveRecord::Base
  #...

  def save_personal_info
    self.save if self.valid?(:personal_submission)
  end

  def save_education
    self.save if self.valid?([:personal_submission,
                              :education_submission])
  end

  def save_work_experience
    self.save if self.valid?([:personal_submission,
                              :education_submission,
                              :work_ex_submission])
  end
end
```

A tad bit cleaner I would say.

## Links

- [Human page](https://www.bigbinary.com/blog/validate-multiple-contexts-in-rails-5)
