26

In a brand new Rails application with a scaffolded RESTful model, the generated delete code looks like this:

class BeersController < ApplicationController
  # DELETE /beers/1
  # DELETE /beers/1.xml
  def destroy
    @beer = Beer.find(params[:id])
    @beer.destroy

    respond_to do |format|
      format.html { redirect_to(beers_url) }
      format.xml  { head :ok }
    end
  end
end

If a user tries to delete the same Beer twice (maybe a quick double-click, or actions in two different browser tabs) they will get a RecordNotFound error resulting in a 404 page. This is a pretty unfriendly experience; it seems like it would be better to complete the redirect back to beers_url regardless, possibly with a flash error, since there isn't really anything the user can do about the second delete failing.

An alternative approach is to act like the delete succeeded anyway with something like this:

def destroy
  @beer = Beer.find_by_id(params[:id])
  destroyed = @beer.try(:destroy)        

  respond_to do |format|
    format.html { redirect_to(beers_url) }
    format.xml  { destroyed ? head(:ok) : head(:not_found) }
  end
end

I can understand the desire for a hard 404 error in the API use-case, but it's harder for me to justify for a web application. Can anyone provide a good reason why we should throw a scary error at the user in the name of RESTfulness?

(This question isn't particular to Rails, but I don't know how other frameworks handle this case out of the box).

Brad
  • 937
  • 1
  • 9
  • 23

5 Answers5

11

UPDATE: It turns out I was wrong: https://stackoverflow.com/a/24713946/14731


Previous answer: HTTP DELETE is an idempotent operation. Invoking it multiple times consecutively must result in the same behavior as the first. Meaning: you shouldn't return HTTP 404.

Nikhil Chilwant
  • 629
  • 3
  • 11
  • 31
Gili
  • 86,244
  • 97
  • 390
  • 689
  • 11
    Must result in the same server state of the resource targeted. That doesn't mean you need to return the same status code. You seem to assume a repeated request in case of a previous failure. What about plain non-existing resources? Trying to delete those should clearly indicate non-existance, shouldn't it? – Oliver Drotbohm Jul 11 '14 at 10:32
  • 1
    Gil, your update means it *can* return 404 without violating a particular principle, ie idempotence. It doesn't imply it *should*. That said, I think it makes sense to do so from a practical perspective. It means the client has an opportunity to distinguish between whether the item was deleted and whether it didn't exist, something the user and/or client developer will probably care about. I'd still like to know if there's any standard for this situation, it's basic enough that there should be. – mahemoff Mar 11 '15 at 17:06
  • The problem here is that Rails is server-side rendering the 404 page. The only way the "client" can treat both 404 and 204 the same, is AJAX. Otherwise, the Rails server has to have a custom error for 404 that looks like success otherwise, but only in this one case, which is horrible design. Thanks REST architects for considering the client! – Larry Kyrala Sep 25 '18 at 21:03
  • @LarryKyrala The problem you are describing seems to be technology-specific and does not affect REST architecture in general. The server returns whatever error code (say 404 or 204) and the client is free to render it however it wishes. REST does not mandate how the client should render the content. – Gili Sep 26 '18 at 07:08
6

I don't think you should ever throw the error at the user for the sake of holding up some standard - especially if this is a consumer facing app (as opposed to B2B). But you also shouldn't make your api change its status code just for this one situation. The resource doesn't exist anymore; so a 404 is a proper response.

I think there is a path of least (or lessor - is that even a word???) resistance here. I haven't explored ruby yet so I can't provide any usable implementation; but I'm somewhat experienced with web apps using html/css/js.

If there is some legitimate problem with users clicking a button twice; why not set up the button so that it disables when the request is submitted, and re-enables once the conditions are proper (request has come back)? In other words, avoid the if(this very specific situation) logic by making it impossible to get into the situation you're seeing. I'm assuming ruby has something specifically for handling requests and adding function handlers for different status codes; or at least non-200 status codes.

Dave
  • 6,141
  • 2
  • 38
  • 65
  • 2
    I like the idea of preventing double-submissions of non-idempotent actions whenever possible. It's not foolproof (imagine that the user has two copies of you app open in two tabs, and tries to delete the same item in both of them) but it's definitely an improvement in usability. – Brad Jun 25 '11 at 16:17
  • I think you wanted "lesser" or simply "less" – Aaron J Lang May 10 '18 at 00:59
  • you can debounce the requests (disable the button after send) with or without AJAX. Making the entry disappear after receiving the first 204 is a nice extra. – Larry Kyrala Sep 25 '18 at 21:05
2

Rails scaffold code is a suggestion at best. Your instinct to make the error message more user friendly is a good one.

jdl
  • 17,702
  • 4
  • 51
  • 54
  • 2
    I think you're right, but I'm making an even stronger claim; that there shouldn't be an error message at all, and that the action should complete _as if_ the destroy had succeeded. – Brad Jun 25 '11 at 16:18
1

There's no reason you can't return a nicely formatted 404 page with your response. The status code could be the same, just the rendering is more user friendly than your typical 404 page. You could even return the "redirected" page in the response body. One possible problem with this is browser support. It's been quite a few years but I seem to recall IE (6?) completely disregarding response body content when receiving a 404. You'll have to experiment to see what works best for you.

bcarlso
  • 2,345
  • 12
  • 12
1

I was thinking about the security aspect of the response provided to the client. If you get 204 you understand that the resource did exist but on 404 you can tell there is no such resource.

Not sure though how this can be exploited.

havryliuk
  • 136
  • 1
  • 11