skip to content

Unobtrusive Destroy Links in Rails using jQuery

4 min read

Rails has a few helpers that make creating RESTful action links, like a delete link, pretty easy. A delete action has to use a POST request which means it has to use a form or AJAX. To get around this Rails spits out some inline JavaScript that generates the form and submits it for you when you click the link in the browser. This means that the delete link requires JavaScript to work. This is becoming a typical requirement for web apps (possibly propagated by how simple Rails and similar frameworks make it to be obtrusive). However, with minimal effort we can create a more flexible solution that offers more control to the designers and provides an alternative for those without JavaScript.

The Rails

The first thing we need to do on the Rails side is add a delete action to our controller. Actually, we really should write a failing spec or test first but I want to keep things a little more focused.

The delete action is to the destroy action as the new is to create and edit is to update. Here is what the delete method might look like in a Post Controller.

class Post < ApplicationController
    # some filters
    # some actions

    def delete
        @post = Post.find( params[:id] )
        respond_to do |format|
            format.html # delete.html.erb
        end
    end
end

The delete.html.erb should contain a form that contains a button to confirm the deletion or to cancel the deletion. Here is what it might look like.

<h2>Are you sure you want to delete this Post?</h2>
<% form_for @post, :html => { :method => 'delete' } do |f| %>
    <%= submit_tag "Yes" %>
    <%= submit_tag "No", :name => "cancel" %>
<% end %>

In the destroy action you’ll want to check to see if the cancel button was clicked and probably redirect the user somewhere. The destroy action might look something like this.

def destroy
    @post = Post.find( params[:id] )
    redirect_to(@post) and return if params[:cancel]
    @post.destroy
    respond_to do |format|
        format.html { redirect_to posts_path }
    end
end

Next, we’ll need to include this new action in our Routes. It should go in the member hash since this action operates on a single object.

map.resource :post, :member => { :delete => :get }

Finally, in our views we’ll write a link using a class of ‘delete’ so that jQuery can find it easily.

<%= link_to("delete", delete_post_path(@post), :class => "delete") %>

The jQuery

So now that we have a link that works without JavaScript, we can unobtrusively and progressively enhance the user experience by hijacking the link with jQuery.

I recommend using the technique from my blog post jQuery, Rails, and AJAX to handle passing the authenticity token with the request.

There are basically two ways of handling this. We could dynamically create a form and submit it or just use AJAX to make the DELETE request. First, I’ll show you the code required to dynamically create a form and submit it.

jQuery(function($) { // document ready

    // Uses the new live method in jQuery 1.3+
    $('a.delete').live('click', function(event) {

        if ( confirm("Are you sure you want to delete this Post?") )
            $('<form method="post" action="' + this.href.replace('/delete', '') + '" />')
                .append('<input type="hidden" name="_method" value="delete" />')
                .append('<input type="hidden" name="authenticity_token" value="' + AUTH_TOKEN + '" />')
                .appendTo('body')
                .submit();

        return false;
    });
});

The second approach is to use AJAX to make the request.

jQuery(function($) { // document ready

    // Uses the new live method in jQuery 1.3+
    $('a.delete').live('click', function(event) {

        if ( confirm("Are you sure you want to delete this Post?") )
            $.ajax({
                url: this.href.replace('/delete', ''),
                type: 'post',
                dataType: 'script',
                data: { '_method': 'delete' },
                success: function() {
                    // the item has been deleted
                    // might want to remove it from the interface
                    // or redirect or reload by setting window.location
                }
            });

        return false;
    });
});

For the AJAX based approach to work properly you’ll need to add a format.js to the respond_to block in the destroy action. In this case I don’t actually want to send anything back to the client so I’ll use render :nothing => true. The updated destroy action for the AJAX based delete link might look like this.

def destroy
    @post = Post.find( params[:id] )
    redirect_to(@post) and return if params[:cancel]
    @post.destroy
    respond_to do |format|
        format.html { redirect_to posts_path }
        format.js { render :nothing => true }
    end
end

And Then?

There are a number of variations to this approach. For example in cubeless we use a jQuery UI dialog to confirm the delete instead of the JavaScript confirm dialog. Maybe I’ll write about that next! :) You could also do some interesting stuff in the AJAX response once the delete is successful.