AJAX with acts_as_commentable

2007 April 8
by mike

I had fun this weekend with AJAX-ifying an implementation of the acts_as_commentable plugin. After reading (and implementing) Dave Naffis’ article on adding AJAX to the acts_as_rateable plugin, I was inspired to do the same with comments.

Well, actually, I was inspired to google the problem to see if anybody else had done it. But imagine my glee when I found out that I could be the first to write about it!

Before I get into this, let me first qualify my skills. I am a complete newbie to RoR. There. That’s out of the way. Now let’s get going.

First of all, follow the links above to install the acts_as_commentable plugin, run your migration and add the acts_as_commentable line to your model. The links above explain it well so I don’t want to repeat their work. If you have any problems with this part then please leave a comment and I’ll help you out as best I can.

Next you need to add some code to your view. In my case, I was displaying an individual map to the user and they were able to make comments on it. Code needed to be added that would display existing comments as well as adding a form to allow for new comments. This is what I added to maps/show.rhtml:

RUBY:
  1. <h5>Comments</h5>
  2. <div id=“comments”>
  3.     <% @map.comments.each do |comment|%>
  4.       <%= render :partial => “comment/comment”, :locals => {:comment => comment } %>           
  5.     <% end %>
  6. </div>
  7. <h5>Leave a comment</h5>
  8. <% remoteformfor :comment, :url=>{:controller=>“comment”, :action=>“new”, :id=>@map.id } do |f| %>
  9.     Title: <%= f.textfield “title” %><br>
  10.     Comment:<br>
  11.     <%= f.textarea “comment”, :rows=>5, :cols=>35 %><br>
  12.     <%= submit_tag “Add Comment”%>
  13. <% end %>

This code simply creates a div named comments that contains (oddly enough) all of the comments for a map. It’s important to use a named div here for the AJAX part that will be coming up. In addition to the named div, there is also a form that uses the AJAXy version of a form called remote_form_for. The remote form posts a comment to the new action of the comment controller. In addition to passing the comment information, @map.id is also passed to ensure that the new comment can be attached to the map that is currently being viewed.

OK…with the view covered, we’re off to see the controller. Here’s the code for comment_controller.rb:

RUBY:
  1. class CommentController <ApplicationController
  2.   def new
  3.     @map = Map.find(params[:id])
  4.    
  5.     @comment = Comment.new(params[:comment])
  6.     @comment.created_at = Time.now
  7.     @map.comments <<@comment
  8.     render :layout=>false
  9.   end
  10. end

To start with, CommentController grabs the @map.id that was passed from the view. Then it creates a new comment, adds a timestamp and assigns the comment to the map. An implicit save is called here. Finally, it ensures that any layouts being used are not rendered. Because this is a remote form using AJAX, Javascript will be replacing the typical HTML return value so the layout isn’t needed.

In Rails, every action in a controller typically has a view. As we just stated, the view in this case isn’t HTML but is instead Javascript. That means that the view is named new.rjs rather than new.rhtml. Here’s the code:

RUBY:
  1. page.inserthtml :bottom, ‘comments’, :partial => ‘comment/comment’, :locals => {:comment => @comment}
  2. page.visualeffect :highlight, ‘comments’, :duration => 3

Pretty basic, eh? This simply adds a new comment to the bottom of the current list of comments and then applies a visual highlight so that the user knows something changed. If you look carefully, you’ll see that the second parameter in each method call is ‘comments’. This is referring to the named div that we talked about earlier.

One other thing to note is that the rjs file doesn’t render out the HTML for the new comment. Instead, it passes that job off to a partial. Through a bit of parsing and a lot of convention, it knows to look for that partial in views/comment/_comment.rhtml. That also happens to be the same partial that the comment list used to render itself in maps/show.rhtml.

That’s basically it. I hit a few stumbling blocks that I’ll talk about in future posts. One of them was the difference in variable names between comment and @comment. I also had a tough time figuring out how to debug something like this so I’ll look into some options in the future (I’ve heard that FireBug might be useful).

This is the most code that I’ve added to one post. Let me know if any of this makes sense. I’m expecting that I’ll have to make some edits to make the process more clear. In the meantime, give this a try. It’s easy!

[Post to Twitter]  [Post to Plurk]  [Post to Yahoo Buzz]  [Post to Delicious]  [Post to Digg]  [Post to Ping.fm]  [Post to Reddit]  [Post to StumbleUpon] 

