Rails 3: Come "redirect_to" in Ajax call?


85

Il attempt_loginmetodo seguente viene richiamato utilizzando Ajax dopo l'invio di un modulo di accesso.

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

Il problema è che redirect_tonon funziona.

Come risolveresti questo problema?

Risposte:


102

Infine, ho appena sostituito

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

con questo:

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

e funziona bene!


43
Un approccio migliore sarebberender :js => "window.location = '#{jobs_path}'"
zakelfassi

3
Funziona, ma non sarebbe molto meglio restituire la posizione di reindirizzamento con un messaggio di successo json effettivo e fare il reindirizzamento sul front-end?
justinxreese

1
jobs_pathFondamentalmente non è rigido come l'URL? Se l'URL cambia, cambia anche il nome del percorso, a meno che tu non stia facendo molta attenzione. Un'altra alternativa sarebbe render js: "window.location = '#{polymorphic_path(@job.class)}'"e utilizzare il percorso pieno di risorse calcolato in base al modello di lavoro. Funziona solo se i tuoi percorsi sono pieni di risorse e usi convenzioni di denominazione standard che si allineano con i tuoi modelli. (O se specifichi nome_modello sui tuoi modelli in modo che generino i nomi di percorso corretti.)
sfumare il

2
Eccezionale. Qualcuno ha idea del perché il semplice redirect_to non funziona?
Tasos Anesiadis

1
@Tasos Anesiadis, redirect_to non funziona quando il form è un form Rails "remoto" perché al browser è stato detto di interpretare la risposta del controller come Javascript. Puoi vedere la pagina redirect_to nella scheda Response (tramite il pannello Network) di Chrome DevTools ma ciò che serve invece è un'istruzione al browser dal controller per andare a trovare una pagina diversa. Sono richieste le soluzioni window.location fornite qui o la modifica del modulo in un normale modulo "locale", a meno che non si desideri inviare ed elaborare manualmente i dati del modulo tramite fetch () e JSON.
MSC

67

C'è un modo molto semplice per mantenere il flash per la richiesta successiva. Nel tuo controller fai qualcosa di simile

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

Si flash.keepassicurerà che il flash venga conservato per la richiesta successiva. Quindi, quando root_pathviene renderizzato, mostrerà il messaggio flash fornito. Rails è fantastico :)


28

Penso che questo sia leggermente più carino:

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


12
leggermente più render js: "window.location.pathname = #{jobs_path.to_json}"
carino

26

In una delle mie app, utilizzo JSON per continuare il reindirizzamento e i dati dei messaggi flash. Sarebbe simile a questo:

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 un semplice esempio di jQuery sarebbe:

$.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.
  }
})

1
Molte buone informazioni qui. Uso corretto e corretto del rendering: location, dell'opzione: status e xhr? dai un'occhiata. Man mano che sempre più applicazioni web adottano API per servire app mobili e simili, spero di vedere le cose in questo post diventare più standardizzate. Sicuramente ho il mio voto positivo. Ottima risposta
TheJKFever

18

Combinando la migliore di tutte le risposte:

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

Grazie per la tua risposta, l'ho usata. Tuttavia, ora per il test, quando provo a richiedere questa azione come JS, viene generato un avviso CORS: ActionController :: InvalidCrossOriginRequest. Hai idea di come integrarlo nei test?
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

0

Non volevo modificare le azioni del controller, quindi ho pensato a questo trucco:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.