Validate form called through another controller - ruby-on-rails

Im writing a blog engine, So I have a post view (show.html.erb) that shows a post, comments list and a comments form.
I call the form this way:
<%= render :partial => 'comments/form', :locals => {:comment => #post.comments.new} %>
Im not sure if passing the comment that way is correct, but at least I get the post_id on the new comment.
This is my form (_form.html.erb on comments view):
<%= form_for comment, :action => "create" do |f| %>
<% if comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(comment.errors.count, "error") %> prohibited this comment from being added:</h2>
<ul>
<% comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :post_id %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :email %>
<%= f.text_field :email %>
</p>
<p>
<%= f.label :url %>
<%= f.text_field :url %>
</p>
<p>
<%= f.label :content %>
<%= f.text_area :content %>
</p>
<p class="no-border">
<%= f.submit "Post", :class => "button" %>
</p>
<% end %>
And this the action:
def create
#comment = Comment.new(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to :back }
else
format.html { redirect_to :back }
end
end
end
A bit verbose but I want to add more stuff.
I can add comments perfectly but I can't view validation errors.
I leave all blank (my model have validation stuff) and I see that the comment created in the create action have the errors and go to the else path.
But... The form doesn't show any errors.
I think that I have an object with the erorrs but when I redirect back, the object I pass to the form is a new one again and doesn't have the errors.
So, where is the problem?
EDIT: Extra stuff:
Im my show.html.erb I also have this (before the form):
<ol class="commentlist">
<%= render #post.comments %>
</ol>
So, When in the show action I put the extra variable:
def show
#post = Post.find(params[:id])
#comment = #post.comments.new
It seems that the render wants to render the empty comment too and make an exception.
How to bypass that?

When you redirect back, you are calling the PostsController#show action again which will reset all of the instance variables. If you wanted to save state after the failed CommentsController#create call, you would need to call render 'posts/show' instead of redirect :back which would reuse the instance variables which were declared in the current action
def create
# assuming you have nested the comments routes underneath posts...
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to :back }
else
format.html do
# remember to declare any instance variables that PostsController#show requires
# e.g. #post = ...
render 'posts/show'
end
end
end
end
You would also need to make sure that the partial uses #comment instead of creating a new comment each time
<%= render :partial => 'comments/form', :locals => {:comment => #comment } %>
And make sure the PostsController declares #comment
# e.g. inside PostsController
def show
#post = Post.find(params[:id])
#comment = Comment.new
end
The most important thing to remember is to make sure that the code inside the failed create call initializes all the instance variables that the PostsController#show action template requires otherwise you'll get errors.

Related

Rails 5: Object's parent ID being lost between the new action and the create action

I'm trying to create a post that will contain it's forum_id that the form resides in. I need both ID to save in the object to achieve that.
I initialize a new #post in the new action using #post = Forum.find(params[:forum_id]).posts.build
Which will spit out unsaved instance of a post containing it the forum_id just as intended.
I then fill out my form here:
<%= form_for #post, :url => {:controller => "posts", :action => "create"} 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 |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %>
<%= f.text_field :title, class: "form-control" %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_area :description, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit class: "btn btn-primary" %>
</div>
<% end %>
And when I click the submit button and inspect post_params with byebug after the line #post = Post.new(post_params) in the create action, only the :title and :description come through. The forum_id is lost in between actions and I cannot the save #post without it. I have :forum_id whitelisted in my post_params but it's not coming through. I would think that if an instance of post is created in the new action with a forum_id that it should persist into the create action inside post_params but something is wrong here. Here is the relevant information that might help with my issue.
My model's relationships:
# User model
has_many :forums
has_many :posts
# Forum model
belongs_to :user
has_many :posts
# Post model
belongs_to :user
belongs_to :forum
# post_controller
def new
#post = Forum.find(params[:forum_id]).posts.build
end
Post Controller
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
...
# Rest of actions
...
def post_params
params.require(:post).permit(:title, :description, :forum_id, :user_id)
end
end
Form doesn't submit forum_id because it doesn't exist on there
I think you need to add this to that form
<%= f.hidden_field :forum_id %>

Form not displaying error messages for nested resource

I am having trouble getting error messages to display when a user tries to submit a blank comment. Upon create failure it render 'posts/show' properly but it doesnt appear to be sending the #comment object that my error_messages partial expects. Any thoughts?
comments_controller.rb:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
if #comment.save
flash[:success] = "Comment created!"
redirect_to post_path(#post)
else
#comments = #post.comments.paginate(page: params[:page], :per_page => 5)
render 'posts/show' # => Renders but does not display errors???
end
end
posts_controller.rb:
def show
#post = Post.find(params[:id])
#comments = #post.comments.paginate(page: params[:page], :per_page => 5)
#comment = #post.comments.build if signed_in?
end
_comment_form.html.erb
<%= form_for([#post, #post.comments.build], :html => { :role => "form" }) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="form-group">
<%= f.label :content %>
<%= f.text_area :content, class: "form-control", placeholder: "Enter new comment..." %>
</div>
<div class="btn-group">
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
</div>
<% end %>
<%= form_for([#post, #post.comments.build], :html => { :role => "form" }) do |f| %>
This always creates a form for blank comment object. So After create action, it does the same and that's why you are not getting any errors.
As you already done the initialization for the #comment in your create action, so you can use that in the form to get the error message for that #comment object. And I believe you have initialized the #comment object in your show action as well to work the partial form for both the show and create action.
So try using the following
<%= form_for([#post, #comment], :html => { :role => "form" }) do |f| %>

comments validation errors not shown in posts view

I am working on basic blog engine and i have applied validations on comments but when i do a submit it doesn't show errors, instead it shows ActiveRecord::RecordInvalid by rails which is default.
my comments controller is
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create!(params[:comment])
redirect_to #post
end
my posts/show view is as below which is working fine for commenting
<%= form_for [#post, Comment.new] do |f| %>
<p class="comment-notes">Your email address will not be published. Required fields are marked <span class="required">*</span></p>
<p>
<b><%= f.label :name, "Name * " %></b><%= f.text_field :name %><br /></p>
<p>
<b><%= f.label :body, "Comment" %></b><%= f.text_area :comment, :cols => 60, :rows => 5 %>
</p>
<p>
<%= f.submit "Post Comment" %>
</p>
can anybody help me to show validation errors on the same posts/show view?
thanks in advance
replace
#comment = #post.comments.create!(params[:comment])
redirect_to #post
with
#comment = #post.comments.create(params[:comment])
if #comment.errors.any?
render "posts/show"
else
redirect_to #post
end
unlike create, create! will raise error if validations fail
in posts/show
<%= form_for [#post, Comment.new] do |f| %>
<% if #comment && #comment.errors.any? %>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
...
Try this:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
if #post.save
redirect_to #post
else
flash[:error] = "Correct errors"
end
end
In Post model:
accepts_nested_attributes_for :comments
or
If you don't want to make as nested model:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(params[:comment])
if #comment.save
redirect_to #post
else
flash[:error] = "Correct errors"
end
end

Rails: form_for doesn't turn parameters

I'm basically trying to create a Rails form_for but it simply does not turn parameters. I've checked the server log and it receives the parameter but cannot capture on the create function. I'm watching andrewperk's tutorial (http://www.youtube.com/watch?v=unq1yubL6lQ) about create and save methods. What could I possible done wrong?. Here is my Users controller;
def new
#user=User.new
end
def create
#user=User.new(params[:user])
if #user.blank?
render :new
else
#user.save
render :index
end
end
And here is my form;
<h1>Add an available user</h1>
<%= form_for #user do |a| %>
<p>
<%= a.label :name %>
<%= a.text_field :name %>
</p>
<p>
<%= a.label :surname %>
<%= a.text_field :surname %>
</p>
<p>
<%= a.submit "Add new available user"%>
</p>
<% end %>
You should use params[:user] in your controller, because this is how the HTML form fields are named by default (you can check page source).
Also I'd suggest refactoring the create method a bit. It is a convention that you redirect to index page instead of just rendering it after creating a record.
def create
#user = User.new(params[:user])
if #user.save
redirect_to users_path
else
render :new
end
end

How do I display error messages for nested resource validations?

I'm creating a basic blog application and I'm running into issues displaying error messages when a user tries to submit a blank comment. Instead of getting a nice looking error message, an active record error message with the correct validation erorrs. Such as
ActiveRecord::RecordInvalid in CommentsController#create
Validation failed: Name can't be blank, Email can't be blank
In my article/show view I have the following code:
<%= form_for([#article, #article.comments.build]) do |f| %>
<%= render "shared/error_messages", :target => #article %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<p><%= f.submit %></p>
<% end %>
My error messages partial looks like this:
<% if target.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(target.errors.count, "error") %> prohibited this record from being saved:</h2>
<ul>
<% target.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
I know the answer is simple but I can't figure it out.
Create action in comments controller:
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#article, :notice => 'Comment was successfully created.') }
format.xml { render :xml => #article, :status => :created, :location => #article }
else
format.html { render :action => "articles/show" }
format.xml { render :xml => #comment.errors, :status => :unprocessable_entity }
end
end
I had a similar problem. everything seemed to work fine, but I was not getting any errors
The solution i found is to build the comment in article#show instead of the view:
#article = Article.find(params[:id])
#comment = #article.comments.build(params[:comment])
and in your articles#show don't use #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 build the comment in your comment#create as well (you really have no choice though :P)
also, I don't know if it matters (i'm pretty new to ruby), but I think you need to pass f.object instead of #comment.
I think you're saying that you get the big grey-on-white error page, right?
Check the backtrace, but I suspect that this is coming from the create action in the controller, not the view.
If your controller uses save! with the ! at the end, that means that it will raise an error if the record is invalid. save, on the other hand, returns true or false and lets you use simple branch logic to decide how to react.
If my hunch on save! isn't right, though, please post controller code so we can dig deeper :) Thanks!
The solution was to direct the comments created action back to the correct controller/action and target #comment in my error message partial.
Final view
<%= form_for([#article, #article.comments.build]) do |f| %>
<%= render "shared/error_messages", :target => #comment %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :body %><br />
<%= f.text_area :body %>
</div>
<p><%= f.submit %></p>
<% end %>
Final create action in comments controller
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to(#article, :notice => 'Comment was successfully created.') }
format.xml { render :xml => #article, :status => :created, :location => #article }
else
format.html { render :action => "articles/show" }
format.xml { render :xml => #comment.errors, :status => :unprocessable_entity }
end
end

Resources