views:

79

answers:

2

Hello I want to write a small blog with Ruby on Rails (3), with posts and comments submitted via a ajax form.

But when I submit a comment it is often shown twice, and I got no idea why. when I write @post.comments.uniq in the _create.js.rjs file, it works fine but this seems not to be a clean solution. When I reload the page without ajax after inserting a comment the comment is also not shown twice. Only when I insert it via ajax.

Here is the sourcecode of my project.

Blog::Application.routes.draw do
  root :to => 'posts#index'
  resources :posts do  
    resources :comments  
  end
end

config/routes.rb

ActiveRecord::Schema.define(:version => 20100907105618) do

  create_table "comments", :force => true do |t|
    t.text     "text"
    t.integer  "post_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "posts", :force => true do |t|
    t.string   "title"
    t.text     "text"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end

db/schema.rb

class Comment < ActiveRecord::Base
  belongs_to :post
  default_scope :order => "id DESC"
end

app/models/comment.rb

class Post < ActiveRecord::Base
  has_many :comments
end

app/models/post.rb

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
  def show
    @post = Post.find(params[:id])
  end
end

*app/controllers/posts_controller.rb*

class CommentsController < ApplicationController
  respond_to :js
  def create
    @post = Post.find(params[:post_id])
    # if I write here p @post.comments.inspect
    # it shows that there where 2 comments with the same id, how could this be?
    @post.comments.create(params[:comment])
  end
end

*app/controllers/comments_controller.rb*

<h2><%= @post.title %></h2>

<p>
  <%= @post.text %>
</p>

<%= form_for [@post, Comment.new], :remote => true do  |f| %>
  <%= f.text_area :text, :rows => 4 %><br />
  <%= f.submit "send" %>
<% end %>

<div id="comments_box">
  <% if @post.comments.any? %>
    <%= render :partial => @post.comments %>
  <% else %>
    No Comments yet
  <% end %>
</div>

app/views/posts/show.html.erb

<div id="comment_<%= comment.id %>"><%= comment.text %></div>

*app/views/comments/_comment.html.erb*

page[:comment_text].clear
page[:comments_box].replace_html :partial => @post.comments  
                                               # ^ write here @post.comments.uniq it works
page.visual_effect(:highlight, "comment_#{@post.comments.first.id}")

app/views/comments/create.js.rjs

<% @posts.each do |post| %>
  <%= link_to post.title, post %>
<% end %>

app/views/posts/index.html.erb

EDIT:

<!DOCTYPE html>
<html>
<head>
  <title>Blog</title>
  <%= stylesheet_link_tag :all %>
  <%= javascript_include_tag :defaults %>
  <%= csrf_meta_tag %>
</head>
<body>

<%= yield %>

app/views/layouts/application.html.erb

A: 

Hi, I'm still not a profi in rails but you can check which js you link to your application layout file. I used to link once defaults and after my application.js and received all the ajax action twice. However I'm not sure about your case. The code you pasted looks fine.

Dennis
Thanks for your answer. I added the code of the layout file in the question above.
ipsum
A: 

I believe what's happening here is...

when you call

@post.comments.create(params[:comment])

Rails appends a new comment to the post. Then, when calling

:partial => @post.comments

Rails will call all of the comments from the DB that belong to this post.

We can see this in the log:

SQL (0.5ms)  INSERT INTO "comments" ("created_at", "post_id", "text", "updated_at") VALUES ('2010-09-09 11:10:18.874471', 1, 'lots of pies', '2010-09-09 11:10:18.874471')
Comment Load (0.9ms)  SELECT "comments".* FROM "comments" WHERE ("comments".post_id = 1) ORDER BY id DESC

Instead, try creating a new comment and then saving like so:

class CommentsController < ApplicationController
  respond_to :js
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.new(params[:comment])
    @comment.save
  end
end

And in the view:

page[:comment_text].clear
page[:comments_box].replace_html render(@post.comments)
page.visual_effect(:highlight, "comment_#{@post.comments.first.id}")

I've posted this as an example to Github http://github.com/GavinM/Comments-Demo

Gavin Morrice
I thought the only difference between "new" and "create" is that "create" directly saves the object, and I don't need to call the "save" method explicit.
ipsum