Nested registration form does not display error messages for nested object

Organization

and User

are related many-to-many

through Relationship

. There is a combined registration form. The registration form works in that the valid information is saved, and if there is invalid information, it rolls back everything.

The problem is that the form is not displaying error messages for the nested object User

. Errors are displayed for Organization

, the form overrides correctly if there are errors for User

, but errors for are User

not displayed.

Why are there no errors when submitting incorrect information for users

? Any help is appreciated.


Registration form / type:

<%= 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 %>

      

Common error messages:

<% 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 %>

      

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

      

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

      

Relationship model:

belongs_to :organization
belongs_to :user

      

User Model:

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

      

Update: . If you add an extra line in def create

like below, it works, that is, it displays error messages. However, for some reason, it does not keep accurate information when sending. Any ideas how to deal with this?

def create
  @organization = Organization.new(new_params.except(:users_attributes))
  @user = @organization.users.new(users_attributes)
  @organization.transaction do
    ...

      

+3


source to share


4 answers


Maybe try this:

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

      



My guess is that the call @organization.users.blank?

does not work as you expected because the user is not created correctly, because #create! threw exemption. Rails is probably doing a check on the database to see if there are currently users and thinks there is nothing there. This way your call gets @organization.users.build(users_attributes)

called, but that doesn't trigger validation.

In general, I would also recommend using a form object (like in another answer) when creating complex forms, as it makes things like this clearer and makes the view cleaner.

+2


source


This is the classic use case for form objects. It is convenient from many points of view (testing, maintenance ...). 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

      

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 representation 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 reading more about form objects .

+1


source


The nested attributes bit me SOOO hard every time I decide this is the right time to use them and I see you know a little about what I'm talking about.

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

You can extract the existing validations in the model User

into a module and import this to deploy 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 less-than-simple logic that you inject internally, and save yourself the hassle of writing integration tests to test all possible scenarios.

Also, you might find that a bunch of validations in the custom model are actually only within the registration form and that these checks will come and bite into more complex forms, especially if you are importing data from a legacy application where the checks weren't that strict. , or when you one day add additional validators and don't do half of your custom entries to update.

0


source


I had a similar problem. everything seemed to work fine, but I didn't get any errors. The solution I found was to create a comment in the # show article instead of a view:

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

      

and in your articles do not use # show @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 create a comment in your # create comment (also you really have no choice: P)

I think you need to pass f.object

instead @comment

.

0


source







All Articles