Rails 3: Como “redirect_to” em uma chamada Ajax?

85

O attempt_loginmétodo a seguir é chamado usando Ajax depois que um formulário de login é enviado.

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

O problema é que redirect_toisso não funciona.

Como você resolveria isso?

Misha Moroshko
fonte

Respostas:

102

Finalmente, acabei de substituir

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

com isso:

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

e funciona bem!

Misha Moroshko
fonte
43
Uma abordagem melhor seriarender :js => "window.location = '#{jobs_path}'"
zakelfassi
3
Funciona, mas não seria muito melhor passar de volta o local de redirecionamento com uma mensagem de sucesso json real e fazer o redirecionamento no front end?
justinxreese
1
Não é jobs_pathbasicamente tão rígido quanto o URL? Se o URL mudar, o mesmo acontecerá com o nome da rota, a menos que você esteja sendo extremamente cuidadoso. Outra alternativa seria render js: "window.location = '#{polymorphic_path(@job.class)}'"usar a rota calculada com base no modelo de trabalho. Isso só funciona se suas rotas forem engenhosas e usarem convenções de nomenclatura padrão que se alinham com seus modelos. (Ou se você especificar model_name em seus modelos para que eles gerem os nomes de rota corretos.)
smudge
2
Impressionante. Alguém tem ideia do porque o simples redirect_to não funciona?
Tasos Anesiadis
1
@Tasos Anesiadis, redirect_to não funciona quando o formulário é um formulário Rails 'remoto' porque o navegador foi instruído a interpretar a resposta do controlador como Javascript. Você pode ver a página redirect_to na guia Response (através do painel Network) do Chrome DevTools, mas o que é necessário é uma instrução para o navegador do controlador ir encontrar uma página diferente. As soluções window.location fornecidas aqui ou alterar o formulário para um formulário 'local' regular são necessárias, a menos que você queira enviar e processar manualmente os dados do formulário por meio de fetch () e JSON.
MSC
68

Existe uma maneira muito fácil de guardar o flash para a próxima solicitação. Em seu controlador, faça algo como

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

o flash.keep irá certificar-se o flash é mantido para o próximo pedido. Portanto, quando o root_pathfor renderizado, ele mostrará a mensagem flash fornecida. Rails é incrível :)

nathanvda
fonte
28

Acho que é um pouco melhor:

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

Mike
fonte
12
um pouco melhor:render js: "window.location.pathname = #{jobs_path.to_json}"
tokland
26

Em um dos meus aplicativos, eu uso JSON para realizar o redirecionamento e os dados de mensagem flash. Seria mais ou menos assim:

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

E um exemplo simples de jQuery seria:

$.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
fonte
1
Muita informação boa aqui. Uso bom e adequado do render: location, da opção: status e do xhr? Verifica. À medida que mais aplicativos web adotam APIs para servir aplicativos móveis e coisas do gênero, espero ver as coisas neste post se tornarem mais padronizadas. Definitivamente, tenho meu voto positivo. Ótima resposta
TheJKFever
18

Combinando o melhor de todas as respostas:

...
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
fonte
Obrigado pela sua resposta, eu usei. Porém, agora para o teste, quando tento solicitar esta ação como JS, surge um aviso do CORS: ActionController :: InvalidCrossOriginRequest. Você tem alguma ideia de como integrar isso nos testes?
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
fonte
0

Eu não queria modificar as ações do meu controlador, então inventei este hack:

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
fonte