---
title: accepts_nested_attributes_for with Has-Many-Through Relations
teaser:
tags: web,rails
author: Pat Brisbin
published_on: 2013-06-14
---

If you find yourself getting validation errors when using `accepts_nested_attributes_for` with has-many-through relations, the answer may be to add an `inverse_of` option.

The `inverse_of` option allows you to tell Rails when two model relations describe the same relationship, but from opposite directions. For example, if a `User has_many :posts` and a `Post belongs_to :user`, you can tell Rails that the `:user` relation on `Post` is the inverse of the `:posts` relation on `User`.

This option is usually not required, but there are cases where it matters. One such case is when using `accepts_nested_attributes_for` with a has-many-through relation. This will eventually lead to a `collection=` assignment which is only possible if Rails knows that one relation is the inverse of another.

## Example

In our case, we had the following three models:

    class Notice < ActiveRecord::Base

      # attribute :title

      has_many :entity_roles
      has_many :entities, through: :entity_roles

      accepts_nested_attributes_for :entity_roles

    end

    class EntityRole < ActiveRecord::Base

      # attribute :name

      belongs_to :entity
      belongs_to :notice

      validates_presence_of :entity
      validates_presence_of :notice

      accepts_nested_attributes_for :entity

    end

    class Entity < ActiveRecord::Base

      # attribute :name
      # attribute :address

      has_many :entity_roles
      has_many :notices, through: :entity_roles

    end

We wanted the form to create two related entities for the notice, each of a specific role.

The controller looks like this:

    class NoticesController < ApplicationController

      def new
        @notice = Notice.new
        @notice.entity_roles.build(name: 'submitter').build_entity
        @notice.entity_roles.build(name: 'recipient').build_entity
      end

    end

Using Simple Form, the view looks like this:

    <%= simple_form_for(@notice) do |form| %>
      <%= form.input :title %>

      <%= form.simple_fields_for(:entity_roles) do |roles_form| %>
        <% role = roles_form.object.name.titleize %>
        <%= roles_form.input :name, as: :hidden %>
        <%= roles_form.simple_fields_for(:entity) do |entity_form| %>
          <%= entity_form.input :name, label: "#{role} Name" %>
          <%= entity_form.input :address, label: "#{role} Address" %>
        <% end %>
      <% end %>

      <%= form.submit "Submit" %>
    <% end %>

The only clever bit here is that we use each role's name to
intelligently affect the entity form's labels each time it's rendered.
Aside from that, it's pretty standard `accepts_nested_attributes` stuff.

On POST, we found validation errors on the `entity_role` objects:

    ["notice", "can't be blank"]

We were confused.

The controller's `#create` action is effectively doing this:

    notice = Notice.new(
      title: "...",
      entity_roles_attributes: [
        { name: "submitter", entity_attributes: { ... } },
        { name: "recipient", entity_attributes: { ... } }
      ]
    )

    notice.save

Which, as far as we knew, should work.

It seemed Rails was not setting the `notice` attribute on the
`EntityRole` before attempting to save it, triggering the validation
errors. This is a bit surprising as other `has_many` relations (omitted in this blog post) should have the same save mechanics and were working just fine.

In an act of experimentation, we added `inverse_of`:

    class Notice < ActiveRecordBase

      has_many :entity_roles, inverse_of: :notice

    end

And suddenly, it all worked.

Only after the fact, when we knew to include "inverse_of" in our search queries, did we find some information on this issue. You can read the details [here][here1], [here][here2], and [here][here3] if you're interested.

[here1]: https://github.com/rails/rails/issues/6161#issuecomment-8615049
[here2]: https://github.com/rails/rails/pull/7661#issuecomment-8614206
[here3]: https://github.com/rails/rails/issues/6161#issuecomment-6330795

When you use `collection=` assignment with a has-many-through (as `accepts_nested_attributes_for` does), you have to specify `inverse_of` for Rails to save everything correctly.
