Reputation: 2847
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
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
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