392

I've installed devise on my app and applied the following in my application.html.erb file:

<div id="user_nav">
    <% if user_signed_in? %>
        Signed in as <%= current_user.email %>. This cannot be cheese?
        <%= link_to 'Sign out', destroy_user_session_path %>
    <% else %>
        <%= link_to 'Register', new_user_registration_path %> or <%= link_to 'Sign in', new_user_session_path %>
    <% end %>
</div>

I ran rake routes and confirmed that all the routes are valid.

Also, in my routes.rb file I have devise_for :users and root :to => "home#index".

I get the following routing error when clicking the "Sign out" link:

No route matches "/users/sign_out"

Any ideas what's causing the error?

vich
  • 11,638
  • 13
  • 46
  • 60
  • 1
    Did you restart your app after adding the routes? Route changes only become effective on startup. – Thilo-Alexander Ginkel Jul 02 '11 at 13:33
  • 2
    Yes. Just did it again to be safe. Also, I read somewhere else on Stack that it could be an issue with the newest devise gem not being compatible with Rails 3.0.3 so I tried changing my devise gem from 1.4.2 to `gem 'devise', :git => 'git://github.com/plataformatec/devise.git'`. That did nothing though. – vich Jul 02 '11 at 13:38
  • Wouldn't changing the entry in the Gemfile just get you an ever newer version of Devise? Have you tried specifying a lower version number? – Leo Cassarani Jul 02 '11 at 14:39
  • Could you post your routes.rb file – felix Jul 02 '11 at 14:45
  • 1
    The second answer by Jessie below worked perfectly. – vich Jul 04 '11 at 15:15
  • Could it be this is a bug with a new Devise version? It seems to work with an older one. – slhck Jul 05 '11 at 11:00
  • This was my initial thought, but I haven't found any documentation to verify that. – vich Jul 07 '11 at 15:57
  • FWIW, this can ALSO happen (fails to signout) if your browser is blocking javascript, which I guess blocks DELETE actions. I ran into this today after starting to use the NoScript add-on for firefox. – jpw Mar 04 '12 at 01:09
  • I only started seeing this after moving to a custom domain. The DELETE approach was working on myapp-staging.herokuapp.com, but broke once I moved to myapp.com. Using GET works, but makes me feel a bit dirty. – pdoherty926 Sep 01 '12 at 15:56

29 Answers29

575

I think the route for signing out is a DELETE method. This means that your sign out link needs to look like this:

<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>

Yours doesn't include the :method => :delete part. Also, please note that for this to work you must also include <%= javascript_include_tag :defaults %> in your layout file (application.html.erb).

K M Rakibul Islam
  • 31,427
  • 11
  • 79
  • 100
