设计 - 确认后重定向页面

时间:2011-06-27 22:17:03

标签: ruby-on-rails-3 devise

假设用户单击指向受保护页面的链接。然后将它们重定向到登录屏幕,然后登录。如果有,则成功重定向到该页面。但如果他们没有帐户,他们必须注册。这是事情变得棘手的地方,因为我正在做电子邮件确认。

通过单击链接创建新会话,我无法自动将用户重定向到该受保护页面。我试图通过在确认链接中添加对重定向的引用来更改它。我想这样做:

<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token, :redirect_to => stored_location_for(@resource)) %>

但我无法弄清楚如何访问stored_location_for(或者如果这是正确的位置)。它在Devise::Controllers::Helpers中定义,但它是一个实例方法,所以我不能Devise::Controllers::Helpers.stored_location_for(…)

我的问题是如何访问stored_location_for或者更好的方法是什么?

我的目标是这样做,然后在我的自定义ConfirmationsController中定义:

def show
  if params[:redirect_to]
    session["user_return_to"] = params[:redirect_to]
  end
  super

end

这应该正常吗?

3 个答案:

答案 0 :(得分:12)

好的,我终于明白了。我不确定这与Devise昨天做的更新是否发生了变化,使得Devise :: Mailer将其大部分功能放入模块中(参见代码here和票证here)。

基本上归结为无法访问邮件程序视图中的session。因此,您必须将重定向作为变量传递。 Devise在您的资源上使用after_create方法(在我的情况下为User),然后发送确认电子邮件。这意味着我不能直接将会话变量传递给邮件程序。因此,我觉得这是一个非常讨厌的工作,以获得此功能。但这是代码。

因此,要将redirect_to变量放入邮件程序,您必须向用户添加变量。因此:

class User < ActiveRecord::Base
  …
  attr_accessor :return_to
  …
end

然后,您必须在第一次创建用户时设置该变量。我已经有了一个用于注册的自定义控制器设置(请参阅设备自述文件,了解如何设置它,或者查看@ ramc的方向答案)。但是做这个部分相对容易,我只是将它添加到参数中,让其余部分自行处理。

class RegistrationsController < Devise::RegistrationsController
  def create
    params[:user][:return_to] = session[:user_return_to] if session[:user_return_to]
    …
    super
  end
end

现在用户已设置变量return_to。我们只需要在confirmation_instructions电子邮件中访问它。我已经重写了confirm_instructions.html.erb的一部分,所以在那里我刚补充说:

<% if @resource.return_to %>
  <%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token, :redirect_to => @resource.return_to) %>
<% else %>
  <%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
<% end %>

(对于那些不熟悉的人,@ resource是Devise用来定义你的用户的变量)。

现在,一旦用户点击该链接,我们就需要重定向它们。过滤前@ ramc的效果很好。

class ConfirmationsController < Devise::ConfirmationsController
  before_filter :set_redirect_location, :only => :show

  def set_redirect_location
    session[:user_return_to] = params[:redirect_to] if params[:redirect_to]
  end
end

这将照顾新用户进入受保护页面,然后注册,点击确认链接并正确重定向到受保护页面的情况。

现在我们只需要处理用户执行上述操作的情况,但不是点击链接,而是尝试返回受保护的页面。在这种情况下,他们会被要求注册/登录。他们登录后会被要求确认他们的电子邮件,并可以选择重新发送确认电子邮件。他们输入了他们的电子邮件,现在我们需要将redirect_to变量放在新的确认电子邮件中。

为此,我们需要修改ConfirmationController,类似于我们执行RegistrationController的方式。这次我们需要修改create方法。开箱即用的方式是在名为send_confirmation_instructions的用户上调用类方法。我们想要重写该方法,以便我们可以将return_to变量传递给它。

class ConfirmationsController < Devise::ConfirmationsController
  def create
    self.resource = resource_class.send_confirmation_instructions(params[resource_name],session[:user_return_to])

    if resource.errors.empty?
      set_flash_message(:notice, :send_instructions) if is_navigational_format?
      respond_with resource, :location => after_resending_confirmation_instructions_path_for(resource_name)
    else
      respond_with_navigational(resource){ render_with_scope :new }
    end
  end
end

唯一不同于设计的东西是第一行创造。我们传入两个变量。现在我们需要重写该方法。

class User < ActiveRecord::Base
  def self.send_confirmation_instructions(attributes={},redirect=nil)
    confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
    confirmable.return_to = redirect if confirmable.persisted?
    confirmable.resend_confirmation_token if confirmable.persisted?
    confirmable
  end
end

confirmable成为用户的实例(当前用户基于电子邮件)。所以我们只需要设置return_to

就是这样。

答案 1 :(得分:10)

在lib / devise / controllers / helpers.rb中实现了stored_location_for的实现方式

def stored_location_for(resource_or_scope)
   scope = Devise::Mapping.find_scope!(resource_or_scope)
   session.delete("#{scope}_return_to")
end

可以使用session ['user_return_to']以其他方式访问它。在您的情况下,您将丢失该会话对象,因为当用户单击确认邮件中的链接时,它可能是一个新生成的会话。

您可以将您建议的任何内容实施为之前的过滤器:

class Users::ConfirmationsController < Devise::ConfirmationsController 
     before_filter :set_redirect_location, :only => :show

     def set_redirect_location
         session["user_return_to"] = params[:redirect_to] if params[:redirect_to]
     end
end

除此之外,您还必须修改路线以使设计调用您的控制器而不是自己的确认控制器。

devise_for :users,
           :controllers => { :confirmations => 'users/confirmations'}

希望这有帮助:)

注意:代码段不完整,仅包含相关详细信息。

答案 2 :(得分:1)

从我在设计源代码中的注释中可以看到,您需要做的就是在registrations_controller.rb中实现以下内容:

def after_inactive_sign_up_path_for(resource_or_scope)
  session["user_return_to"]
end