Errors can be indexed with nested attributes in Rails 5

Abhishek Jain

By Abhishek Jain

on July 7, 2016

This blog is part of our  Rails 5 series.

We use accepts_nested_attributes_for when we want a single form to cater to multiple models. By using this we can easily provide attributes for associated models.

In Rails 4.x, if a validation fails for one or more of the associated models, then it is not possible to figure out from error message, which of the associated model object is the error related to.

1
2class Product < ApplicationRecord
3has_many :variants
4accepts_nested_attributes_for :variants
5end
6
7class Variant < ApplicationRecord
8validates :display_name, :price, presence: true
9end
10
11> > product = Product.new(name: 'Table')
12> > variant1 = Variant.new(price: 10)
13> > variant2 = Variant.new(display_name: 'Brown')
14> > product.variants = [variant1, variant2]
15> > product.save
16> > => false
17
18> > product.error.messages
19> > => {:"variants.display_name"=>["can't be blank"], :"variants.price"=>["can't be blank"]}
20

In the example above we can see that if this error message is sent as JSON API, we cannot find out which variant save failed because of which attribute.

This works well when we render forms using Active Record models, as errors are available on individual instances. But, the issue arises with an API call, where we don't have access to these instances.

Rails 5 allows indexing of errors on nested attributes

In Rails 5, we can add an index to errors on nested models.

We can add the option index_errors: true to has_many association to enable this behavior on individual association.

1
2class Product < ApplicationRecord
3has_many :variants, index_errors: true
4accepts_nested_attributes_for :variants
5end
6
7class Variant < ApplicationRecord
8validates :display_name, :price, presence: true
9end
10
11> > product = Product.new(name: 'Table')
12> > variant1 = Variant.new(price: 10)
13> > variant2 = Variant.new(display_name: 'Brown')
14> > product.variants = [variant1, variant2]
15> > product.save
16> > => false
17
18> > product.error.messages
19> > => {:"variants[0].display_name"=>["can't be blank"], :"variants[1].price"=>["can't be blank"]}
20

Using global configuration

In order to make this change global, we can set configuration config.active_record.index_nested_attribute_errors = true which is false by default.

1
2config.active_record.index_nested_attribute_errors = true
3
4class Product < ApplicationRecord
5has_many :variants
6accepts_nested_attributes_for :variants
7end
8
9class Variant < ApplicationRecord
10validates :display_name, :price, presence: true
11end
12

This will work exactly same as an example with has_many :variants, index_errors: true in Product.

Stay up to date with our blogs. Sign up for our newsletter.

We write about Ruby on Rails, ReactJS, React Native, remote work,open source, engineering & design.