Schienen 3: Wie kann man im Ajax-Aufruf "redirect_to"?

85

Die folgende attempt_loginMethode wird mit Ajax aufgerufen, nachdem ein Anmeldeformular gesendet wurde.

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

Das Problem ist, dass redirect_todas nicht funktioniert.

Wie würden Sie das lösen?

Mischa Moroshko
quelle

Antworten:

102

Schließlich habe ich gerade ersetzt

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

mit diesem:

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

und es funktioniert gut!

Mischa Moroshko
quelle
43
Ein besserer Ansatz wärerender :js => "window.location = '#{jobs_path}'"
zakelfassi
3
Es funktioniert, aber wäre es nicht viel besser, den Umleitungsort mit einer tatsächlichen JSON-Erfolgsmeldung zurückzugeben und die Umleitung am Frontend durchzuführen?
Justinxreese
1
Ist im jobs_pathGrunde nicht so starr wie die URL? Wenn sich die URL ändert, ändert sich auch der Name der Route, es sei denn, Sie sind besonders vorsichtig. Eine andere Alternative wäre render js: "window.location = '#{polymorphic_path(@job.class)}'"und die berechnete einfallsreiche Route basierend auf dem Jobmodell zu verwenden. Dies funktioniert nur, wenn Ihre Routen einfallsreich sind und Standard-Namenskonventionen verwenden, die mit Ihren Modellen übereinstimmen. (Oder wenn Sie model_name auf Ihren Modellen angeben, damit sie die richtigen Routennamen generieren.)
Fleck
2
Genial. Hat jemand eine Idee, warum das einfache redirect_to nicht funktioniert?
Tasos Anesiadis
1
@Tasos Anesiadis, redirect_to funktioniert nicht, wenn das Formular ein 'Remote'-Rails-Formular ist, da der Browser angewiesen wurde, die Antwort des Controllers als Javascript zu interpretieren. Sie können die Seite redirect_to auf der Registerkarte Antwort (über das Netzwerkbedienfeld) von Chrome DevTools sehen. Stattdessen wird vom Browser eine Anweisung an den Browser benötigt, eine andere Seite zu suchen. Die hier bereitgestellten window.location-Lösungen oder das Ändern des Formulars in ein reguläres 'lokales' Formular sind erforderlich, es sei denn, Sie möchten die Formulardaten manuell über fetch () und JSON senden und verarbeiten.
MSC
65

Es gibt eine sehr einfache Möglichkeit, den Blitz für die nächste Anfrage zu behalten. Machen Sie in Ihrem Controller so etwas wie

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

Dadurch flash.keepwird sichergestellt, dass der Blitz für die nächste Anforderung beibehalten wird. Wenn das root_pathgerendert wird, wird die angegebene Flash-Nachricht angezeigt. Rails ist großartig :)

Nathanvda
quelle
27

Ich finde das etwas schöner:

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

Mike
quelle
12
etwas etwas schöner:render js: "window.location.pathname = #{jobs_path.to_json}"
tokland
26

In einer meiner Apps verwende ich JSON, um die Umleitungs- und Flash-Nachrichtendaten fortzusetzen. Es würde ungefähr so ​​aussehen:

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

Ein einfaches jQuery-Beispiel wäre:

$.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
quelle
1
Viele gute Informationen hier. Gute und ordnungsgemäße Verwendung des Render: location, der Option: status und des xhr? prüfen. Da immer mehr Webanwendungen APIs für mobile Apps und dergleichen verwenden, hoffe ich, dass die Dinge in diesem Beitrag standardisierter werden. Habe auf jeden Fall meine Gegenstimme. Tolle Antwort
TheJKFever
18

Die besten aller Antworten kombinieren:

...
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
quelle
Danke für deine Antwort, ich habe sie benutzt. Wenn ich jetzt zum Testen versuche, diese Aktion als JS anzufordern, wird eine CORS-Warnung ausgegeben: ActionController :: InvalidCrossOriginRequest. Haben Sie eine Idee, wie Sie dies in Tests integrieren können?
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
quelle
0

Ich wollte meine Controller-Aktionen nicht ändern, also habe ich mir diesen Hack ausgedacht:

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
quelle