Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repopulating form on failed validation

I have a form for a user to create a question (in additon to user model, there's a question model, with nested answers) on their profile page. It submits from the users profile page /views/users/show.html.erb to the create action of the questions_controller.rb. If it doesn't validate, I think the default for Rails is to render the form(with the invalid information in the form for the user to edit). However, since I'm submitting the form for the question model from the users profile page the prepopulation isn't happening upon failed validation; the user is forced to enter all the information in the form again. Is there a way in this context to get the form on the users show page to fill out with the information that was entered prior to submission?

questions_controller

def create
    @question = current_user.questions.build(params[:kestion])

    if @question.save
      redirect_to current_user, :notice => "Successfully created question."
    else
      ###render  :action => 'show'
      redirect_to current_user

    end
  end

Update I've changed the end of the create method too

Redirect ( : back ), :notice => "something went wrong.try again" 

But I still can't get the form to populate, and the validation error messages aren't showing either, only the flash notice.

Update The show method of the users controller creates the new Question (along with the user)

   def show
        @user = User.find(params[:id])
        @question = Question.new 
        3.times {@question.answers.build}

   end 

The /views/users/show.html.erb

<%= form_for @question do |f| %>
   <% if @question.errors.any? %>
  <h2><%= pluralize(@question.errors.count, "error") %> prohibited this question
    from being saved: </h2>
    <ul>
      <% @question.errors.full_messages.each do |msg| %>
      <li> <%= msg %></li>
    <% end %>
    </ul>   
  <% end %>
  <p>
     <%= f.label :content, "Question"%>
    <%= f.text_area :content, :class => 'span4', :rows => 1 %>   
  </p>
  <p>
    <%= f.label :link, "QuoraLink" %>
    <%= f.text_field :link, :class => 'span4', :rows => 1  %>   
  </p>  
  <%= f.fields_for :answers do |builder| %>
    <p>
      <%= render 'answer_fields', :f => builder %>
    </p>
   <% end %>
   <p><%= link_to_add_fields "Add Answer", f, :answers %></p>
  <p><%= f.submit %></p>
<% end %>

the answer_fields partial rendered from the questions partial

<p class="fields">
<%= f.label :content, "Answer..." %>

  <%= f.text_field :content, :class => 'span3', :rows => 1%>

   <%= f.label :correctanswer, "Correct" %>
  <%= f.check_box :correctanswer, :class => 'span1' %>

   <%= link_to_remove_fields "remove answer", f %>      
</p>
like image 345
Leahcim Avatar asked Feb 28 '13 02:02

Leahcim


2 Answers

Currently, in views/users/show.rb you do

@question = Question.new

that creates an empty new question. Then you populate the forms with this empty model. What you could do instead is:

if session[:question]
  @question = @user.questions.new(session[:question])
  session[:question] = nil
  @question.valid? # run validations to to populate the errors[]
else
  @question = Question.new
end

Now all what's left to do is populating session[:question] in your questions_controller before redirecting to :controller=>"users", :action=>"show". Something like:

if @question.save
  redirect_to current_user, :notice => "Successfully created question."
else
  session[:question] = params[:question]
  redirect_to current_user
end

You may need to work on serialization/deserialization additionally for populating/using session[:question]. I didn't try to compile, so am not sure.

All this is needed because when you do redirect_to your processing of the user request ends, the user browser gets a redirect status code from your server and goes for another page, sending you a new request (which lands on the path, and eventually controller/action, to which you redirected to). So, as soon as you return from the request processing, all your variables are lost. For the next request you start from scratch.

The render :action => "show" approach (that was in the original scaffold and that you commented out) worked because you didn't return back to user but simply rendered the template with a specific name using the variables you already had in place (including @question, on which 'save' was called and failed, and thus internally validations were called and populated the errors object).

Actually, that reminded me that you may want to use another approach. Instead of passing parameters through session[] and redirecting to UsersController, you may want to populate all required variables and just render the view from that controller. Like below:

if @question.save
  redirect_to current_user, :notice => "Successfully created question."
else
  @user = current_user
  render "users/show"
end
like image 93
moonfly Avatar answered Oct 17 '22 08:10

moonfly


Firstly, the reason that using redirect_to instead of render doesn't repopulate the form is that when you redirect_to, the controller logic for the action is run, whereas using render ignored the controller logic.

So when you render :action => 'show' (the "default" behaviour), it renders show.html.erb, with @question set like this:

@question = current_user.questions.build(params[:kestion])

When you redirect_to current_user, it renders show.html.erb with @question set using the code in your show action:

@question = Question.new 
3.times {@question.answers.build}

This is why you get a new (blank) form, instead of a pre-populated one.

Is it really that important that you use redirect_to? If it is, you'll need to get your show method to do the validation. For example, you could rewrite your show method to something like:

def show
  @user = User.find(params[:id])
  if params.has_key?(:kestion)
    @question = @user.questions.build(params[:kestion])
  else
    @question = Question.new 
    3.times {@question.answers.build}
  end
end

and then make your form point at that page, with something like:

<%= form_for(@question, url: users_path(@question.user) do |f| %>
  ...
<% end %>

(depending on how your routes are set up and named). Of course, by that point the whole thing become horribly un-RESTful, a bit of a mess, and definitely not the Rails way of doing things. (The other, worse option would be to redirect back and pass the params through a get query.) In my opinion, you lose a lot for a minor gain, and I'm not sure that I'd really recommend it.

like image 39
zkcro Avatar answered Oct 17 '22 06:10

zkcro