2

Using Rails 3.1.3. I have Accounts and Users. One Account can have many Users. I set this up using accepts_nested_attributes_for as described in this answer.

I have a new.html.erb view that accepts data for one account and one user at the same time. (The user's data goes into a "subform.") The form works fine. However if there is an error, the messages for the subform's fields are pluralized, even though they should be singular. For example, I get

Users password doesn't match confirmation
Users password is too short (minimum is 8 characters)

instead of

User password doesn't match confirmation
User password is too short (minimum is 8 characters)

I don't think this is an inflection issue, since "User" follows standard pluralization rules. Rather it must have to do with the use of nested attributes in a subform. In my case, the subform returns an array containing one user, but theoretically it could return data for multiple users.

How/where can I tell Rails not to pluralize when referring to only one element of an array?


Edit 3/14/2012 to show controller and view:

app/controllers/accounts_controller.rb ("New" action only):

class AccountsController < ApplicationController
  before_filter :authenticate_user!, :except => [:new, :create]
  before_filter :user_signed_out,    :only   => [:new, :create]
  load_and_authorize_resource # CanCan - does a standard load for each action, and authorizes user

  def new
    # CanCan:  @account = Account.new (and tests each attribute for ability)
    @account.users.build
    @title = "Sign Up for a New Account"
    @header = "Sign up for a new account"
  end
end

app/views/accounts/new.html.erb:

<h2><%= @header %></h2>

<%= form_for(@account) do |f| %>
  <%= render 'account_fields', :f => f %>

  <%= f.fields_for :users do |user_form| %>
    <div class="field"><%= user_form.label :email %><br />
    <%= user_form.email_field :email %></div>
    <div class="field"><%= user_form.label :password %><br />
    <%= user_form.password_field :password %></div>
    <div class="field"><%= user_form.label :password_confirmation %><br />
    <%= user_form.password_field :password_confirmation %></div>
  <% end %>

  <div class="actions">
    <%= f.submit "Create Account" %>
  </div>
<% end %>

I was trying error_messages_for inside the f.fields_for block above.

app/views/shared/_error_messages.html.erb rendered by layout before all views:

<% if object.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(object.errors.count, "error") %> 
      prohibited this <%= object.class.to_s.underscore.humanize.downcase %>
      from being saved:</h2>
    <p>There were problems with the following fields:</p>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
Community
  • 1
  • 1
Mark Berry
  • 17,843
  • 4
  • 58
  • 88

2 Answers2

1

This is happening because it's a has_many association. What you can do to override the object name that's render is to pass this option like so:

<%= error_messages_for @account, :object => [@account, :users], :object_name => 'Account' %>

This allows you to change that label to whatever you want for example:

<%= error_messages_for @account, :object => [@account, :users], :object_name => 'Profile' %>

Which then would look like

Profile password doesn't match confirmation
Profile password is too short (minimum is 8 characters)

Enjoy!

Marc
  • 2,584
  • 5
  • 33
  • 47
  • Interesting idea, but it seems that f.error_messages was removed from Rails 3 so I wasn't able to test it. I've since added SimpleForm and Twitter Bootstrap, which puts a highlight box around the form field containing the error and adds only the suffix of the error text. I.e. it's a workaround that avoids the issue by not actually naming the object. – Mark Berry Mar 13 '12 at 21:39
  • Nope it's still available. the `f.` is the variable that's passed into your `form_for`. Try my edit above. – Marc Mar 14 '12 at 02:35
  • Okay I reverted my code to make sure I could duplicate the problem. Then I added `<%= error_messages_for @user, :object_name => 'User' %>` to the subform and got `undefined method 'error_messages_for' for #<#:0xa278328>`. This seems to confirm what I saw in several articles, e.g. [No more error_messages_for in Rails 3](http://www.suffix.be/blog/error-messages-for-rails3). Even if it did work, I didn't want to change it to "Profile", I just wanted to get it to do correct pluralization: if there was only one user in the array, don't pluralize to "users". – Mark Berry Mar 14 '12 at 19:26
  • Can I see the full _form.erb.html and your controller? – Marc Mar 15 '12 at 01:22
  • Added to original question. Also added the error_messages partial from which the `object.errors.full_messages` are displayed. – Mark Berry Mar 15 '12 at 03:15
  • Updated answer. It wasn't working because the instance you were calling is `@account` and not `@user`. But now it'll validate both `@account` and the associated `:users` that's in the `fields_for`. Let me know how this goes. This should help though. Also check this out: http://apidock.com/rails/ActionView/Helpers/ActiveRecordHelper/error_messages_for – Marc Mar 15 '12 at 04:31
  • Actually it hits the undefined method _before_ the object, so with your updated code, I still get `undefined method 'error_messages_for' for #<#:0xa3ef3f0>`. As it says at the top of the link you posted, "This method is deprecated or moved on the latest stable version. The last existing version (v2.3.8) is shown here." You see at the top of that page that everything from 3.0.0 onward is gray. Maybe you are using a plugin the recreates error_messages_for, e.g. [Dyanmic Form](https://github.com/rails/dynamic_form)? – Mark Berry Mar 15 '12 at 20:58
  • And it should be getting that error if you don't have it looking at the correct model. I'm not using any plugins that affect `error_messages_for`. It still works across all the versions. – Marc Mar 16 '12 at 01:15
  • Well for whatever reason, `error_messages_for` wasn't working for me, but the suggestion of using a helper method rather than `full_messages` is valid. I marked this as helpful, and outlined additional options in a separate answer. – Mark Berry Mar 20 '12 at 01:41
0

As @Marc suggested, the messages are generated based on the model. This gets tricky when accepts_nested_attributes_for is involved, as it is in my Account model:

has_many :users, :inverse_of => :account, :dependent => :destroy
accepts_nested_attributes_for :users

To get more insight into the error object, I inserted <%= object.errors.inspect %> into _error_messages.html.erb. This showed me that the full error object is, for example:

#<ActiveModel::Errors:0xb5c86a8 @base=#<Account id: nil, name: "", created_at: nil, updated_at: nil>, @messages={:"users.password"=>["doesn't match confirmation", "is too short (minimum is 8 characters)"], :name=>["can't be blank"]}>

When Rails generates the full_messages array, it just feeds each attribute and message into the full_message method. If the attribute is "users.password", for example, then that will simply be "humanized" (without respect to the number of objects in the array) and concatenated with the message(s) to return

Users password doesn't match confirmation
Users password is too short (minimum is 8 characters)

The crux of any solution is to avoid using full_messages:

  • Although I couldn't get error_messages_for to work, it wouldn't be too hard to write a small helper that allows specifying a custom object name to prepend to the message.

  • Avoid the problem of the displaying object name altogether by highlighting the specific field that is in error and placing the error message suffix(es) next to the field. This works nicely with Twitter Bootstrap and SimpleForm.

  • This article make a good case for providing explicit translations for each message, claiming that any attempt to concatenate strings is bound to cause problems.

Mark Berry
  • 17,843
  • 4
  • 58
  • 88