Rails 3: Как «redirect_to» в вызове Ajax?

85

Следующий attempt_loginметод вызывается с использованием Ajax после отправки формы входа.

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

Проблема в том, что redirect_toэто не работает.

Как бы вы это решили?

Миша Морошко
источник

Ответы:

102

Наконец, я просто заменил

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

с этим:

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

и работает нормально!

Миша Морошко
источник
43
Лучше было быrender :js => "window.location = '#{jobs_path}'"
zakelfassi
3
Это работает, но не лучше ли было бы передать обратно местоположение перенаправления с фактическим сообщением об успешном выполнении json и выполнить перенаправление на внешнем интерфейсе?
Justinxreese
1
Не jobs_pathтакой уж жесткий, как URL? Если URL-адрес изменится, изменится и имя маршрута, если только вы не проявите особую осторожность. Другой альтернативой было бы render js: "window.location = '#{polymorphic_path(@job.class)}'"использование рассчитанного ресурсного маршрута на основе модели Job. Это работает, только если ваши маршруты изобретательны и используют стандартные соглашения об именах, которые соответствуют вашим моделям. (Или если вы укажете model_name на своих моделях, чтобы они генерировали правильные имена маршрутов.)
smudge
2
Потрясающие. Кто-нибудь знает, почему не работает простой redirect_to?
Tasos Anesiadis
1
@Tasos Anesiadis, redirect_to не работает, когда форма является «удаленной» формой Rails, потому что браузеру было сказано интерпретировать ответ от контроллера как Javascript. Вы можете увидеть страницу redirect_to на вкладке Response (через панель Network) Chrome DevTools, но вместо этого требуется инструкция для браузера от контроллера, чтобы найти другую страницу. Приведенные здесь решения window.location или изменение формы на обычную «локальную» форму необходимы, если вы не хотите вручную отправлять и обрабатывать данные формы с помощью fetch () и JSON.
MSC
67

Есть очень простой способ сохранить вспышку для следующего запроса. В вашем контроллере сделайте что-то вроде

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

flash.keepБудет убедиться , что вспышка сохраняется для следующего запроса. Поэтому при root_pathрендеринге будет отображаться данное флэш-сообщение. Rails - это круто :)

Натанвда
источник
28

Думаю, это немного приятнее:

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

Майк
источник
12
немного лучше:render js: "window.location.pathname = #{jobs_path.to_json}"
tokland
26

В одном из своих приложений я использую JSON для перенаправления и передачи данных флэш-сообщений. Это выглядело бы примерно так:

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

И простой пример jQuery:

$.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
Здесь много полезной информации. Хорошее и правильное использование render: location, опции: status и xhr? проверять. Поскольку все больше веб-приложений используют API-интерфейсы для обслуживания мобильных приложений и т. П., Я надеюсь, что в этом посте все станет более стандартизированным. Определенно получил мой голос. Отличный ответ
TheJKFever
18

Сочетание лучшего из ответов:

...
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
...
Ярин
источник
Спасибо за ответ, пользовался. Однако теперь для тестирования, когда я пытаюсь запросить это действие как JS, возникает предупреждение CORS: ActionController :: InvalidCrossOriginRequest. У вас есть идеи, как интегрировать это в тесты?
В. Дехай
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
источник
0

Я не хотел изменять действия своего контроллера, поэтому я придумал этот хак:

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
источник