19 Responses
  1. 2007 August 6

    what is the code for the comments partial?

  2. 2007 August 7
    mike permalink

    @ivan - I didn't add any code for the comments partial because it's pretty self-explanatory. Here it is:

    RUBY:
    1. <span class="dark_header"><%= time_ago_in_words(comment.created_at) %> ago<br />
    2.   <div class="comment"><%= comment.comment %></div><br />

  3. 2007 August 7

    I know, I figured it out a few minutes later. But thanks for the timeagoin_words, that's neat.

  4. 2007 August 13

    Hey, this tutorial helped me a bunch, thanks a lot. I've got a question, though, something I didn't understand.

    Is there a benefit to rendering the comments with a block, rather than a partial with a collection? I'm having trouble posting more than one comment in succession, could that be the trouble?

  5. 2007 August 13
    mike permalink

    @micah - a partial with a collection should work just fine. I just tested it with the following code and it worked OK:

    < %= render :partial => "comment/comment", :collection => @map.comments %>

    Can you post the code that you're having problems with?

  6. 2007 December 21
    Jay permalink

    Mike,

    Thanks for the good tip. I followed exactly the same steps you explained above. However, AJAX insert_html part doesn't work. It does saves the comments ok though. Any guess?

  7. 2007 December 21
    Jay permalink

    def new @query = Query.find( params[:id] ) @comment = Comment.new( params[:comment]) @comment.created_at = Time.now @query.comments false

    render :update do |page|
      page.insert_html :bottom, 'comments', :partial=&gt;'comment', :locals =&gt; {:comment =&gt; @comment}
    end
    

    end

    If I put AJAX part in comment controller, then it works fine. But not when I put in new.rjs... hmm any guess?

  8. 2007 December 21
    Jay permalink

    def new ... render :update do |page| page.insert_html :bottom, 'comments', :partial=>'comment', :locals => {:comment => @comment} end

    end

    If I put AJAX part in comment controller, then it works fine. But not when I put in new.rjs... hmm any guess?

  9. 2008 January 11
    Trey permalink

    Your explanation of the actsascommentable was very useful and worked well. I noticed you mentioned using the actsasrateable, do you have some sample code (much like above) for how to implement it?

    Regards, Marion

  10. 2008 March 14
    Conan permalink

    @Mike, thanks for the great tutorial. It's exactly what I was looking for.

    @Jay (or mike): I'm having the exact same issue you're having. Any solution?

  11. 2008 March 15

    @Trey - I put the link for the actsasrateable article right at the very top of the article. Here it is again: http://www.naffis.com/blog/articles/2006/08/31/rails-ajax-star-rating-system

  12. 2008 March 15

    @Jay & @Conan - I'll take a look at this again. It's been a while since I played with this code and I'm expecting that there might have been some changes since Rails 2.x.

    Speaking of that, what version of Rails are you using with this? If it's a newer version of Rails then you might need to name your file "new.js.rjs" to accommodate the new file naming templates.

  13. 2008 March 18
    Conan permalink

    @mike -- So, I'm kind of embarrassed about this, but the issue was that I hadn't the JavaScript include in the HTML header of the template that used this.

    After I added "javascriptincludetag :defaults" it all worked dandy.

  14. 2008 March 18

    @conan - that's great to hear! Thanks for posting your solution back here.

    And don't be embarassed - you have no idea how many times I've messed up as I put this post together. All of this trial and error adds up to experience.

  15. 2008 April 6
    Michael permalink

    I keep on getting the same error: ActionView::TemplateError (undefined local variable or method `comment' for #) on line #1 of comments/new.rjs: 1: page.inserthtml :bottom, 'comments', :partial => 'comments/comment', :locals => {:comment => comment} 2: page.visualeffect :highlight, 'comments', :duration => 3

    Anyone else with this problem?

  16. 2008 May 9

    I was having same problem as you michael. For some reason the @comment variable was not being sent to rjs. I tried moving new.rjs into my controller, and that seemed to do the trick. My controller looks like this (replace video with whatever your actsascommentable is):

    class CommentController 'comment/comment', :locals => {:comment => @comment } page.visual_effect :highlight, 'comments', :duration => 3 end } end end

    end

  17. 2008 June 21

    Thanks for this! Your post saved me a boatload of time this evening.

    Cheers Chris

  18. 2009 February 6

    This is awesome !!! Good work

Trackbacks and Pingbacks

  1. Latest Bookmarks on Ma.gnolia.com at Ivan Enviroman

Comments are closed.