R – Deleting Records with Ajax in Rails


I have a user page where the user can type and submit posts. I'm using Ajax to allow the user to do this from the user/show page. The problem that I'm having occurs when I try to let the user delete posts. When the user clicks the "remove" link, the post is removed from the page. However, when I reload the page, the post is back. It's not getting destroyed.

I have the same code implemented on the user/edit page in a different context, and that works fine because the post record is destroyed when the form is submitted. However, in this situation where there is no form submitted, I'm not sure how to get this working.

Here's my setup:

:user has_many :posts
:post belongs_to :user
:post belongs_to :post_type
:post_type has_many :posts

This is the code for the user to submit the post. This works perfectly.

<% if logged_in? && current_user == @user %>
  <% form_tag(:controller => "posts", :action => "create") do %>
    <div><%= text_area_tag(:message, nil, :size => "27x3") %></div>
    <div><%= submit_tag("Post") %></div>
    <div><%= label_tag(:post_type_id, "Update type?", :class => 'typeLabel') %></div>
    <div><%= collection_select(@posts, :post_type_id, PostType.all, :id, :name) %></div>
  <% end %>
<% end %>

This is the code where the posts are displayed, including the "remove link" at issue:

<% @user.posts.each do |c| %>
  <div class="postS">
    <div><%=h c.message %></div>
    <p><%=h c.post_type.name %></p>
    <p>Posted <%=h time_ago_in_words(c.created_at) %> ago</p>       
    <% if logged_in? && current_user == @user %>
        <%= link_to_function "- Delete Post", "if(confirm('Are you sure you want to permanently delete this post?')) $(this).up('.postS').remove()" %>                          
    <% end %>                   


def destroy
  @post = Post.find(params[:id])
  if @post.destroy
    redirect_to :back
    flash[:notice] = "Post failed to delete."
    redirect_to :back

Let me know if any other information would be helpful. Thanks!

Update 2

I tried using link_to_remote as explained in the answer below. I agree this is the right solution, but can't get it working. I think it's because of a routes issue.

I have posts set up as a user resource like this:

map.resources :users, :member => { :enable => :put } do |users|
  users.resources :posts

When I implement the link_to_remote code provided in the answer, I get a "no method error" exception as follows: undefined methodpost_url' for #`

I tried some tweaks that accounted for this, but none of my attempts worked. Any ideas? Am I right that this is related to my route?


The code from the Rails Guides is not working. I tried many variations as well.

The closest I've gotten is this:

<%= link_to_remote "- Delete Post", :url => { :controller => 'posts', :action => 'destroy', :id => @user.posts, :method => :delete }, :confirm => "Are you sure you want to permanetly delete this post?", :success => "$(this).up('.postS').remove();" %>  

Using this code, I click the delete link and I am prompted to accept the deletion. After I accept, the post is not deleted on the screen. HOWEVER, it is deleted from the database. Then, when I reload the page, the post is in fact deleted from the screen. So this is mostly working, except that the ajax removal action is now not functioning.

But the code seems wrong. Notice that I wrote: :id => @user.posts

This doesn't seem right to me, but it's the only way I got this code to work at all. Look what this does to the log file:
Processing PostsController#destroy (for at 2009-12-17 02:07:05) [POST]

`Parameters: {"action"=>"destroy", "authenticity_token"=>"GZfPHUc2+qPa6Fj5jNogMyhuB4YUO+KlOdkwjM5NSvw=", "id"=>"12/11", "method"=>"delete", "controller"=>"posts"}  `

Notice the id=>"12/11". That's not what I want happening.

