Rails 3: ¿Cómo "redirect_to" en una llamada Ajax?

85

El siguiente attempt_loginmétodo se llama usando Ajax después de que se envía un formulario de inicio de sesión.

class AccessController < ApplicationController
  [...]
  def attempt_login
    authorized_user = User.authenticate(params[:username], params[:password])

    if authorized_user
      session[:user_id] = authorized_user.id
      session[:username] = authorized_user.username
      flash[:notice] = "Hello #{authorized_user.name}."
      redirect_to(:controller => 'jobs', :action => 'index')
    else
      [...]
    end
  end
end

El problema es que eso redirect_tono funciona.

Como resolverias esto ?

Misha Moroshko
fuente

Respuestas:

102

Finalmente, acabo de reemplazar

redirect_to(:controller => 'jobs', :action => 'index')

con este:

render :js => "window.location = '/jobs/index'"

¡y funciona bien!

Misha Moroshko
fuente
43
Un mejor enfoque seríarender :js => "window.location = '#{jobs_path}'"
zakelfassi
3
Funciona, pero ¿no sería mucho mejor devolver la ubicación de redireccionamiento con un mensaje de éxito de json real y hacer el redireccionamiento en la interfaz?
justinxreese
1
¿No es jobs_pathbásicamente tan rígido como la URL? Si la URL cambia, también lo hará el nombre de la ruta, a menos que tenga mucho cuidado. Otra alternativa sería render js: "window.location = '#{polymorphic_path(@job.class)}'"utilizar la ruta ingeniosa calculada basada en el modelo de trabajo. Esto solo funciona si sus rutas son ingeniosas y usan convenciones de nomenclatura estándar que se alinean con sus modelos. (O si especifica nombre_del_modelo en sus modelos de modo que generen los nombres de las rutas correctas.)
mancha
2
Increíble. ¿Alguien tiene alguna idea de por qué el simple redirect_to no funciona?
Tasos Anesiadis
1
@Tasos Anesiadis, redirect_to no funciona cuando el formulario es un formulario de Rails 'remoto' porque se le ha dicho al navegador que interprete la respuesta del controlador como Javascript. Puede ver la página redirect_to en la pestaña Respuesta (a través del panel Red) de Chrome DevTools, pero lo que se necesita en su lugar es una instrucción para el navegador desde el controlador para buscar una página diferente. Se requieren las soluciones de window.location que se proporcionan aquí, o cambiar el formulario a un formulario 'local' regular, a menos que desee enviar y procesar manualmente los datos del formulario a través de fetch () y JSON.
MSC
67

Existe una forma muy sencilla de mantener el flash para la próxima solicitud. En tu controlador haz algo como

flash[:notice] = 'Your work was awesome! A unicorn is born!'
flash.keep(:notice)
render js: "window.location = '#{root_path}'"

Se flash.keepasegurará de que el flash se mantenga para la próxima solicitud. Entonces, cuando root_pathse procese, mostrará el mensaje flash dado. Rails es increíble :)

nathanvda
fuente
28

Creo que esto es un poco mejor:

render js: "window.location.pathname='#{jobs_path}'"

Miguel
fuente
12
un poco más agradable:render js: "window.location.pathname = #{jobs_path.to_json}"
tokland
26

En una de mis aplicaciones, uso JSON para llevar a cabo la redirección y los datos de los mensajes flash. Se vería así:

class AccessController < ApplicationController
  ...
  def attempt_login
    ...
    if authorized_user
      if request.xhr?
        render :json => {
          :location => url_for(:controller => 'jobs', :action => 'index'),
          :flash => {:notice => "Hello #{authorized_user.name}."}
        }
      else
        redirect_to(:controller => 'jobs', :action => 'index')
      end
    else
      # Render login screen with 422 error code
      render :login, :status => :unprocessable_entity
    end
  end
end

Y un ejemplo simple de jQuery sería:

$.ajax({
  ...
  type: 'json',
  success: functon(data) {
    data = $.parseJSON(data);
    if (data.location) {
      window.location.href = data.location;
    }
    if (data.flash && data.flash.notice) {
      // Maybe display flash message, etc.
    }
  },
  error: function() {
    // If login fails, sending 422 error code sends you here.
  }
})
Priit
fuente
1
Mucha buena información aquí. Buen y correcto uso del render: location, de la opción: status y del xhr? cheque. A medida que más aplicaciones web adopten API para servir aplicaciones móviles y demás, espero que las cosas en esta publicación se vuelvan más estandarizadas. Definitivamente tengo mi voto a favor. Gran respuesta
TheJKFever
18

Combinando la mejor de todas las respuestas:

...
if request.xhr?
  flash[:notice] = "Hello #{authorized_user.name}."
  flash.keep(:notice) # Keep flash notice around for the redirect.
  render :js => "window.location = #{jobs_path.to_json}"
else
...
Yarin
fuente
Gracias por tu respuesta, lo usé. Sin embargo, ahora para la prueba, cuando intento solicitar esta acción como JS, aparece una advertencia de CORS: ActionController :: InvalidCrossOriginRequest. ¿Tiene alguna idea de cómo integrar esto en las pruebas?
V. Déhaye
1
def redirect_to(options = {}, response_status = {})
  super(options, response_status)
  if request.xhr?
    # empty to prevent render duplication exception
    self.status = nil
    self.response_body = nil
    path = location
    self.location = nil

    render :js => "window.location = #{path.to_json}"
  end
end
OlegZ
fuente
0

No quería modificar las acciones de mi controlador, así que se me ocurrió este truco:

class ApplicationController < ActionController::Base
  def redirect_to options = {}, response_status = {}
    super

    if request.xhr?
      self.status        = 200
      self.response_body = "<html><body><script>window.location.replace('#{location}')</script></body></html>"
    end
  end
end
Macario
fuente