ruby on rails 3 - Rails 3.0.9+Devise+Cucumber+Capybara the infamous “No route match/users/sign_out”
ruby-on-rails-3 rspec (6)
Estoy usando el dispositivo 1.4.2 con rieles 3.0.9, cucumber-rails 1.0.2, capybara 1.0.0. No obtuve el error No route matches "/users/sign_out"
al hacer clic en cerrar sesión. Agregué :method => :delete
to link_to tag después de pasar por esta pregunta ( no-route-matches-users-sign-out-devise-rails-3 ).
Desde que reemplacé el prototipo con jquery, también tuve que cambiar
config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
a
config.action_view.javascript_expansions[:defaults] = %w(jquery jquery_ujs)
para moverse por rails.js no se encuentra el error.
Aunque con los cambios anteriores puedo cerrar la sesión correctamente y redirigirme a la raíz, cuando veo la respuesta de localhost: 3000 / users / sign_out request en FireBug, muestra el mismo mensaje de error de enrutamiento. Haga clic aquí para ver la captura de pantalla con notas.
Después de implementar con éxito la autenticación a la aplicación Rails 3 a través del dispositivo, cuando agregué características y especificaciones usando Cucumber + Capybara + RSpec siguiendo este tutorial ( github.com/RailsApps/rails3-devise-rspec-cucumber/wiki/Tutorial ), obtuve el siguiente error
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
con la siguiente step_definition para ''I out out''
Then /^I sign out$/ do
visit(''/users/sign_out'')
end
Busqué mucho y descubrí que esto se debe a que javascript no intrusivo en Rails 3 se usaba para los atributos del ''método de datos'', pero también leí en alguna parte que Capybara verifica los atributos del método de datos y se comporta en consecuencia. Pero no me funcionó, así que siguiendo este ataque posterior a Capybara: prueba de rack, sesiones perdidas y métodos de solicitud http, cambié mi definición de paso a la siguiente:
Then /^I sign out$/ do
rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.process :delete, ''/users/sign_out''
end
pero tengo un process
método indefinido para Capybara::RackTest::Driver (NoMethodError)
.
Siguiendo este ejemplo, cambié la definición del paso anterior de la siguiente manera:
Then /^I sign out$/ do
rack_test_session_wrapper = Capybara.current_session.driver
rack_test_session_wrapper.delete ''/users/sign_out''
end
Esto, al menos, aprobó el paso "Cerrar sesión", pero no se redirigió a la página de inicio después de cerrar la sesión y el siguiente paso falló:
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"''
Después de todo esto tuve que recurrir a agregar el método ''GET'' para cerrar sesión en el archivo de rutas:
devise_for :users do get ''logout'' => ''devise/sessions#destroy'' end
modificado mi punto de vista de
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
a
<%= link_to "Logout", logout_path %>
y cambié mi definición de paso a la siguiente:
Then /^I sign out$/ do
visit(''/logout'')
end
Esto obviamente resolvió todos los problemas, todas las pruebas pasaron y firebug no mostró ningún error en sign_out. Pero sé que usar la solicitud ''get'' para destruir sesiones no es una buena práctica, porque es un comportamiento que cambia de estado.
¿Podría ser debido a una versión particular o Rails, Devise, Cucumber-Rails o Capybara que estoy usando? Quiero usar la ruta de inicio de sesión por defecto de Devise en lugar de anularla con el método de obtención y poder hacer BDD utilizando Cucumber y RSpec. Soy nuevo en el uso de Cucumber + Capybara, ¿existe otro método para enviar la solicitud POST en lugar de usar "visit (''/ users / sign_out'')", que solo usa el método GET?
Así que he encontrado que
<%= link_to "Logout", destroy_user_session_path, :method => :delete %>
Rails helper genera el siguiente html
<a rel="nofollow" data-method="delete" href="/users/sign_out">Sign out</a>
y jquery_ujs.js tiene el siguiente método para convertir los enlaces con el atributo data-method = "delete" en un formulario y enviarlo en tiempo de ejecución:
// 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();
}
Y la visita del ayudante de Capybara (''/ users / sign_out'') simplemente hace clic en el enlace y envía una solicitud GET al servidor que no tiene ninguna ruta para esta solicitud.
A diferencia de link_to helper, button_to helper agrega el formulario requerido dentro del html cuando la página se representa en lugar de confiar en javascript:
<%= button_to "Logout", destroy_user_session_path, :method => :delete %>
genera siguiente 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>
con esto puedo usar fácilmente Capybara helper click_button (''Logout'') en mi definición de paso ''I log out''.
Como Max Will explicó al hacer clic con el botón derecho y abrir el enlace para enlazar con el método de no obtener datos en una nueva pestaña, se produce un enlace roto.
En este enlace se puede encontrar una discusión más útil sobre link_to helper con '': method =>: delete'' y el problema de la capibara.
Por ahora me atendría a un simple link_to helper sin: atributo de método, y preferiría usar button_to si quiero cambiar a un método de no obtención para eliminar.
Al mismo tiempo, creo que debería haber un ayudante de capibara equivalente a Visitar para atender el atributo de método de datos para enviar solicitudes posteriores, de modo que se pueda evitar el uso de controladores basados en JavaScript para las pruebas de integración. O puede que ya haya uno que no conozco. Corrígeme si estoy equivocado.
Cuando necesito usar algo como esto en test.env :
visit destroy_user_session_path
es un trabajo para mí, pero tal vez esto no sea correcto)
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
De hecho, estoy teniendo el mismo problema pero con una aplicación Rails / Sinatra. He configurado a Devise para Rails y el cierre de sesión funciona. Tengo una aplicación Sinatra de GuestApp que se ejecuta en lib, que funciona muy bien a excepción del enlace de cierre de sesión. Estoy intentando forzar data-method = "delete" en el enlace de cierre de sesión de sinatra, pero nada de lo que haga hará que la solicitud sea una solicitud de eliminación.
Creo que esto podría ser un problema de sinatra para mí, pero pensé que todas las solicitudes que llegan son procesadas primero por rutas de ferrocarril hasta que llegan a mi ruta de sinatra. Estoy a punto de agregar manualmente la ruta GET para el cierre de sesión, pero prefiero no tener que hacerlo.
Aquí están mis rutas de diseño:
devise_for :member, :path => '''', :path_names => {
:sign_in => "login",
:sign_out => "logout",
:sign_up => "register" }
Aquí está mi enlace:
%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>
Devise 1.4.1 (27 de junio de 2011) cambió el comportamiento predeterminado para las solicitudes de cierre de sesión:
https://github.com/plataformatec/devise/commit/adb127bb3e3b334cba903db2c21710e8c41c2b40
José Valim explicó por qué: "Las solicitudes GET no deben cambiar el estado del servidor. Cuando cerrar sesión es una solicitud GET, CSRF se puede usar para cerrar sesión automáticamente y las cosas que precargan los enlaces también pueden cerrar la sesión por error".
Cucumber quiere probar las solicitudes GET, no ELIMINAR las solicitudes de destroy_user_session_path. Si pretende utilizar Cucumber with Devise, cambie el valor predeterminado de Devise de DELETE a GET para el entorno de prueba de Rails solo con este cambio a config/initializers/devise.rb
:
config.sign_out_via = Rails.env.test? ? : get:: delete
No intente modificar el archivo route.rb para realizar la corrección. No es necesario Si no va a utilizar Cucumber, deje el nuevo valor predeterminado de Devise (DELETE) en su lugar.
El código fuente de ejemplo aquí:
Ahora incluye el cambio al inicializador de Devise para pepino.
La plantilla de aplicación aquí:
ahora detecta la colisión entre Devise y Cucumber y modifica el inicializador de Devise según sea necesario.
Estos cambios se probaron con Rails 3.1.0.rc4 pero el comportamiento debería ser el mismo con Rails 3.0.9. Agregue comentarios aquí si el problema no está resuelto o si tiene más información.
La forma correcta de resolver este problema se explica en la página wiki del dispositivo: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara
Básicamente, una vez que haya incluido en su archivo user_step.rb:
include Warden::Test::Helpers
Warden.test_mode!
Puede reemplazar la visit ''/users/sign_out''
con logout(:user)
La forma más fácil de corregir este problema (aunque probablemente no sea la más correcta) es modificar el archivo de rutas para que coincida con el resto de la aplicación. Por ejemplo, hacer que la versión GET de destroy_user_session_path funcione. Puede hacerlo modificando el archivo de rutas de la siguiente manera
Retirar:
devise_for :users
Añadir:
devise_for :users do
get "/users/sign_out" => "devise/sessions#destroy", :as => :destroy_user_session
end
Esto está un poco sucio. Estoy seguro de que Devise desaprobó la ruta GET por una buena razón. Sin embargo, arreglarlo de cualquier otra forma está más allá de mi conocimiento de Pepino en este momento, ya que cada prueba en esa suite depende en última instancia de la visita (''/ users / logout''), lo que simplemente no es posible con el dispositivo listo para usar. rutas
ACTUALIZAR
También puede solucionar esto comentando lo siguiente en config / initialers / devise.rb
#config.sign_out_via = :delete