link_to destroy action

11 views
Skip to first unread message

justin....@gmail.com

unread,
Jan 7, 2009, 11:39:21 PM1/7/09
to merb
I seem to have run into an odd issue. I am trying to create a simple
link_to "delete", resource(@snippet), :method => 'delete' and it does
not seem to work. It renders:

<a method="delete" href="/snippets/5">delete</a>

I am wondering how I can make this happen without using the
delete_button helper...

Roy Wright

unread,
Jan 7, 2009, 11:56:14 PM1/7/09
to me...@googlegroups.com

Try:

link_to('delete', resource(obj, :delete)

Here's a good reference:

http://wiki.merbivore.com/development/resource_urls

HTH,
Roy

Justin Smestad

unread,
Jan 8, 2009, 12:31:29 AM1/8/09
to me...@googlegroups.com
Does not work, it links to the 'delete action' which does not exist,
it needs to go to the destroy action.

Justin

Matt Aimonetti

unread,
Jan 8, 2009, 12:34:40 AM1/8/09
to me...@googlegroups.com
Roy, if you look at the example I put in the wiki:
resource(@article, :delete) # GET => delete

A GET request to the delete action won't destroy the object, it should display a warning screen confirming that the user really wanted to destroy the object.

What justin is trying to do isn't possible in pure HTML. Let me explain quickly.

A restful destroy action on a resource needs to be called using the DELETE http verb/method. Browsers don't support all the http verbs such as DELETE and UPDATE. Instead, merb cheats by doing a post and "stuff" the method to us in a parameter.

The same way you cannot POST with a link, you can't DELETE with a link and that's why we have the delete_button helper which creates a form for you and send call the destroy action by using the DELETE http verb in a request made to the resource.

So how does Rails do it?

Rails uses a JS trick. You can also do the same thing using jQuery or Prototype. Here is an example extracted from the http://merbclass.com material:

view:

<%= link_to "[delete]", resource(article, :delete), :class => "delete", :rel => resource(article) %>

The code above would usually send the user to the delete action which has a confirmation form which calls the destroy action.
We can Ajaxify/enhance the process by adding the following snippet of JS:

jQuery(function($) {
  $("a.delete").click(function() {
    // jQuery idiom that allows use of 'this' inside the callback
    var self = $(this);
    
    if(confirm("Are you sure you wish to delete this article")) {
      $.post($(this).attr("rel"), {"_method": "delete"}, function(json) {
        self.parents("li:first").remove();
        $("div.notice").html(json.notice);
      }, "json");
    }
    return false;
  });
});

As you can see, we are using the rel attibute to get the url of the resource and we are "post"ing a request with a fake method ("_method") set to "delete". The controller returns a notice that we then display in the view.

If you want to learn more about this kind of advanced technics, you can join us in the merb/rails3 training sessions: http://merbcamp.com ;)

I hope this helps.

- Matt

Justin Smestad

unread,
Jan 8, 2009, 12:52:56 AM1/8/09
to merb
so is the preferred approach than to rename all destroy methods, to
delete methods?
> On Wed, Jan 7, 2009 at 8:56 PM, Roy Wright <roywrig...@gmail.com> wrote:

Matt Aimonetti

unread,
Jan 8, 2009, 1:14:58 AM1/8/09
to me...@googlegroups.com
so is the preferred approach than to rename all destroy methods, to
delete methods?

noooooo!!!!  oh boy, doing that would send you to merb/web hell right away!

I gave you the solution in my previous email, you should NOT have a link (GET request) to a modifying action, you need to use buttons/form using a POST request..
You can certainly put your destroy code in your delete method but you and I won't be friend anymore ;)

Just use the delete_button helper or follow the unobtrusive javascript method explained in my previous email.

- Matt

Roy Wright

unread,
Jan 8, 2009, 1:09:09 AM1/8/09
to me...@googlegroups.com

On Jan 7, 2009, at 11:34 PM, Matt Aimonetti wrote:

> Roy, if you look at the example I put in the wiki:
> resource(@article, :delete) # GET => delete
>
>
> A GET request to the delete action won't destroy the object, it
> should display a warning screen confirming that the user really
> wanted to destroy the object.
>
> What justin is trying to do isn't possible in pure HTML. Let me
> explain quickly.
>
> A restful destroy action on a resource needs to be called using the
> DELETE http verb/method. Browsers don't support all the http verbs
> such as DELETE and UPDATE. Instead, merb cheats by doing a post and
> "stuff" the method to us in a parameter.
>
> The same way you cannot POST with a link, you can't DELETE with a
> link and that's why we have the delete_button helper which creates a
> form for you and send call the destroy action by using the DELETE
> http verb in a request made to the resource.

OK, I actually cheat a little in the controller and have the delete
call the destroy method. I really didn't want the "are you sure"
prompt, so this seems to work fine from a link_to('delete',
resource(obj, :delete))

def destroy
if get_resource.destroy
redirect resource(controller_name.to_sym)
else
raise InternalServerError
end
end

def delete(id)
destroy
end

Have fun,
Roy

Matt Aimonetti

unread,
Jan 8, 2009, 1:19:43 AM1/8/09
to me...@googlegroups.com
Yikes Roy... you just got yourself a first class ticket to Merb/Web hell :p

You are making a GET request on an action that should receive a POST request. You basically went around a protection set in the framework to help you do the "right" thing.

It's not a huge deal but it's definitely not the right thing to do.

- Matt

Michael D. Ivey

unread,
Jan 8, 2009, 1:22:01 AM1/8/09
to me...@googlegroups.com
On Jan 8, 2009, at 12:09 AM, Roy Wright wrote:
> OK, I actually cheat a little in the controller and have the delete
> call the destroy method. I really didn't want the "are you sure"
> prompt, so this seems to work fine from a link_to('delete',
> resource(obj, :delete))

Yeah, this is pretty evil. Not only is it wrong, in the sense that GET
requests aren't supposed to modify anything, it opens you up to some
pretty nasty user experience bugs.

You should listen to Matt. Seriously.

Yehuda Katz

unread,
Jan 8, 2009, 1:55:36 AM1/8/09
to me...@googlegroups.com
Just to clarify... this is not just a "you should do it this way because it's prettier" issue. The infrastructure of the web, including caches and proxies, assume (as per the HTTP spec), that GET requests do not make modifications and can therefore be cached. Making your application compliant with the web is more than a "good idea", it's basically mandatory if you want to be sure it actually works behind company firewalls, combinations of end-user routers, mobile phones that use caching tricks to save bandwidth, etc.

-- Yehuda
--
Yehuda Katz
Developer | Engine Yard
(ph) 718.877.1325

Justin Smestad

unread,
Jan 8, 2009, 1:57:54 AM1/8/09
to me...@googlegroups.com
Yehuda, than are you in support of Matt's implementation or something else?

Justin

Yehuda Katz

unread,
Jan 8, 2009, 2:02:42 AM1/8/09
to me...@googlegroups.com
I actually wrote that implementation for MerbClass with Matt, specifically because the delete action will not be called when JavaScript is turned on (i.e. the vast majority of cases). I feel that it's cleaner to have a confirmation page for the few cases where JS is turned off than to just link directly to the destroy page (esp. because JavaScript being off is likely to be compounded in this case by a company firewall or mobile device, so following web assumptions becomes crucial).

From the perspective of the vast majority of your users, the experience is identical to a link directly to the destroy action, but it uses a POST action in the JavaScript so the web's infrastructure is happy.

-- Yehuda

Jim Freeze

unread,
Jan 8, 2009, 11:04:03 AM1/8/09
to me...@googlegroups.com
On Thu, Jan 8, 2009 at 12:22 AM, Michael D. Ivey <michae...@gmail.com> wrote:
>
> Yeah, this is pretty evil. Not only is it wrong, in the sense that GET
> requests aren't supposed to modify anything, it opens you up to some
> pretty nasty user experience bugs.

I think specifically the problem here (and the real reason this should
not be done) is that a crawler (using get requests) can actually
delete an object.
To me, this is the real danger and the real issue and the reason for
avoiding this type of web coding. Not because "it's evil", or "it will
make the web unhappy" or "Matt won't be my friend". ;-)

Jim

>
> You should listen to Matt. Seriously.

--
Jim Freeze

Michael D. Ivey

unread,
Jan 8, 2009, 11:17:13 AM1/8/09
to me...@googlegroups.com
On Jan 8, 2009, at 10:04 AM, Jim Freeze wrote:
> I think specifically the problem here (and the real reason this should
> not be done) is that a crawler (using get requests) can actually
> delete an object.
> To me, this is the real danger and the real issue and the reason for
> avoiding this type of web coding. Not because "it's evil", or "it will
> make the web unhappy" or "Matt won't be my friend". ;-)

That's why it's evil and that's why it makes the web unhappy. They're
the same thing.

Yehuda Katz

unread,
Jan 8, 2009, 12:10:22 PM1/8/09
to me...@googlegroups.com
The reason I don't use the crawler as an example is that people then start to rationalize "well it's an internal app only..."

But corporate firewalls, airports, mobile devices, etc. are fairly common, and can be in front of even internal applications. It's not about "making the web happy", it's about following the same rules that the rest of the web's infrastructure uses.

-- Yehuda

Nicolas Cavigneaux

unread,
Jan 8, 2009, 12:09:55 PM1/8/09
to me...@googlegroups.com
Jim used another way to tell it but I think it's more explonatory this
way.

Crawler and "WebAccelerators" are evil!

Le 8 janv. 09 à 17:17, Michael D. Ivey a écrit :

--
Nicolas Cavigneaux
http://www.bounga.org
http://www.cavigneaux.net

Matt Aimonetti

unread,
Jan 8, 2009, 1:00:23 PM1/8/09
to me...@googlegroups.com
I agree with Yehuda, the reason why I didn't mention the crawlers is because you might protect your link and only show it to admins for instance.

Btw, me not being your friend anymore should be a compelling enough reason to do things right ;)