When I change the id paramater to :id => @user.post, I get a "no method error." undefined method `post' for #User:0x3ddc20c

When I try :id => user_post_url(@user, @post), I get: "user_post_url failed to generate from [and then it lists all the database column values for this user].

When I try :id => user_posts_url(@user, @posts), the page loads. When I click the delete link I'm prompted to accept. Then nothing happens. When I reload the page, the post is still there and never deleted from the database.

That's every variation that I could think of. Any ideas?

Update 3

This is what my routes look like:

    user_posts GET    /users/:user_id/posts(.:format)          {:controller=>"posts", :action=>"index"}
               POST   /users/:user_id/posts(.:format)          {:controller=>"posts", :action=>"create"}
 new_user_post GET    /users/:user_id/posts/new(.:format)      {:controller=>"posts", :action=>"new"}
edit_user_post GET    /users/:user_id/posts/:id/edit(.:format) {:controller=>"posts", :action=>"edit"}
     user_post GET    /users/:user_id/posts/:id(.:format)      {:controller=>"posts", :action=>"show"}
               PUT    /users/:user_id/posts/:id(.:format)      {:controller=>"posts", :action=>"update"}
               DELETE /users/:user_id/posts/:id(.:format)      {:controller=>"posts", :action=>"destroy"}


Based on the guidance from ScottD below, my code now looks like this:

<%= link_to_remote "- Delete Post", :url => user_post_url(@user,c), :method => :delete, :confirm => "Are you sure you want to permanently delete this post?", :success => "$(this).up('.postS').remove();" %>  

This solves most of my problem, but the javascript remove action is still not getting triggered. In other words, I click the delete link. The post is deleted from the database, but remains on the screen. When I reload the page, the post is gone. So, the problem is that the following part is not getting triggered: :success => "$(this).up('.postS').remove();"

Here's what happens in the log when I click on the delete link as described directly above:

Processing PostsController#destroy (for at 2009-12-17 11:59:18) [DELETE]  
  Parameters: {"action"=>"destroy", "authenticity_token"=>"ccF1QNCGs9IlvbIYWwmQmcbi7kROgQsTZjM1dM687xA=", "_method"=>"delete", "id"=>"14", "controller"=>"posts", "user_id"=>"1"}  
  User Columns (3.2ms)   SHOW FIELDS FROM `users`
  User Load (29.6ms)   SELECT * FROM `users` WHERE (`users`.`id` = 1) ORDER BY name
  Post Columns (8.7ms)   SHOW FIELDS FROM `posts`
  Post Load (1.6ms)   SELECT * FROM `posts` WHERE (`posts`.`id` = 14) 
    app/controllers/posts_controller.rb:22:in `destroy'
  SQL (0.1ms)   BEGIN
    app/controllers/posts_controller.rb:23:in `destroy'
  Post Destroy (0.5ms)   DELETE FROM `posts` WHERE `id` = 14
    app/controllers/posts_controller.rb:23:in `destroy'
  SQL (0.9ms)   COMMIT
    app/controllers/posts_controller.rb:23:in `destroy'
Redirected to http://localhost:3000/users/1
Completed in 65ms (DB: 45) | 302 Found [http://localhost/users/1/posts/14]  

To me, this all looks good. So I'm not sure why the javascript removal is not working.

When I reload the page, the log shows everything loading normally and the post is gone (as it should be). Any ideas on how I can get the javascript removal to work?

Update #5

Here's what's currently generated in the html based on the link_to_remote code that I'm using:

<a href="#" onclick="if (confirm('Are you sure you want to permanently delete this post?')) { new Ajax.Request('http://localhost:3000/users/1/posts/18', {asynchronous:true, evalScripts:true, method:'delete', onComplete:function(request){$(this).up('.postS').remove();}, parameters:'authenticity_token=' + encodeURIComponent('ccF1QNCGs9IlvbIYWwmQmcbi7kROgQsTZjM1dM687xA=')}); }; return false;">- Delete Post</a>  

Here's what Firebug shows in the console when I click the "delete" text on the page:

POST http://localhost:3000/users/1/posts/18  302 Moved Temporarily  
GET  http://localhost:3000/users/1 200 OK

Best Solution

The problem is that you are using link_to_function which only calls a Javascript function, it does not POST anything to your app. If you want to do Ajax try link_to_remote. That will make an AJAX call, and then it lets you call javascript based on callback hooks like :success or :complete.

Example based on your sample:

<% if logged_in? && current_user == @user %>
  <%= link_to_remote "- Delete Post", :url => user_post_url(@user,c, :format => :json), :method => :delete  , :confirm => "Are you sure you want to permanetly delete this post?", :success => "$(this).up('.postS').remove();" %>                                                      
<% end %> 

Check out the Rails API docs for link_to_remote for more examples.

You will also need to update your Post Controller to respond to the delete request from Ajax. I use the new json gem to render the json.

def destroy
  @post = Post.find(params[:id])
  respond_to do |format|
    if @post.destroy
      format.html { redirect_to :back }
      format.json { :success => true }.as_json
      flash[:notice] = "Post failed to delete."
      format.html { redirect_to :back }
      format.json { :success => false }.as_json

Updated to reflect nested routes in question.