Jessie Dedecker
  • 6,609
  • 1
  • 15
  • 12
  • 3
    I can safely say I've never had to do that in any of my Rails apps. `link_to "Sign out", destroy_user_session_path` has always been good enough for me. – Leo Cassarani Jul 02 '11 at 14:37
  • 8
    I did the test and it works for me. Don't forget that things change from one version to another (be it in Rails or Devise). Besides, logging out is state-changing behaviour which should not be done using GET methods (in my humble opinion). – Jessie Dedecker Jul 02 '11 at 14:44
  • 23
    For more information, the rationale why this was changed in the latest version of Devise is described [here](https://github.com/plataformatec/devise/issues/431) and [here](http://groups.google.com/group/plataformatec-devise/browse_thread/thread/99d466710a95a72/965df9eac3afab89?lnk=gst&q=DELETE#965df9eac3afab89). – Jessie Dedecker Jul 03 '11 at 08:57
  • 1
    I took a look at a route that came from a couple months ago for another application and its destroy is via GET and in my new applications routes the route is accessed via delete, saw this by running `rake routes`. – Travis Pessetto Jul 29 '11 at 18:05
  • 6
    I ran into this same issue and Jessie's suggestion worked for me. I expect anyone who is running through the devise railscast to eventually end up here due to this change... – johnnygoodman Sep 18 '11 at 00:44
  • 2
    you can also type this into your web console to test it out `$("Sign out").appendTo('body');` - if jQuery is loaded on the page. – mraaroncruz Jul 24 '12 at 18:52
  • I am not using Rails default asset management. So how do I accomplish this without `http://stackoverflow.com/questions/6557311/no-route-matches-users-sign-out-devise-rails-3#comment15414595_6557627` – Jason Kim Mar 22 '13 at 19:33
  • I don't like the Javascript dependancy in order to sign out. Will Nathan's answer later on about turning it into a button_to and letting Rails implement the delete method sits much more confortably with me. – Chris Lewis Apr 03 '13 at 11:27
  • this is useless if you are rendering the html on the frontend – richsoni Mar 01 '16 at 22:19
135

I changed this line in devise.rb:

config.sign_out_via = :delete

to

config.sign_out_via = :get

and it started working for me.

kitdesai
  • 1,608
  • 2
  • 10
  • 9
  • This would be how to do it if you did not want to add a custom path or use delete – Travis Pessetto Jul 29 '11 at 18:06
  • 22
    The way it was done before was to sign out using "GET /users/sign_out", but they changed it to "DELETE" to make it more RESTful. The author explained that a GET shouldn't make changes to the server such as logging out. – Jonathan Allard Aug 26 '11 at 22:08
  • 2
    this worked for me. Whilst I appreciate it's not a best practise, the other answers failed. I can't see why!! – Mild Fuzz Mar 18 '12 at 11:08
  • 8
    It is also possible to use an array if you wish to support multiple methods. For example: `config.sign_out_via = [ :post, :delete ]` or `devise_for :users, :sign_out_via => [ :post, :delete ]`, as described in [devise/rails/routes.rb](https://github.com/plataformatec/devise/blob/f49b605514910b9fa145cf9e548430434b5a3e51/lib/devise/rails/routes.rb#L92). – Hosam Aly May 10 '12 at 12:53
  • 2
    I wouldn't use a GET request for signing out as it opens the user up to a XSRF attack. (Hacker creates a website with an image tag with src="http://foo.com/users/sign_out", user visits it and is logged out). – Ryan Doherty Jan 20 '13 at 21:57
  • 1
    @RyanDoherty Id I'm not mistaken, XSRF is still possible (the attacker just has to use a form with action="DELETE" on the target url, then auto-submit it at the page load). – Maël Nison Mar 11 '13 at 04:02
  • @MaëlNison True, I forgot about that. IIRC Rails builds in XSRF protection via tokens. – Ryan Doherty Mar 11 '13 at 04:36
  • I thought this was a good solution, but then I checked all the other link_to.....method: :delete in my app and found none of them were working. So looks like I have to fix the real problem, probably related to js – ryan2johnson9 Dec 12 '17 at 00:04
61

You probably didn't include jquery_ujs javascript file. Make sure you are using the latest version of jquery-ujs : https://github.com/rails/jquery-ujs and the last files available :

rails generate jquery:install

You should not have any more rails.js file. If you do, you're probably out-of-date. Make sure also this file is loaded with defaults, in config/application.rb

config.action_view.javascript_expansions[:defaults] = %w(jquery.min jquery_ujs)

(Again, you should not have rails.js file here). Finally, add the link as documented on Devise wiki (haml-style):

= link_to('Logout', destroy_user_session_path, :method => 'delete')

And everything will be fine.

Drew Stephens
  • 15,475
  • 12
  • 54
  • 81
Gravis
  • 26,093
  • 4
  • 20
  • 20
  • 8
    I had :method => 'delete' in my link_to, the problem was jquery_ujs not included, this solution solved my problem. Remember to put gem 'jquery-rails' in your gemfile. – Rob Bazinet Sep 22 '11 at 14:08
  • 2
    Thanks this worked for me. Instead of using defaults you can also use `javascript_include_tag "jquery_ujs"` – Bnicholas Jul 09 '12 at 08:05
  • Good catch, thank you! I am using require.js to load files asynchronously and forgot to require jquery_ujs. – Dan Fairaizl May 09 '13 at 16:43
  • I assumed the "u" meant uncompressed and removed this line from `application.js`. Bad assumption, I suppose. Thanks. – Jeff Sep 10 '13 at 01:08
  • This problem cropped up (in my 100th devise based app) after changing out twitter bootstrap from less to sass and I forgot to add back the //= require jquery_ujs into my application.js. – Joe Mar 22 '15 at 19:47
31

The ability to make the Logout link a DELETE RESTful call requires an html attribute data-method = "delete" by using the rails code = link_to('Logout', destroy_user_session_path, :method => :delete).

However, if you do not have the gem jquery-ujs installed or are not calling the resulting javascript in your application.html via = javascript_include_tag "application", the response will be sent as a GET request, and the route will fail.

You have a few options if you do not want to use jquery-ujs or cannot find a way to make it work:

  1. Change config.sign_out_via to equal :get within devise.rb (not recommended, since DELETE is the appropriate RESTful query)
  2. OR Change the link_to to = button_to('Logout', destroy_user_session_path, :method => :delete). With button_to Rails will do the heavy lifting on making the proper DELETE call. You can then style the button to look like a link if you wish.
Will Nathan
  • 625
  • 7
  • 13
  • The change from link_to to button_to worked for me, but i really don't understand why? What exactly changed apart from the html/css? – Spyros Mandekis Nov 29 '12 at 16:49
  • 1
    It has to do with the rails 'magic' that auto generates html from functions like button_to and link_to. It just so happens that the rails magic for button_to specifies the proper RESTful DELETE call and link_to does not. If I had to guess, I'd say the reason is that html button elements can handle a DELETE call (or bundle with a hidden field specifying the action) while regular links cannot. – Will Nathan Nov 30 '12 at 17:48
  • Thanks for this. I setup my app to manually handle jquery, so I didn't have jquery-ujs. Installing with bower or bundler and including the appropriate line for the asset pipeline fixes the issue. – jrhorn424 Sep 09 '13 at 06:01
  • 1
    This seems to me the safest and most portable solution. –  Mar 06 '14 at 18:41
  • About `button_to`, Rails generates a `form` for the whole action. That's why it's possible to use `:delete` method with `button_to` and not with `link_to`, just see the generated HTML. – Fernando Fabreti Oct 12 '15 at 19:56
25

Try adding a new route to devise/sessions#destroy and linking to that. Eg:

routes.rb
devise_for :users do
  get 'logout' => 'devise/sessions#destroy'
end

view:

<%= link_to "Logout", logout_path %>
Dave Newton
  • 152,765
  • 23
  • 240
  • 286
Kevin Tsoi
  • 1,777
  • 14
  • 16
  • Getting the same error as mmichael. This above test works for me. – rtfminc Jul 03 '11 at 06:42
  • 1
    I also had the same error as mmichael. The solution above will work but it's not how it's supposed to be fixed. The default routes in Devise already include the signing out route as a DELETE method. You should normally not need to change the default routes themselves. That's why you can fix it by simply adding a separate parameter to the `link_to` call, as described in the other answer. – Jessie Dedecker Jul 03 '11 at 08:54
  • You don't have to add a route to the route.rb fiile, devise lets you change the method in devise.rb which is in the /confit/initializers/ directory. – Travis Pessetto Jul 29 '11 at 18:07
  • 6
    Never ever have logout path as GET. – Jagira Dec 03 '11 at 07:44
  • @Jagira why? Why not both DELETE and GET? – hrdwdmrbl Oct 22 '12 at 14:47
  • Anyone else can confirm what Jagira says. Also i find this answer as properly, i am implementing this on all my projects – David Mauricio Nov 20 '12 at 21:41
  • How do you set a path if it was **routes.rb** `get '/:lang/user/logout' => 'devise/session#destroy'` **view** `` ? Thanks – RajG Apr 09 '13 at 15:43
  • The syntax is now: devise_for :users then the gets must be put in devise_scope :user do NOTE: singular :user for devise_scope. see the answer from dipole_moment. – Taylored Web Sites Jan 03 '18 at 18:32
14

Use it in your routes.rb file:

devise_for :users do
    get '/users/sign_out' => 'devise/sessions#destroy'
end
pjumble
  • 16,280
  • 6
  • 41
  • 51
13

I had the same problem with rails 3.1.0, and I solved adding in file the followings lines:

app/assets/javascripts/application.js
//= require_tree
//= require jquery
//= require jquery_ujs
gringo
  • 220
  • 2
  • 4
10

With one exception, Jessie's answer worked for me:

<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>

change:

:delete

... to:

'delete'

So the code that worked for me is:

<%= link_to "Sign out", destroy_user_session_path, :method => 'delete' %>
Jon
  • 308
  • 2
  • 10
Galen
  • 210
  • 3
  • 13
9

Many answers to the question already. For me the problem was two fold:

  1. when I expand my routes:

    devise_for :users do 
       get '/users/sign_out' => 'devise/sessions#destroy'
    end
    
  2. I was getting warning that this is depreciated so I have replaced it with:

    devise_scope :users do
       get '/users/sign_out' => 'devise/sessions#destroy'
    end
    
  3. I thought I will remove my jQuery. Bad choice. Devise is using jQuery to "fake" DELETE request and send it as GET. Therefore you need to:

    //= require jquery
    //= require jquery_ujs
    
  4. and of course same link as many mentioned before:

    <%= link_to "Sign out", destroy_user_session_path, :method => :delete %>
    
Lukasz Muzyka
  • 2,707
  • 1
  • 27
  • 40
  • can you explain 3rd point..i was a victim of it and i want to understand it more – Rahul Dess Sep 23 '14 at 09:27
  • 1
    Certainly. Your website is not really sending any DELETE requests. Quite frankly it will only use GET and POST. Because in Rails community it has been agreed that (for good reasons) we want to delete records when submitting DELETE request we need to use a little trick. When in your ERB you specify method: :delete rails will convert that to HTML5 tag: data-method="delete" and submit it as GET. Now, this is where jQuery-ujs and jQuery kick in. They allow app to recognise this is what you're doing. So that your request can be matched with controller action. – Lukasz Muzyka Sep 24 '14 at 03:44
  • without those gems, this will not work. Of course, you can change settings of devise (in devise initialiser) and ask it to use GET instead of DELETE. – Lukasz Muzyka Sep 24 '14 at 03:45
7

Add:

  <%= csrf_meta_tag %>  and 
  <%= javascript_include_tag :defaults %>  to layouts

Use these link_to tags

 link_to 'Sign out', destroy_user_session_path, :method => :delete

  or

 link_to 'Sign out', '/users/sign_out', :method => :delete

In routes add:

  devise_for :users do
    get '/users/sign_out' => 'devise/sessions#destroy'
  end
Amal Kumar S
  • 14,139
  • 19
  • 51
  • 83
7

Other option is to configure the logout to be a GET instead a DELETE, you can do that adding the following line on /config/initializers/devise.rb

config.sign_out_via = :get

But as Steve Klabnik wrote on his blog (http://blog.steveklabnik.com/2011/12/11/devise-actioncontroller-routingerror-no-route-matches-get-slash-users-slash-sign-out.html) try to use DELETE because of the semantic of this method.

Rodrigo Flores
  • 2,261
  • 15
  • 17
6

If you are using Rails 3.1 make sure your application.html.erb sign out looks like:

<%= link_to "Sign out", destroy_user_session_path, :method => :delete %>

And that your javascript include line looks like the following

<%= javascript_include_tag 'application' %>

My guess is that some gems overwrite the new structure of the default.js location.

StlTenny
  • 111
  • 2
  • 3
5

Don't forget to include the following line in your application.js (Rails 3)

//= require_self
//= require jquery
//= require jquery_ujs

Include jquery_ujs into my rails application and it works now.

peter.hrasko.sk
  • 3,827
  • 2
  • 16
  • 32
The Lazy Log
  • 3,404
  • 2
  • 18
  • 26
5

Most answers are partial. I have hit this issue many times. Two things need to be addressed:

<%= link_to(t('logout'), destroy_user_session_path, :method => :delete) %>

the delete method needs to be specified

Then devise uses jquery, so you need to load those

   <%= javascript_include_tag "myDirectiveJSfile" %> 

and ensure that BOTH jquery and jquery-ujs are specified in your myDirectiveJSfile.js

//= require jquery
//= require jquery_ujs
Jerome
  • 4,155
  • 1
  • 26
  • 47
5

Check it out with source code in github:

https://github.com/plataformatec/devise/commit/adb127bb3e3b334cba903db2c21710e8c41c2b40#lib/generators/templates/devise.rb (date : June 27, 2011 )

  • # The default HTTP method used to sign out a resource. Default is :get. 188
  • # config.sign_out_via = :get 187
  • # The default HTTP method used to sign out a resource. Default is :delete. 188
  • config.sign_out_via = :delete
Shane
  • 61
  • 1
5

Well, guys for me it was only remove the :method => :delete

<%= link_to('Sign out', destroy_user_session_path) %>
workdreamer
  • 2,698
  • 1
  • 32
  • 37
5

I want to add to this even though it's a bit old.

the "sign_out" link didn't work, despite having :method => :delete.

The comment indicating that <%= javascript_include_tag :defaults %> must be included reminded me I had recently added JQuery java script and used simple <script src=""/> tags to include them.

When I moved them from after the :defaults to before, the sign_out started working again.

Hopefully this helps someone.

NoNaMe
  • 5,410
  • 30
  • 73
  • 98
Greg
  • 458
  • 1
  • 5
  • 15
5

This means you haven't generated the jquery files after you have installed the jquery-rails gem. So first you need to generate it.

rails generate devise:install

First Option:

This means either you have to change the following line on /config/initializers/devise.rb

config.sign_out_via = :delete to config.sign_out_via = :get

Second Option:

You only change this line <%= link_to "Sign out", destroy_user_session_path %> to <%= link_to "Sign out", destroy_user_session_path, :method => :delete %> on the view file.

Usually :method => :delete is not written by default.

Deepak Lamichhane
  • 11,130
  • 4
  • 27
  • 42
4

If you're using HTTPS with devise, it'll break if your sign-out link is to the non-secure version. On the back end, it redirects to the secure version. That redirect is a GET, which causes the issue.

Make sure your link uses HTTPS. You can force it with protocol: "https" in your url helper (make sure you use the url helper and not the path helper).

<%= link_to "Sign out", destroy_user_session_url(protocol: "https"), method: :delete %>
Tyler Collier
  • 9,964
  • 9
  • 63
  • 74
4
  devise_for :users
  devise_scope :user do
    get '/users/sign_out' => 'devise/sessions#destroy'
  end
dipole_moment
  • 3,489
  • 2
  • 30
  • 48
3

Lots of solutions are there. but mostly use this,

<%= link_to 'Sign out', destroy_user_session_path, method: :delete %>

or config devise.rb with proper sign_out method

In devise.rb

config.sign_out_via = :delete ( or  :get which u like to use.) 
jon snow
  • 2,992
  • 1
  • 16
  • 30
3

use :get and :delete method for your path:

devise_scope :user do
  match '/users/sign_out' => 'devise/sessions#destroy', :as => :destroy_user_session, via: [:get, :delete]
end
Zakaria
  • 873
  • 14
  • 20
3

The problem begin with rails 3.1... in /app/assets/javascript/ just look for application.js.

If the file doesn't exist create a file with that name I don't know why my file disappear or never was created on "rails new app"....

That file is the instance for jquery....

Stephan
  • 37,597
  • 55
  • 216
  • 310
rome3ro
  • 111
  • 1
  • 6
2

In your routes.rb :

 devise_for :users do
    get '/sign_out' => 'devise/sessions#destroy'
    get '/log_in' => 'devise/sessions#new'
    get '/log_out' => 'devise/sessions#destroy'
    get '/sign_up' => 'devise/registrations#new'
    get '/edit_profile' => 'devise/registrations#edit'
 end

and in your application.html.erb:

<%if user_signed_in?%>
          <li><%= link_to "Sign_out", sign_out_path %></li>
<% end %>
Uladz Kha
  • 1,354
  • 4
  • 22
  • 39
2

This is what I did (with Rails 3.0 and Devise 1.4.2):

  1. Make sure your page loads rails.js
  2. Use this param: 'data-method' => 'delete'
  3. Good idea to add this param: :rel => 'nofollow'
Peter O.
  • 28,965
  • 14
  • 72
  • 87
Larry
  • 21
  • 1
1

See if your routes.rb has a "resource :users" before a "devise_for :users" then try swapping them:

  1. Works

    • devise_for :users
    • resources :users
  2. Fails

    • resources :users
    • devise_for :users
Charles Magid
  • 121
  • 1
  • 1
  • 5
1

the ':method => :delete' in page is 'data-method="delete"' so your page must have jquery_ujs.js, it will submit link with method delete not method get

Code Lღver
  • 15,171
  • 16
  • 51
  • 71
1

I know this is an old question based on Rails 3 but I just ran into and solved it on Rails 4.0.4. So thought I'd pitch in how I fixed it for anyone encountering this problem with this version. Your mileage may vary but here's what worked for me.

First make sure you have the gems installed and run bundle install.

gem 'jquery-rails'

gem 'turbolinks'

gem 'jquery-turbolinks'

In application.js check that everything is required like below.

Beware if this gotcha: it's //= require jquery.turbolinks and not //= require jquery-turbolinks

//= require jquery
//= require jquery_ujs
//= require jquery.turbolinks
//= require turbolinks
//= require_tree .

Next, add the appropriate links in the header of application.html.erb.

<%= javascript_include_tag  "application", "data-turbolinks-track" => true %>
<%= javascript_include_tag :defaults %>

There seems to be many variations on how to implement the delete method which I assume depends on the version of Rails you are using. This is the delete syntax I used.

<p><%= link_to "Sign Out", destroy_user_session_path, :method => 'delete' %></p>

Hope that helps dig someone out of this very frustrating hole!

mikeym
  • 4,006
  • 6
  • 26
  • 42
0

In general when you get "No route matches" but you think you have that route defined then double check the http verb / request method (whether its get, put, post, delete etc.) for that route.

If you run rake routes then you will see the expected method and you can compare this with the request log.

Muntasim
  • 6,371
  • 3
  • 39
  • 68