jbatista
jbatista

Reputation: 1002

How to pass a model when redirecting to another action in order to display the model errors in the redirected view

I want to display the validation errors of a new @comment on the articles\view.html.erb when it fails to persist a comment through the comments#create.

TLDR, I have created a blog using the Getting Started tutorial with rails 4.2.6 and ruby 2.2.3.


A blog has many Articles and each Article has many Comments.

Following the tutorial, the form for posting a new comment is in the articles\view.html.erb and it's persisted in the comments#create. After posting a new comment it redirects to the articles#show again.

class Article < ActiveRecord::Base
  has_many :comments
  validates :title, presence: true, length: { minimum: 5 }
end

class Comment < ActiveRecord::Base
  belongs_to :article
end

I use the same view - articles/show.html.erb - to display an article, its comments and post a new comment

<p><strong>Title:</strong><%= @article.title %></
<p><strong>Text:</strong><%= @article.text %></p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p><strong>Commenter:</strong><%= comment.commenter %></p>
  <p><strong>Comment:</strong><%= comment.body %></p>
<% end %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p><%= f.label :commenter %><br><%= f.text_field :commenter %></p>
  <p><%= f.label :body %><br><%= f.text_area :body %></p>
  <p><%= f.submit %></p>
<% end %>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

through the articles_controller

def show
  @article = Article.find(params[:id])
end

and the comments_controller to post a new comment

def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.create(comment_params)
  redirect_to article_path(@article)
end

It worked as expected :)


Here is where my problems started...

I decided to go further by adding some validation to the Comment model

class Comment < ActiveRecord::Base
  belongs_to :article   
  validates :commenter, presence: true
  validates :body, presence: true
end 

and display the comment errors when it fails to save it.

First, I tried to display the errors trough the article model

<% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>
    <ul><% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %></ul>
  </div>
<% end %>

Then I tried with a global var @comment in articles_controller

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

<% if @comment.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@comment.errors.count, "error") %> prohibited
      this comment from being saved:</h2>
    <ul><% @comment.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %></ul>
  </div>
<% end %>

It didn't work too.

As far as I understand it doesn't work that way since when I submit a new comment I redirect to the article show action redirect_to article_path(@article) - either if it succeeded or not to save the new comment - thus a new comment instance is created, overriding the previous one with its errors.

My best solution was using the flash property with the error messages. It looks like this

comments_controller

def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.create(comments_params)
  redirect_to article_path(@article), flash: {new_article_errors: @comments.errors.full_messages}
end 

articles/show.html.erb

<% unless flash[:new_comment_errors].nil?  %>
  <div id="error_explanation">
    <h2>
      <%= pluralize(flash[:new_comment_errors].count, "error") %> prohibited this comment from begin saved:
    </h2>
    <ul>
      <% flash[:new_comment_errors].each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

But this isn't the best solution too. It displays the errors but loses the values already filled (the form is clear).

Should I need to pass the @comment through the redirect_to? Something like this

comments_controller

def create
  @article = Article.find(params[:article_id])
  @comment = @article.comments.build(comments_params)

  if @comment.save
    redirect_to article_path(@article)
  else 
    redirect_to article_path(:comment => params[:comment] )
  end
end

article_controller

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

  @comment.valid? if params[:comment]
end

Now I'm getting the No route matches error. Should I create a new action that receives a params[:comment]? or editing the show route to receive it?

I already read the Nested model validation - errors don't show but didn't help because they suggested to create the new action in the comments_controller and I don't want that (the new post form needs to be in the article/show.html.erb).

Nested Model Form Part 1 and Nested Model Form Part 2 didn't help too.

Thank you for your time and help!

Upvotes: 1

Views: 162

Answers (1)

SteveTurczyn
SteveTurczyn

Reputation: 36860

Instead of redirecting to the ArticlesController#show method (which as you've discovered will cause all of your instance variables to be lost) you can, instead, render the show view.

if @comment.save
  redirect_to article_path(@article)
else 
  render :template => 'articles/show'
end

You already have @template and @comment instance variables, so they'll be used in the render.

Upvotes: 1

Related Questions