Bazley
Bazley

Reputation: 2847

Rails save failing validations with nested attributes

user.rb

has_many :characters
accepts_nested_attributes_for :characters

after_save self.characters.create

character.rb

belongs_to :user

validates :username, presence: true, uniqueness: true
validates :user, presence: true

seeds.rb

user = User.find_or_initialize_by(email: "[email protected]")
user.characters_attributes = [{ name: "Barry", username: "barry" }]
user.save!

I'm trying to set up my seeds.rb file using find_or_initialize_by so I can run rails db:seed whenever I want to add attributes to records. But running rails db:seed gives this error:

Validation failed: Characters username has already been taken

How do I get validations working through the nested attributes?

Upvotes: 0

Views: 1055

Answers (2)

max
max

Reputation: 102016

The problem is after_save self.characters.create. Not only is it completely unnecessary but it triggers the validation failure since you are creating a user record without any attributes.

To validate the associated records use the validates_associated method:

class User < ApplicationRecord
  has_many :characters
  accepts_nested_attributes_for :characters
  validates_associated :characters
end

This loops through the associated records and adds characters are invalid to the errors on the parent user record unless all the characters are valid.

If you want to display the errors for each character you have to iterate through the errors object on each nested record:

<%= form_with(model: user, local: true) do |form| %>
  # ...
  <fieldset>
    <legend>Characters</legend>
    <%= form.fields_for(:characters) do |cf| %>
      <% cf.object.tap do |character| %>
        <% if character.errors.any? %>
        <div class="errors">
          <h2><%= pluralize(character.errors.count, "error") %> prohibited this character from being saved:</h2>
          <ul>
          <% character.errors.full_messages.each do |msg| %>
            <li><%= msg %></li>
          <% end %>
          </ul>
        </div>
        <% end %>
      <% end %>
      <div class="character">
         <div class="field">
           <%= cf.label :username %>
           <%= cf.text_field :username %>
         </div>
      </div>
    <% end %>
  </fieldset>
  # ...
<% end %>

This can of course be cleaned up by creating a generic partial for the error messages.

Upvotes: 1

Iran R
Iran R

Reputation: 131

Here accepts_nested_attributes_for does what you are trying to achieve having an after save hook. So what happens here is that, when you try to save your user with the character attributes, by default (because you accepts_nested_attributes_for) both objects get saved. Then you also have a after save hook to create your characters, which tries to create the characters again, hence throwing a unique constraint validation error. You wouldn't need your after_save hook here.

Upvotes: 0

Related Questions