- Matt

p.s: what if I were telling you every time you make a GET request on a modifying action, a little baby seal dies in Antarctica, would that help?

Ted Han

unread,
Jan 8, 2009, 1:24:53 PM1/8/09
to me...@googlegroups.com
Baby seals are unsympathetic anyway. But everybody loves polar bears
(and they eat baby seals too!).

Seriously object destruction via GET is no fun to mess around with.
And Dr Freeze's description should read "a crawler *will* delete your
objects". It's worth it to read up about REST and why it's structured
the way it is. Merb isn't opinionated about how you want to build
your stuff, but it is opinionated about respect for standards
adherence (hopefully not blindly so [Yeh, gerrof my case, Zed!]).
anyway check out rtomayko's post about it, it's a good place to start:
http://tomayko.com/writings/rest-to-my-wife

here's another good explanation of why post vs get matters:
http://www.cs.tut.fi/~jkorpela/forms/methods.html#why

Really, it's bad mojo. (your users will hate you, it'll make the
universe divide by 0, and google will eat your babies) Don't do it!

-T

Ted Han

unread,
Jan 8, 2009, 1:35:29 PM1/8/09
to me...@googlegroups.com
This link is perhaps a bit more authoritative!

http://www.w3.org/2001/tag/doc/whenToUseGet.html#checklist

