Rails 3: Jak „redirect_to” w wywołaniu Ajax?

85

Następująca attempt_loginmetoda jest wywoływana przy użyciu Ajax po przesłaniu formularza logowania.

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

Problem w tym, że redirect_toto nie działa.

Jak byś to rozwiązał?

Misha Moroshko
źródło

Odpowiedzi:

102

W końcu właśnie wymieniłem

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

z tym:

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

i działa dobrze!

Misha Moroshko
źródło
43
Lepszym podejściem byłobyrender :js => "window.location = '#{jobs_path}'"
zakelfassi
3
To działa, ale czy nie byłoby lepiej przekazać z powrotem lokalizację przekierowania z rzeczywistym komunikatem json o powodzeniu i wykonać przekierowanie na interfejsie użytkownika?
justinxreese
1
Czy w jobs_pathzasadzie nie jest tak sztywny jak adres URL? Jeśli adres URL ulegnie zmianie, zmieni się także nazwa trasy, chyba że jesteś bardzo ostrożny. Inną alternatywą byłoby render js: "window.location = '#{polymorphic_path(@job.class)}'"skorzystanie z obliczonej, pomysłowej trasy opartej na modelu zadania. Działa to tylko wtedy, gdy trasy są pomysłowe i używają standardowych konwencji nazewnictwa, które są zgodne z modelami. (Lub jeśli określisz model_name na swoich modelach, aby generowały prawidłowe nazwy tras.)
Smudge
2
Niesamowite. Czy ktoś ma pojęcie, dlaczego proste redirect_to nie działa?
Tasos Anesiadis
1
@Tasos Anesiadis, redirect_to nie działa, gdy formularz jest „zdalnym” formularzem Railsów, ponieważ przeglądarka otrzymała polecenie zinterpretowania odpowiedzi ze sterownika jako JavaScript. Możesz zobaczyć stronę redirect_to na karcie Odpowiedź (przez panel Sieć) w Chrome DevTools, ale zamiast tego potrzebna jest instrukcja dla przeglądarki z kontrolera, aby znaleźć inną stronę. Podane tutaj rozwiązania window.location lub zmiana formularza na zwykły formularz „lokalny” są wymagane, chyba że chcesz zająć się ręcznym przesyłaniem i przetwarzaniem danych formularza za pomocą funkcji fetch () i JSON.
MSC
67

Istnieje bardzo łatwy sposób na zachowanie pamięci flash do następnego żądania. W swoim kontrolerze zrób coś takiego

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

flash.keepBędzie upewnić się, że lampa jest przechowywane przez następne żądanie. Więc kiedy root_pathjest renderowany, pokaże podany komunikat flash. Szyny są super :)

nathanvda
źródło
28

Myślę, że to jest trochę przyjemniejsze:

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

Mikrofon
źródło
12
nieco ładniej:render js: "window.location.pathname = #{jobs_path.to_json}"
tokland
26

W jednej z moich aplikacji używam JSON do przenoszenia danych przekierowania i wiadomości flash. Wyglądałoby to mniej więcej tak:

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

Prosty przykład jQuery to:

$.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
źródło
1
Wiele dobrych informacji tutaj. Dobre i właściwe użycie renderowania: lokalizacja opcji: status i xhr? czek. Ponieważ coraz więcej aplikacji internetowych przyjmuje interfejsy API do obsługi aplikacji mobilnych i tym podobnych, mam nadzieję, że sprawy w tym poście staną się bardziej ustandaryzowane. Zdecydowanie popieram. Świetna odpowiedź
TheJKFever
18

Połączenie najlepszych ze wszystkich odpowiedzi:

...
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
źródło
Dzięki za odpowiedź, wykorzystałem ją. Jednak teraz do testów, kiedy próbuję zażądać tej akcji jako JS, generuje ostrzeżenie CORS: ActionController :: InvalidCrossOriginRequest. Czy masz pomysł, jak to zintegrować w testach?
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
źródło
0

Nie chciałem modyfikować moich działań kontrolera, więc wymyśliłem ten 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
źródło