We have comments!
Well, I have one comment, which is the one I just added myself, to test the new comment feature on blog.rb. It is actually working, including reCAPTCHA integration. Very nice.
There are definitely more work to do on comments, so feel free to restrain yourself from actually using that feature for the time being…
Let’s look at some code, instead. That’s where the fun is, after all!
Nested resources is a new thing for me. I have been aware of the concept since Rails went the REST way, but I haven’t actually tried it. Turns out, it was really easy…
I wanted to nest the Comment resource as a sub resource to Post. This makes the URLs look like “/posts/7/comments/14”, which is referring to the comment with id 14 belonging to the post with id 7. Fairly intuitive. (Not that it matters in the slightest — I don’t even expect to show the URL for individual comments anywhere, but there you go…)
First step is to define the routing. This was as easy as adding a parameter to the existing route for posts:
map.resources :posts, :has_many => :comments
Next, I want to use the fact that the Post is known for all actions in the controller (since it is implied in the URL). Therefore, I add a “before filter” that gets the Post instance and saves it in an instance variable (@post) for later use in the action methods:
class CommentsController < ApplicationController
before_filter :load_post
# all the action methods here...
protected
def load_post
@post = Post.find(params[:post_id])
end
end
Finally, we must remember to specify the post as well as the comment for all helper methods for comment. For example, instead of a method comment_path(@comment) like we would have for a non-nested resource, we use: post_comment_path(@post, @comment).
One other thing: I wanted to have the whole comments thing ajax based. This is almost trivial in Rails. Form handling consists of using the helper remote_form_for() instead of the conventional form_for()…
Handling the ajax response is more fun! I decided to try RJS since that was another piece of technology I hadn’t used yet. RJS means that we can write simple Javascript code in pure Ruby. I.e. we write Ruby and Javascript is generated.
RJS can be used both on the client side (in views) and on the server (in controllers). When using it in controllers, Javascript is generated as response to a request (most likely an ajax request) and then run on the client.
def create
@comment = Comment.new(params[:comment])
@comment.post = @post
if validate_recap(params, @comment.errors) && @comment.save
render :update do |page|
page[:comment_form].visual_effect :fade, :duration => 0.3
page.insert_html :bottom, "comments", :partial => "comment", :locals => {:comment => @comment}
page.replace_html "comments_count_info", :partial => "posts/comments_count_info", :locals => {:comments => @post.comments}
page[:comments_count_info].visual_effect :highlight, :duration => 4.0
page["comment_#{@comment.id}".to_sym].scroll_to
page["comment_#{@comment.id}".to_sym].visual_effect :highlight, :duration => 4.0
page[:form_hide_link].hide
page[:form_show_link].show
end
else
@comment.errors.add_to_base "Unable to save comment. Please try again later."
render :update do |page|
page.replace_html "form_errors", @comment.errors.full_messages.join("<br/>")
end
end
end
What’s interesting with this technique is that I can update several elements on the page just with a few lines of Ruby code on the server. What I’m not totally convinced about yet is that if it’s a good thing to be able to do or not… It very tempting to trigger visual effects and similar things that feels wrong to do on the server. Not for the MVC purists, so to speak. (As you see in the code above, I’m not a purist. Also, this was my first foray to the RJS world and hopefully it’s more obvious to me how it should be used optimally in the future.)