oh yeah, also: Posting in Epic Thread! :)

Matt Aimonetti

unread,
Jan 8, 2009, 1:43:40 PM1/8/09
to me...@googlegroups.com
Good resources Ted, and yes you are probably right, people might not care about baby seals.

Here is the gist from the w3 site:

  • Use GET if:
    • The interaction is more like a question (i.e., it is a safe operation such as a query, read operation, or lookup).
  • Use POST if:
    • The interaction is more like an order, or
    • The interaction changes the state of the resource in a way that the user would perceive (e.g., a subscription to a service), or
    • The user be held accountable for the results of the interaction.

However, before the final decision to use HTTP GET or POST, please also consider considerations for sensitive data and practical considerations.


- Matt

Ezra Zygmuntowicz

unread,
Jan 8, 2009, 2:37:08 PM1/8/09
to me...@googlegroups.com


Here is how you can make it happen. You need this javascript in your
app:

$(document).ready(function(){
$('a[method]').livequery(function(){
var message = $(this).attr('confirm');
var method = $(this).attr('method');

if (!method && !message) return;

$(this).click(function(event){
if (message && !confirm(message)) {
event.preventDefault();
return;
}

if (method == 'post' || method == 'put' || method == 'delete') {
event.preventDefault();

var form = $("<form/>").attr('method', 'post').attr('action',
this.href).attr('style', 'display: none');

if (method != "post") {
form.append($('<input type="hidden" name="_method"/
>').attr('value', method));
}
form.insertAfter(this).submit();
}
});
});
)};


Then you can use a normal link_to like this:

= link_to "Destroy!", url(:decom, instance.id), :method
=> :delete, :confirm => "Are you sure? There is no undo."


Cheers-
-Ezra


Ezra Zygmuntowicz
e...@engineyard.com

Matt Aimonetti

unread,
Jan 8, 2009, 2:57:43 PM1/8/09
to me...@googlegroups.com
As mentioned in an earlier post, the recommended way is to offer a non JS path using the delete action:


<%= link_to "[delete]", resource(article, :delete), :class => "delete", :rel => resource(article) %>

jQuery(function($) {
  $("a.delete").click(function() {
    // jQuery idiom that allows use of 'this' inside the callback
    var self = $(this);
    
    if(confirm("Are you sure you wish to delete this article")) {
      $.post($(this).attr("rel"), {"_method": "delete"}, function(json) {
        self.parents("li:first").remove();
        $("div.notice").html(json.notice);
      }, "json");
    }
    return false;
  });
});


If you prefer to not care about UJS you can simply do:

Let's imagine we have a list of articles and in the li of each article we add the following link:
<%= link_to "[delete]", resource(article), :class => "delete" %>

Then just add the following JS:

jQuery(function($) {
  $("a.delete").click(function() {
    // jQuery idiom that allows use of 'this' inside the callback
    var self = $(this);
    
    if(confirm("Are you sure you wish to delete this article")) {
      $.post($(this).attr("href"), {"_method": "delete"}, function() {
        self.parents("li:first").remove();
        $("div.notice").html("Article deleted");
      });
    }
    return false;
  });
});


The code is pretty simple and compact so let me know if it doesn't make sense.

- Matt

Jim Freeze

unread,
Jan 8, 2009, 3:24:57 PM1/8/09
to me...@googlegroups.com

My point was that some people like to know the real cost. There are
numerous examples of people succeeding by going against accepted
convention. Just saying something is evil is a lame explanation. (Good
thing DHH didn't listen to people saying what he was doing was evil).
Saying 'it is evil' is no where near the same as saying 'your data
could be destroyed by accident'.

Jim


--
Jim Freeze

Richard Boldway

unread,
Jan 8, 2009, 4:32:35 PM1/8/09
to me...@googlegroups.com
I think some of this bantering about has been fun to watch. 

I know from my shallow memory, the first thing that comes to my mind is that it is EVIL. 
But then I have to think a bit deeper why it is evil. 
Ahh.  Exposure to unintended results.  Not a good thing. 
Anyways I enjoyed the discussion and the reminders of why it is evil.

Thanks you all.... Rich

Roy Wright

unread,
Jan 8, 2009, 5:03:58 PM1/8/09
to me...@googlegroups.com
Thanks everyone.  My base controller has now been modified to be GOOD.
That's what I get for copying code and not taking the time to understand it completely.

Thank you,
Roy

Matt Aimonetti

unread,
Jan 8, 2009, 5:53:35 PM1/8/09
to me...@googlegroups.com
hehe, no problem, that's why the mailing list is for. I remember the first time I came across this issue and what convinced me was that a user can press the reload button and the same action can be executed twice.

I believe I had a form adding a comment on the page or something like that. Turned out that a new item was being added every time a user would refresh the page after clicking on the link. That's when I learned that a POST request should always be redirected to a GET request so the user could refresh the page without breaking anything. Merb and Rails already do that for you when we never ever have an update or destroy method render a template but back then I was not using a nice framework and I had to learn the hard way.

All that to say that I'm sorry for not explaining right away why what Justin was trying to do was evil :)

- Matt

Jim Freeze

unread,
Jan 9, 2009, 11:51:16 AM1/9/09
to me...@googlegroups.com
On Thu, Jan 8, 2009 at 4:53 PM, Matt Aimonetti <mattai...@gmail.com> wrote:
> I believe I had a form adding a comment on the page or something like that.
> Turned out that a new item was being added every time a user would refresh
> the page after clicking on the link. That's when I learned that a POST
> request should always be redirected to a GET request so the user could
> refresh the page without breaking anything. Merb and Rails already do that
> for you when we never ever have an update or destroy method render a
> template but back then I was not using a nice framework and I had to learn
> the hard way.

Great explanation Matt. I learned something too. "POST request should
always be redirected to a GET".

Thanks

Jim


--
Jim Freeze

Reply all
Reply to author
Forward
0 new messages