Polymorphic comments, how to render when comment validation is not performed?
I have created an application with multiple models (say A, B) that are politically related to the comment model. When you view the page associated with controller A, show the action, the comments associated with object A are displayed as a form to create a new object. It all works and looks like Ryan Bates' 15 minute blog posted on the rails website. However, if I add a confirmation to prevent the user from submitting an empty comment, I'm not sure how. Here's what I have in my comment controller:
before_filter :load_resources, :only => [:create]
def create
if @comment.save
redirect_to @back
else
render @action
end
end
private
def load_resources
@comment = Comment.new(params[:comment])
case @comment.commentable_type
when 'A'
@a = A.find(params[:a_id]
@comments = @a.comments
@back = a_url(@comment.commentable_id)
@resource = @a
@action = 'as/show'
when 'B'
...
end
end
View partial comment and form (with Haml):
=render :partial => 'comments/comment', :collection => @comments
%h3 Leave a comment:
-form_for [@resource, Comment.new] do |f|
=f.error_messages
=f.hidden_field :commentable_type, :value => params[:controller].singularize.titleize
=f.hidden_field :commentable_id, :value => params[:id]
=f.hidden_field :editor_id, :value => @current_user.id
=f.hidden_field :creator_id, :value => @current_user.id
%fieldset
=f.label :subject, 'Subject', :class => 'block'
=f.text_field :subject, :class => 'block'
=f.label :text, 'Comment', :class => 'block'
=f.text_area :text, :class => 'block'
.clear_thick
=f.submit 'Submit', :id => 'submit'
I can figure out how to deal with validation errors. When validation errors are fired, it doesn't seem to call f.error_messages. Also, when the render is triggered, the user is taken to a page with the following url: a / 2 / comments when I would like to do a / 2.
newest solution:
def create
subject = ""
if !@comment.save
subject = "?subject=#{@comment.subject}"
end
redirect_to @back + subject
end
Then, in controller view A:
if params.has_key?('subject')
@comment = Comment.create(:subject => params[:subject])
else
@comment = Comment.new
end
It works, but it feels ugly ...
source to share
It's hard to wrap your head around yourself because you don't know which object you are going to receive in the comment controller.
It's much easier if it's not a polymorphic relationship. Before we understand how to do this, we need to understand the best way to make a single version.
I should note that this assumes you have your resources / routes defined correctly:
map.resources: posts ,: has_many => [: comments] map.resources: pages ,: has_many => [: comments]
Let's say that we have a lot of comments on a simple Post example. Here's an example of this:
class CommentsController < ApplicationController
before_filter => :fetch_post
def create
@comment = @post.comments.new(params[:comment])
if @comment.save
success_message_here
redirect post_path(@post)
else
error_message_here
redirect_to post_path(@post)
end
end
protected
def fetch_post
@post = Post.find(params[:post_id])
end
end
Now we want to use this in a polymorphic relationship, so we need to set up a few things. Let's say we have Pages and Posts that have comments. Here's an example of this:
From you Pages and Pages Pages Pages:
<%= render 'comments/new' %>
In the message controller:
before_filter :fetch_post
def show
@comment = @commentable.comments.build
end
protected
def fetch_post
@post = @commentable = Post.find(params[:id])
end
This simplifies your form: <% error_messsages_for: comment%>
<% form_for [ @commentable, @comment ] do |f| %>
#Your form fields here (DO NOT include commentable_type and or commentable_id also don't include editor and creator id here either. They will created in the controller.)
<% end %>
In your comments, the controller:
def create
@commentable = find_commentable
# Not sure what the relationship between the base parent and the creator and editor are so I'm going to merge in params in a hacky way
@comment = @commentable.comments.build(params[:comment]).merge({:creator => current_user, :editor => current_user})
if @comment.save
success message here
redirect_to url_for(@commentable)
else
failure message here
render :controller => @commentable.class.downcase.pluralize, :action => :show
end
end
protected
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
source to share