ytbryan
ytbryan

Reputation: 2694

Duplicate method-delete on rails using axios

Due to a previous issue, I have to remove //= require jquery_ujs from application.js

Now, I need to replace the /users/sign_out method with an ajax using axios. The following is my code:

axios.delete("users/sign_out", { 
  headers: { 
    "X-CSRF-Token": $('meta[name="csrf-token"]').attr('content') },
  params: { 
    "authenticity_token": $('meta[name="csrf-token"]').attr('content')
  }
})
.then(function(response) {
    alert(response)
})
.catch(function(error) {
    alert(error)
})           

The server log shows that there is a DELETE "/" right after the delete "/users/sign_out". This is not correct.

Started DELETE "/users/sign_out?authenticity_token=mHQ3d4lJzDNS5TSWEFkDZ%2F3fI0vTDFxW6CabEffaNk6h2JRYNk8kkgCSBOXFdHmgDKcVtY8e29aGU%2F3q9gajWA%3D%3D" for 127.0.0.1 at 2017-08-01 20:59:55 +0800
Processing by Devise::SessionsController#destroy as HTML
  Parameters: {"authenticity_token"=>"mHQ3d4lJzDNS5TSWEFkDZ/3fI0vTDFxW6CabEffaNk6h2JRYNk8kkgCSBOXFdHmgDKcVtY8e29aGU/3q9gajWA=="}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 2], ["LIMIT", 1]]
   (0.2ms)  BEGIN
   (0.2ms)  COMMIT
Redirected to http://localhost:3000/
Completed 302 Found in 8ms (ActiveRecord: 0.9ms)


**Started DELETE "/" for 127.0.0.1 at 2017-08-01 20:59:55 +0800**

ActionController::RoutingError (No route matches [DELETE] "/"):

Upvotes: 1

Views: 1073

Answers (2)

Ben
Ben

Reputation: 1

You can also solve this by ensuring the axios request has an Accept header with 'application/json'. As it can be seen in the devise source code for the sessions controller:

  def respond_to_on_destroy
    # We actually need to hardcode this as Rails default responder doesn't
    # support returning empty response on GET request
    respond_to do |format|
      format.all { head :no_content }
      format.any(*navigational_formats) { redirect_to after_sign_out_path_for(resource_name) }
    end
  end

Which means it should respond with head :no_content for any non-navigational format, avoiding the redirect.

Upvotes: 0

dave_slash_null
dave_slash_null

Reputation: 1124

TL;DR - set config.sign_out_via = :get in initializers/devise.rb

Devise is responding from the server with a redirect_to when DELETE /users/sign_out is requested. The default HTTP status code used by rails for redirect_to is 302 Found. The 302 status was originally meant to indicate that the browser should retry the same request with the same method, but this was subverted early on by many browsers which were changing the request method to GET automatically (see the RFC 1945 Note regarding 302 Moved Temporarily)

The Rails docs for redirect_to also have a note about this specifically as it relates to non-standard HTTP request methods via AJAX:

If you are using XHR requests other than GET or POST and redirecting after the request then some browsers will follow the redirect using the original request method. This may lead to undesirable behavior such as a double DELETE.

The solution is to return a 303 See Other like this: redirect_to resource_path, status: 303. I have looked for a way to set the HTTP status for the Devise::SessionsController#destroy and it doesn't seem to exist at this time in the Devise API.

However, you can tell Devise to use GET as the request method for sign out in the Devise initializer:

# in initializers/devise.rb
Devise.setup do |config|
  . . .
  config.sign_out_via = :get
  . . . 
end

Now when you visit the sign out link you should see GET /users/sign_out in your server logs and the browser redirect should also use GET.

Some other resources I used to research this issue:

Upvotes: 6

Related Questions