Подтвердить что ты не робот

Rails 3.0.9 + Devise + Cucumber + Capybara позорный "Нет совпадений маршрутов /users/sign _out"

Я использую devise 1.4.2 с рельсами 3.0.9, cucumber-rails 1.0.2, capybara 1.0.0. Я получил ошибку No route matches "/users/sign_out", когда я нажал кнопку выхода из системы. Я добавил тег :method => :delete в link_to после прохождения этого вопроса (no-route-matches-users-sign-out-devise-rails-3).

Поскольку я заменил прототип с помощью jquery, мне также пришлось изменить

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

к

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

чтобы обойти rails.js не найдена ошибка.

Хотя с приведенными выше изменениями я могу успешно выходить и перенаправляться на root, когда я смотрю на ответ запроса localhost: 3000/users/sign_out в FireBug, он показывает одно и то же сообщение об ошибке маршрутизации нажмите здесь, чтобы посмотреть скриншот с примечаниями

После успешной реализации аутентификации в rails 3 app через devise, Когда я добавил функцию и спецификации, используя Cucumber + Capybara + RSpec, следуя этому руководству (github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial), я получил следующую ошибку

When I sign in as "[email protected]/please"                              # features/step_definitions/user_steps.rb:41
Then I should be signed in                                            # features/step_definitions/user_steps.rb:49
And I sign out                                                        # features/step_definitions/user_steps.rb:53
  No route matches "/users/sign_out" (ActionController::RoutingError)
  <internal:prelude>:10:in `synchronize'
  ./features/step_definitions/user_steps.rb:55:in `/^I sign out$/'
  features/users/sign_out.feature:10:in `And I sign out'
And I should see "Signed out"                                         # features/step_definitions/web_steps.rb:105
When I return next time                                               # features/step_definitions/user_steps.rb:60
Then I should be signed out  

со следующим шагом_определения для 'I sign out'

Then /^I sign out$/ do
    visit('/users/sign_out')
end

Я много искал и обнаружил, что это связано с тем, что в Rails 3 используется атрибут "data-method", который используется в unbbriveive javascript, но я также где-то читал, что Capybara проверяет атрибуты метода данных и ведет себя соответственно. Но это не сработало для меня, поэтому после этого сообщения Атака Capybara: тесты в стойке, потерянные сеансы и методы http-запросов Я изменил определение шага к следующему:

Then /^I sign out$/ do
    rack_test_session_wrapper = Capybara.current_session.driver
    rack_test_session_wrapper.process :delete, '/users/sign_out'
end

но я получил метод undefined process для Capybara::RackTest::Driver (NoMethodError).

Следуя этому примеру, я изменил определение вышеописанного шага следующим образом:

Then /^I sign out$/ do
    rack_test_session_wrapper = Capybara.current_session.driver
    rack_test_session_wrapper.delete '/users/sign_out'
end

Это, по крайней мере, прошло шаг "Я выхожу", но после его выхода на домашнюю страницу он не перенаправлялся, и следующий шаг не удалось:

And I should see "Signed out"                                         # features/step_definitions/web_steps.rb:105
  expected there to be content "Signed out" in "YasPiktochart\n\n  \n      Signed in as [email protected] Not you?\n      Logout\n  \n\n    Signed in successfully.\n\n  Home\n  User: [email protected]\n\n\n\n" (RSpec::Expectations::ExpectationNotMetError)
  ./features/step_definitions/web_steps.rb:107:in `/^(?:|I )should see "([^"]*)"$/'
  features/users/sign_out.feature:11:in `And I should see "Signed out"'

После этого мне пришлось прибегнуть к добавлению метода GET для выхода из файла маршрутов:

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

изменил мое представление из

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

к

<%= link_to "Logout", logout_path %>

и изменил мое определение шага на следующее:

Then /^I sign out$/ do
    visit('/logout')
end

Это, очевидно, решило все проблемы, все пройденные тесты и firebug не отображали ошибок на sign_out. Но я знаю, что использование запроса "get" для уничтожения сеансов не является хорошей практикой, потому что это изменение состояния.

Может ли это быть из-за конкретной версии или Rails, Devise, Cucumber-Rails или Capybara, которые я использую? Я хочу использовать Devise default sign_out route вместо того, чтобы переопределять его методом get и иметь возможность делать BDD с использованием Cucumber и RSpec. Я новичок в использовании Cucumber + Capybara, существует ли другой метод отправки запроса POST вместо использования "visit ('/users/sign_out')", который использует только метод GET?

4b9b3361

Ответ 1

Итак, я обнаружил, что

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

Помощник rails генерирует следующий html

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

и jquery_ujs.js имеет следующий метод для преобразования ссылок с атрибутом data-method = "delete" в форму и отправки во время выполнения:

// 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();
}

И помощник помощника Capybara ('/users/sign_out') просто щелкает по ссылке и отправляет запрос GET на сервер, у которого нет маршрута для этого запроса.

В отличие от link_to helper, помощник button_to добавляет требуемую форму в html, когда страница отображается, вместо того, чтобы полагаться на javascript:

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

генерирует следующий 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>

с этим я могу легко использовать помощник Capybara click_button ( "Выход" ) в определении шага "Я выхожу".

"link_to с помощью метода ничего, кроме GET, на самом деле является плохой идеей, поскольку ссылки можно щелкнуть правой кнопкой мыши и открыть в новой вкладке/окне, а потому это просто копирует URL-адрес (а не метод), который он будет разорвать для не-ссылок на ссылки..."

Как Max Will объяснил щелчок правой кнопкой мыши и открытие ссылки link_to с не-get data-method в новой вкладке приводит к неработающей ссылке.

Более полезное обсуждение ссылки на link_to с ключом: метод = > : удалить 'и проблему capybara можно найти по этой ссылке

На данный момент я буду придерживаться простого link_to helper без атрибута method и предпочитаю использовать button_to, если я хочу переключиться на метод non-get для удаления.

В то же время я думаю, что должен быть помощник capybara, эквивалентный Visit, чтобы обслуживать атрибут data-method для отправки post-запроса, чтобы можно было избежать использования javascript-драйвера для тестирования интеграции. Может быть, уже есть тот, о котором я не знаю. Исправьте меня, если я ошибаюсь.

Ответ 2

Самый простой способ исправить эту проблему (хотя, вероятно, и не самый правильный) - это изменить файл маршрутов в соответствии с остальной частью приложения. Например. сделайте GET версию destroy_user_session_path. Вы можете сделать это, изменив файл маршрутов следующим образом

Удалить

devise_for :users

Добавить

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

Это немного грязно. Я уверен, что Devise не рекомендовал маршрут GET. Тем не менее, исправление его каким-либо другим способом за пределами моего знания огурца на данный момент, так как каждый тест в этом пакете в конечном итоге зависит от посещения ('/users/logout'), который просто невозможно с готовым дизайном маршруты.

UPDATE

Вы также можете исправить это, комментируя следующее в config/initialers/devise.rb

#config.sign_out_via = :delete

Ответ 3

Devise 1.4.1 (27 июня 2011 г.) изменило поведение по умолчанию для запросов на выход:

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

Хосе Валим объяснил, почему: "Запросы GET не должны изменять состояние сервера. Когда выдается запрос GET, CSRF может использоваться для автоматического подписания вами, а вещи, которые предварительно загружают ссылки, могут в конечном итоге вывести вас по ошибке как хорошо".

Cucumber хочет протестировать GET-запросы, а не DELETE-запросы для destroy_user_session_path. Если вы собираетесь использовать Cucumber с Devise, измените значение Devise по умолчанию от DELETE до GET для тестовой среды Rails только с этим изменением на config/initializers/devise.rb:

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

Не пытайтесь настроить файл route.rb для исправления. Это не обязательно. Если вы не собираетесь использовать Cucumber, оставьте Devise new default (DELETE) на месте.

Пример исходного кода здесь:

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

теперь включает изменение инициализатора Devise для огурца.

Шаблон приложения здесь:

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

теперь обнаруживает столкновение между Devise и Cucumber и при необходимости изменяет инициализатор Devise.

Эти изменения были протестированы с помощью Rails 3.1.0.rc4, но поведение должно быть одинаковым с Rails 3.0.9. Пожалуйста, добавьте здесь комментарии, если проблема не решена или у вас есть дополнительная информация.

Ответ 4

Правильный способ решения этой проблемы объясняется на странице вики-разработки: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

В принципе, как только вы включили в свой файл user_step.rb:

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

Вы можете заменить visit '/users/sign_out' на logout(:user)

Ответ 5

У меня на самом деле такая же проблема, но с приложением Rails/Sinatra. У меня есть Devise для Rails, и выход из системы работает. У меня есть приложение GuestApp Sinatra, работающее в lib, которое отлично работает, за исключением ссылки выхода. Я пытаюсь заставить data-method = "удалить" ссылку на выход sinatra, но ничего не сделает запрос на удаление.

Я думаю, что это может вызвать у меня проблему с синатрой, но я подумал, что когда-либо запрашиваемые запросы обрабатываются маршрутами рельсов сначала, пока они не достигнут моего маршрута синатра. Я собираюсь вручную добавить маршрут GET для выхода из системы, но я бы не стал этого делать.

Здесь мои маршруты разработки:

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

Здесь моя ссылка:

%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>

Ответ 6

Когда мне нужно использовать что-то вроде этого в test.env:

  visit destroy_user_session_path

он работает для меня, но, возможно, это неправильно)

конфигурации/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