Are you SURE?! (How to confirm HTTP methods in Rails)
In Scott Raymond’s excellent book Ajax on Rails I came across a cool (non-ajax related) pattern for insuring a request’s method is POST and showing a confirmation form if not.
This is really useful in situations such as confirmation links in emails, where a form can’t be displayed and javascript won’t work, yet we don’t want a destructive action occurring through a GET request.
Here’s an example:
def unsubscribe if request.post? @user = User.find(params[:id]) @user.update_attributes :subscribed => false redirect_to home_url else render :inline => %Q( <% form_tag do %> <%= submit_tag 'Confirm' %> <% end %> ) end end
One other piece of code you’ll need to get this to work, assumming you’re using RESTful routes, in config/routes.rb
map.resources :users, :member => {:subscribe => :any, :unsubscribe => :any}
This states that we have a couple extra actions in our controller, and we’re not going to mandate a specific HTTP method to get there. This way we can handle it within the action if the HTTP method is wrong.
Now if your user comes to your page from a straight GET request, he’ll get prompted to confirm the big destructive action he’s about to commit.
(In a real app we’d make this form look a bit nicer.)
Wouldn’t it be nice if we could re-use this pattern, without writing out the inline form code every time? Seems generic enough.
DRY it up!
We could abstract that out, and also support any HTTP method we want. Lets follow Rails / RESTful conventions , and require PUT for updating a model. Using some handy ruby block syntax, we could write something like, say:
def unsubscribe confirm_unless :put do @user = User.find(params[:id]) @user.update_attribute :subscribed, false flash[:notice] = "User is now unsubscribed" redirect_to users_url end end
Much better. But how?! Keep reading.
Get one for yourself!
To get the confirm_unless method for yourself, slap the following code into app/controllers/application.rb:
def confirm_unless(method) if request.method == method yield else render :inline => %Q( <% form_tag({}, :method => :#{method}) do %> <%= submit_tag "Confirm" %> <% end %> ) end end
We could take it one step further and make it a before_filter, but I’ll leave that for a possible future post.