45

Rails 3.0 deprecated f.error_messages and now requires a plugin to work correctly - I however want to learn how to display error messages the (new) native way. I am following the getting started guide, which uses the deprecated method when implementing the comments form. For example:

<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <%= f.error_messages %>
<div class="field">
  <% f.label :commenter  %><br />
  <%= f.text_field :commenter  %>
</div>
<div class="field">
  <%= f.label :body %><br />
  <%= f.text_area :body %>
</div>
<div class="actions">
  <%= f.submit %>
</div>
<% end %>

Here is the correct way to do it (as generated by the scaffold):

<%= form_for(@post) do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
 . . . 

I understand that I use the @post variable in the latter example, but what variable do I reference in the former to get the error messages for comment creation?

Craig Smitham
  • 3,395
  • 4
  • 31
  • 38
  • Your question is contradicting. Here you're asking how to get error messages for comments "in the former" example, which indicates you're asking about the case with `f.error_messages`. (Or did you mean "in the form", not "in the former"?) However, in the comment, you're asking how to get error messages "without using `f.error_messages`". So which one is it? – Max Chernyak Nov 12 '10 at 05:40

7 Answers7

24

The best and clean way to implement error_messages in your form is by implementing error_messages in a FormBuilder.

For example, here is the error_messages method I've implemented for my last project. By implemeting your own FormBuilder you can follow the rules and styles of your webdesigner... Here is an example that will output the errors list in ul/li's with some custom styles :

class StandardBuilder < ActionView::Helpers::FormBuilder
  def error_messages
    return unless object.respond_to?(:errors) && object.errors.any?

    errors_list = ""
    errors_list << @template.content_tag(:span, "There are errors!", :class => "title-error")
    errors_list << object.errors.full_messages.map { |message| @template.content_tag(:li, message) }.join("\n")

    @template.content_tag(:ul, errors_list.html_safe, :class => "error-recap round-border")
  end
end

Then in my forms :

= f.error_messages

And that's all.

