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?
Ответы
Ответ 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