Rails 3.0.9 + Devise + Cucumber + Capybara the infamous “No route matches /users/sign_out”

后端 未结 6 1741
我寻月下人不归
我寻月下人不归 2021-02-02 09:27

I am using devise 1.4.2 with rails 3.0.9, cucumber-rails 1.0.2, capybara 1.0.0. I got No route matches \"/users/sign_out\" error when I clicked logout. I added

相关标签:
6条回答
  • 2021-02-02 10:06

    The easiest way to correct this problem (albeit probably not the most correct one) is to modify your routes file to match the rest of the application. E.g. make the GET version of destroy_user_session_path work. You can do this by modifying the routes file as follows

    Remove:

    devise_for :users
    

    Add:

    devise_for :users do
      get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session
    end
    

    This is a bit dirty. I'm sure that Devise deprecated the GET route for good reason. However, fixing it any other way is beyond my Cucumber knowledge at this point, as every test in that suite ultimately relies on visit('/users/logout') which just isn't possible with the out-of-the-box Devise routes.

    UPDATE

    You can also fix this by commenting out the following in config/initialers/devise.rb

    #config.sign_out_via = :delete
    
    0 讨论(0)
  • 2021-02-02 10:06

    Devise 1.4.1 (27 June 2011) changed the default behavior for sign out requests:

    https://github.com/plataformatec/devise/commit/adb127bb3e3b334cba903db2c21710e8c41c2b40

    Jose Valim explained why: "GET requests should not change the state of the server. When sign out is a GET request, CSRF can be used to sign you out automatically and things that preload links can eventually sign you out by mistake as well."

    Cucumber wants to test GET requests not DELETE requests for destroy_user_session_path. If you intend to use Cucumber with Devise, change the Devise default from DELETE to GET for the Rails test environment only with this change to config/initializers/devise.rb:

    config.sign_out_via = Rails.env.test? ? :get : :delete

    Don't try to tweak the routes.rb file to make the fix. It isn't necessary. If you're not going to use Cucumber, leave Devise's new default (DELETE) in place.

    The example source code here:

    https://github.com/RailsApps/rails3-devise-rspec-cucumber

    now includes the change to the Devise initializer for Cucumber.

    The application template here:

    https://github.com/RailsApps/rails3-application-templates

    now detects the collision between Devise and Cucumber and alters the Devise initializer as needed.

    These changes were tested with with Rails 3.1.0.rc4 but the behavior should be the same with Rails 3.0.9. Please add comments here if the issue is unresolved or if you have more information.

    0 讨论(0)
  • 2021-02-02 10:14

    When i need use something like this in test.env:

      visit destroy_user_session_path
    

    it's work for me, but maybe this is not right)

    config/init/devise.rb

      # The default HTTP method used to sign out a resource. Default is :delete.
      if Rails.env.test?
        config.sign_out_via = :get
      else
        config.sign_out_via = :delete
      end
    
    0 讨论(0)
  • 2021-02-02 10:18

    The right way to solve this problem is explained in the devise's wiki page: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

    Basically, once you have included on your user_step.rb file:

    include Warden::Test::Helpers
    Warden.test_mode!
    

    You may replace visit '/users/sign_out' with logout(:user)

    0 讨论(0)
  • 2021-02-02 10:20

    So I have found that

    <%= link_to "Logout", destroy_user_session_path, :method => :delete %>
    

    rails helper generates following html

    <a rel="nofollow" data-method="delete" href="/users/sign_out">Sign out</a>
    

    and jquery_ujs.js has following method to convert the links with data-method="delete" attribute to a form and submit at runtime:

    // Handles "data-method" on links such as:
    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
    handleMethod: function(link) {
    var href = link.attr('href'),
    method = link.data('method'),
    csrf_token = $('meta[name=csrf-token]').attr('content'),
    csrf_param = $('meta[name=csrf-param]').attr('content'),
    form = $('<form method="post" action="' + href + '"></form>'),
    metadata_input = '<input name="_method" value="' + method + '" type="hidden" />';
    if (csrf_param !== undefined && csrf_token !== undefined) {
    metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />';
    }
    form.hide().append(metadata_input).appendTo('body');
    form.submit();
    }
    

    And Capybara helper visit('/users/sign_out') simply clicks the link and send a GET request to the server which does not have any route for this request.

    As opposed to link_to helper the button_to helper adds the required form within the html when page is rendered instead of relying on javascript:

    <%= button_to "Logout", destroy_user_session_path, :method => :delete %>
    

    generates following html

    <form class="button_to" action="/users/sign_out" method="post">
        <div>
            <input type="hidden" name="_method" value="delete">
            <input type="submit" value="Logout">
            <input type="hidden" name="authenticity_token" value="0Il8D+7hRcWYfl7A1MjNPenDixLYZUkMBL4OOoryeJs=">
        </div>
    </form>
    

    with this I can easily use Capybara helper click_button('Logout') in my 'I sign out' step definition.

    "link_to with a method anything other than GET is actually a bad idea, as links can be right clicked and opened in a new tab/window, and because this just copies the url (and not the method) it will break for non-get links..."

    As Max Will explained right clicking and opening the link_to link with non-get data-method in new tab results in a broken link.

    Some more useful discussion on link_to helper with ':method => :delete' and capybara issue can be found on this link

    For now I would stick to simple link_to helper without :method attribute, and would prefer using button_to if I want to switch to non-get method for deleting.

    At the same time I think there should be a capybara helper equivalent to Visit to cater for data-method attribute to send post request, so that one could avoid using javascript based driver for integration testing. Or may be there already is one which I am not aware of. Correct me if I am wrong.

    0 讨论(0)
  • 2021-02-02 10:30

    I'm actually having the same exact problem but with a Rails/Sinatra app. I've got Devise set up for Rails and the logout works. I've got a GuestApp Sinatra app running in lib, which works great except for the logout link. I'm trying to force data-method="delete" on the sinatra logout link, but nothing i do will make the request a delete request.

    I think this might me a sinatra problem for me, but I thought what ever requests come in are processed by rails routes first until they reach my sinatra route. I'm about to manually add the GET route for logout, but I'd rather not have to do that.

    Here's my devise routes:

    devise_for :member, :path => '', :path_names => {
      :sign_in => "login",
      :sign_out => "logout",
      :sign_up => "register" }
    

    Here's my link:

    %a{:href => '/logout', :"data-method" => 'delete', :rel => 'nofollow'}Log Out
    <a href="/logout" data-method="delete" rel="nofollow">Log Out</a>
    
    #- realized it should be method instead, but still not reaching routes.rb as delete
    <a href="/logout" method="delete" rel="nofollow">Log Out</a>
    
    0 讨论(0)
提交回复
热议问题