Nicolas Blanco
  • 11,164
  • 7
  • 38
  • 49
  • Hi Nicolas, were would I add this FormBuilder? – John Paul Mc Feely Jun 05 '12 at 15:00
  • Still not working for me. I cut-and-pasted the code into a new file, app/helpers/standard_builder.rb, but <%= f.error_messages %> on my form gives the error "undefined method `error_messages' for #". Do I have to do something else to make it available? – digitig Aug 12 '14 at 10:43
  • 1
    Thanks, I found that in order to make this work I had to also do the following in application_helper.rb: ActionView::Base.default_form_builder = StandardBuilder (See http://www.alexreisner.com/code/form-builders-in-rails) – vas Nov 07 '15 at 03:47
13

I'm pretty sure all you'd need to do is reference @post.comments

So you could do something like:

<% @post.comments.each do |comment| %>
    <% if comment.errors.any? %>

    <% comment.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
    <% end %>

    <% end %>
<% end %>

Or just pull all the errors out out:

comment_errors = @post.comments.map(&:errors)

and then loop through them in your display logic to output each of the comment errors.

Patrick Klingemann
  • 8,884
  • 4
  • 44
  • 51
Lukas
  • 3,175
  • 2
  • 25
  • 34
  • 1
    Thanks for the attempt, but no, that isn't working. Perhaps a simpler way to ask the question would be "How do I display comment errors without using "f.error_messages" given "[@post, @post.comments.build]" instead of something simple like @post? – Craig Smitham Oct 06 '10 at 14:47
  • 1
    honestly, I've never seen someone do `form_for([@post, @post.comments.build])`. Rather it's always been `form_for(@post) do |p|` and then `p.fields_for(@post.comments.build) do |c|`. So you may look into using fields for inside of the post form instead? – Lukas Oct 06 '10 at 15:08
  • Well, actually its the way the "Rails - Getting started" guide is doing it. – shadowhorst Nov 24 '14 at 13:55
7

This functionality exists as a standalone gem dynamic_form.

Add the the following to your Gemfile

gem 'dynamic_form'

From the github page:

DynamicForm holds a few helpers method to help you deal with your Rails3 models, they are:

  • input(record, method, options = {})
  • form(record, options = {})
  • error_message_on(object, method, options={})
  • error_messages_for(record, options={})

It also adds f.error_messages and f.error_message_on to your form builders.

Justin Tanner
  • 14,062
  • 17
  • 82
  • 103
3

Here is my solution to the whole error scene.

I created a partial which simply uses a model variable which one would pass when rendering it:

<%# app/views/errors/_error.html.erb %>

<%= content_for :message do %>
  <% if model.errors.any? %>
    <ul>
      <% model.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  <% end %>
<% end %>

You can easily add dynamic html class and/or id names based on the name of the model, as well as generic ones.

I have things setup where my error messages render in all the same place in a layout file:

<%# app/views/layouts/application.html.erb %>

<%= yield :message %>

If one didn't want that functionality, removing the content_for in the partial would do the trick.
Then in really any view you want you can simply write:

<%= render 'errors/error', model: @some_model %>

One could further expand this by creating a partial which takes a collection and leverages the error partial above:

<%# app/views/errors/_collection.html.erb %>

<% collection.each do |model| %>
  <%= render 'errors/error', model: model %>
<% end %>

Render it with:

<%= render 'errors/collection', collection: @some_model.some_has_many_association %>

I like this way. It is simple, easy to manage/maintain, and incredibly tweakable.
I hope this helps!

EDIT: Everything in HAML

-# app/views/errors/_error.html.haml

= content_for :message do
  - if model.errors.any?
    %ul
      - model.errors.full_messages.each do |msg|
        %li= msg


-# app/views/layouts/application.html.haml

= yield :message


= render 'errors/error', model: @some_model


-# app/views/errors/_collection.html.haml

- collection.each do |model|
  = render 'errors/errors', model: @some_model


= render 'errors/_collection', collection: @some_model.some_has_many_association
Benjamin
  • 1,832
  • 1
  • 17
  • 27
  • I am new to this and I found it confusing, if possible can you post the github link for the code? – Am33d May 07 '18 at 14:06
  • @Am33d If I get some free time to write a basic Rails application demonstrating this functionality I will let you know. Did you have a specific question about what I created? Which part is confusing? – Benjamin May 08 '18 at 19:48
1

I guess that the [@post, @post.comments.build] array is just passed to polymorphic_path inside form_for. This generates a sub-resource path for comments (like /posts/1/comments in this case). So it looks like your first example uses comments as sub-resources to posts, right?.

So actually the controller that will be called here is the CommentsController. The reason why Lukas' solution doesn't work for you might be that you actually don't use @post.comments.build inside the controller when creating the comment (it doesn't matter that you use it in the view when calling form_for). The CommentsController#create method should look like this (more or less):

def create
  @post = Post.find(params[:post_id]
  @comment = @post.comments.build(params[:comment])

  if(@comment.save)
    # you would probably redirect to @post
  else
    # you would probably render post#show or wherever you have the form
  end
end

Then you can use the code generated by scaffolding, only replace @post instance variable with @comment in all the lines except form_for call.

I think it may also be a good idea to add the @comment = @post.comment.build to the controller method that displays this form and use form_for([@post, @comment], ...) to keep the form contents displayed in the form if there're errors.

If this doesn't work and you're not able to figure it out, please add your CommentsController#create method to the question.

Tom Morris
  • 3,979
  • 2
  • 25
  • 43
Matt
  • 5,328
  • 2
  • 30
  • 43
0

I just looked into the docrails github issues, and they've decided to remove f.error_messages instead of explaining how to do validation for comments.

bunwich
  • 434
  • 5
  • 12
0

a rather simple implementation can be achieved with

class ActionView::Helpers::FormBuilder
  def error_message(method)
    return unless object.respond_to?(:errors) && object.errors.any?

    @template.content_tag(:div, object.errors.full_messages_for(:"#{method}").first, class: 'error')
  end
end

which allows one to use

 <%= form.label :first_name %>
 <%= form.text_field :first_name %>
 <%= form.error_message :first_name %>

and with the following sass

@import variables

.error
  padding: $margin-s
  margin-left: $size-xl
  color: red

.field_with_errors
  input
    border: 1px red solid

  input:focus
    outline-color: red

it looks like

enter image description here


using simple form gives you quite similiar functionality with more advanced functionality.

For example check out their examples with bootstrap

OuttaSpaceTime
  • 710
  • 10
  • 24