3

Organization and User have a many-to-many relationship through Relationship. There's a joined signup form. The sign up form works in that valid information is saved while if there's invalid information it rolls back everything.

The problem is that the form does not display the error messages for the nested User object. Errors for Organization are displayed, the form correctly re-renders if there are errors for User, but the errors for User are not displayed.

Why are the errors when submitting invalid information for users not displayed? Any help is appreciated.


The signup form/view:

<%= form_for @organization, url: next_url do |f| %> <%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: f.object.users } %> ... fields for organization... <%= f.fields_for :users do |p| %> ...fields for users... <% end %> <%= f.submit "Register" %> <% end %> 

The shared error messages partial:

<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(object.errors.count, "error") %>. </div> <ul> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <% if defined?(nested_models) && nested_models.any? %> <div id="error_explanation"> <ul> <% nested_models.each do |nested_model| %> <% if nested_model.errors.any? %> <ul> <% nested_model.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> <% end %> <% end %> </ul> </div> <% end %> 

The controller method:

def new @organization = Organization.new @user = @organization.users.build end def create @organization = Organization.new(new_params.except(:users_attributes)) @organization.transaction do if @organization.valid? @organization.save begin @user = @organization.users.create!(users_attributes) @relationship = @organization.relationships.where(user: @user).first @relationship.update_attributes!(member: true, moderator: true) rescue raise ActiveRecord::Rollback end end end if @organization.persisted? if @organization.relationships.where('member = ? ', true).any? @organization.users.where('member = ? ', true).each do |single_user| single_user.send_activation_email end end flash[:success] = "A confirmation email is sent." redirect_to root_url else @user = @organization.users.build(users_attributes) if @organization.users.blank? render :new end end 

The Organization model:

has_many :relationships, dependent: :destroy has_many :users, through: :relationships, inverse_of: :organizations accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true validates_associated :users 

The Relationship model:

belongs_to :organization belongs_to :user 

The User model:

has_many :relationships, dependent: :destroy has_many :organizations, through: :relationships, inverse_of: :users 

Update: If I add an additional line to def create as below, it seems to work, i.e., then it does display the error messages. However, then it for some reason doesn't save when valid information is submitted. Any ideas how to deal with that?

def create @organization = Organization.new(new_params.except(:users_attributes)) @user = @organization.users.new(users_attributes) @organization.transaction do ... 
5
  • Can you please post your shared/error_messages partial? Commented Aug 2, 2015 at 9:59
  • Thanks, I added it to the post. Commented Aug 2, 2015 at 10:02
  • @Marty, from my experience with nested forms, you don't have to display them somehow separately. They supposed to appear along with the rest of the errors of the parent model (the nested models names look ugly though). what kind of validation do you expect to be triggered? If you simply leave the form bank, your nested attributes will be silently rejected, so there would be no errors. Commented Aug 2, 2015 at 20:51
  • I have different kinds of validations on the user model, particularly regarding their length. These should be displayed but aren't. Commented Aug 2, 2015 at 21:01
  • I would suggest you to go through your controller create action with pry, but you may not know how to do it. So, what i can suggest you here is to try to output your errors into the log in different places of your controller action. They might not be there at all and you are expecting them on the view. Commented Aug 2, 2015 at 21:09

5 Answers 5

2
+50

Maybe try this:

 <%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: [ @user ] } %> 

I guess the call to @organization.users.blank? doesn't work in the way you expected it to do, as the user is not correctly created, because #create! threw an exeption. Rails probably does a check on the database, to see if there are any users now, and thinks there is still nothing in there. So your @organization.users.build(users_attributes) gets called, but this doesn't trigger validation.

In general I would also recommend you the use of a form object (like in the other answer), when creating complex forms, as this clarifies things like that and makes the view more clean.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, I will study the use of a form object (refactoring with objects is yet completely new to me but it looks like the proper way to go) but won't be able to in the short-term (building a prototype). I tried the render line as you suggested. Upon submitting the signup form it produces an error: undefined method 'errors' for #<Array:0x007fc9340c1e08> pointing to if nested_model.errors.any?. Substituting [@user] with [@users] and the error is gone, but then the original problem persists: validation errors for users are not displayed.
I guess this happens, because you kind of mix single user instances with multiple users attributes @organization.users.create!(users_attributes). So the create! method may create an array of users. Imho the code is a little messed up because of that.
Just to make sure I understand: I don't have a choice to mix the two or do I? Since it's a many-to-many association I have to use @organization.users. even though I'm just creating a single user. If you have any other ideas how to tweak the code to display the validation errors let me know.
1

This is classic use case for form objects. It is convenient from many perpectives (testing, maintainance ...). For example:

class Forms::Registration extend ActiveModel::Naming include ActiveModel::Conversion include ActiveModel::Validations def persisted? false end def initialize(attributes = {}) %w(name other_attributes).each do |attribute| send("#{attribute}=", attributes[attribute]) end end validates :name, presence: true validate do [user, organization].each do |object| unless object.valid? object.errors.each do |key, values| errors[key] = values end end end end def user @user ||= User.new end def organization @organization ||= Organization.new end def save return false unless valid? if create_objects # after saving business logic else false end end private def create_objects ActiveRecord::Base.transaction do user.save! organization.save! end rescue false end end 

the controller:

class RegistrationsController < ApplicationController def new @registration = Forms::Registration.new end def create @registration = Forms::Registration.new params[:registration] if @registration.save redirect_to root_path else render action: :new end end end 

and the view in HAML:

= form_for @registration, url: registrations_path, as: :registration do |f| = f.error_messages = f.label :name = f.text_field :name = f.submit 

It is worth to read more about form objects.

Comments

0

Nested attributes bit me SOOO hard every time I decided it's a good time to use them, and I see you know a bit of what I'm talking about.

Here's a suggestion of a different approach, use a form object instead of nested attributes: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ see under section 3. Extract Form Objects

You can extract your existing validations on the User model into a module and import that, to expand on the solution from the blog:

https://gist.github.com/bbozo/50f8638787d6eb63aff4

With this approach you can make your controller code super-simple and make simple and fast unit tests of the not-so-simple logic that you implemented inside and save yourself from writing integration tests to test out all different possible scenarios.

Also, you might find out that a bunch of the validations in the user model are actually only within the concern of the signup form and that those validations will come and bite in later complex forms, especially if you're importing data from a legacy application where validations weren't so strict, or when you one day add additional validators and make half of your user records invalid for update.

Comments

0

I had a similar problem. everything seemed to work fine, but I was not getting any errors The solution i found is to build the comment in article#show instead of the view:

@article = Article.find(params[:id]) @comment = @article.comments.build(params[:comment]) 

and in your articles#show don't use @article.comments.build but @comment:

<%= form_for([@article, @comment]) do |f| %> <%= render 'shared/error_messages', :object => f.object %> <p><%= f.submit %></p> <% end %> 

make sure you build the comment in your comment#create as well (you really have no choice though :P)

I think you need to pass f.object instead of @comment.

Comments

0

In case someone might be looking for a solution to render form errors in a form, try:

f.object.errors["account.address"].present?` 

The address is the nested attribute here.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.