为什么逻辑上相同的两个方法不执行相同的任务?

时间:2016-03-22 22:49:35

标签: ruby-on-rails

在Rails应用程序中,我的应用程序控制器中有两个方法。其中一个对用户进行身份验证(即检查用户是否已登录,如果没有则将其重定向到登录)。另一个执行该任务的前半部分(即检查用户是否已登录)。

检查完成后,如果用户已登录,则这两种方法都应该使用@current_user为当前登录的用户的用户对象设置User.find变量。

前者:

protected
def authenticate_user
  if session[:user_id]
     # set current user object to @current_user object variable
    @current_user = User.find session[:user_id]
    return true
  else
    flash[:notice] = "You must log in first."
    flash[:color] = "invalid"
    redirect_to(:controller => 'sessions', :action => 'login')
    return false
  end
end

后者:

def check_login_status
  if session[:user_id]
    @current_user = User.find session[:user_id]
    return true
  end
end

如您所见,每种方法的前半部分的逻辑是相同的。但是,authenticate_user正确设置了@current_user变量; check_login_status根本没有设置它(例如,布局文件中的检查显示为@current_user.nil? == true

这是布局文件的相关部分:

<% if not @current_user.nil? %>
  Logged in as <%= @current_user.username %> —
  <a href="/logout">log out</a> —
  <a href="/dashboard">dashboard</a> —
  <a href="/contacts">contacts</a> —
  <a href="/help">help</a>
  <% if @current_user.is_admin %>
    — <a href="/admin">admin</a>
  <% end %>
<% else %>
  <a href="/login">log in</a> —
  <a href="/sign-up">sign up</a> —
  <a href="/help">help</a>
<% end %>

我显示了第二组链接,表明我已退出。

那么,为什么后者没有正确设置@current_user变量呢?它是否与protected标记有关(虽然不知怎的,我怀疑它)?

1 个答案:

答案 0 :(得分:1)

我想说最可能的解释是check_login_status根本没有被调用,因为两者在逻辑上是等价的。但是两者都重复相同的认证逻辑!

如果你坚持重新发明授权轮(除非是为了学习目的),你应该避免在你的控制器和视图中传播认证逻辑。

而是使用帮助程序模块创建一个简单的API进行身份验证。该模块应该是应用程序中唯一知道用户如何存储在会话中的部分:

module AuthorizationHelper
  def current_user
     return nil unless session[:user_id]
     # conditional assignment so DB is only queried once!
     @current_user ||= User.find(session[:user_id])
  end

  def sign_in!(user)
     reset_session
     session[:user_id] = user.id
     @current_user = user
  end

  def sign_out!(user)
     reset_session
     @current_user = nil
  end

  def signed_in?
    current_user.present?
  end
end

现在我们只在ApplicationController中包含帮助器。

class ApplicationController
  include AuthorizationHelper
  # ...
end

我们还希望以可重复和可扩展的方式确保授权。执行此操作的一种好方法是引发异常并使用rescue_from缓存它。

让我们创建自己的错误类:

class User < ActiveRecord::Base
  class AuthorizationError < StandardError; end
end 

让我们添加一个授权方法:

module authorizationHelper
  # ..
  def authorize!
    raise User::AuthorizationError unless signed_in?
  end
end

现在我们可以在控制器中使用它:

class ThingsController < ApplicationController
  before_action :authorize!
end

然而它不是很有用,因为它只是导致应用程序崩溃!让救援异常:

class ApplicationController
  include AuthorizationHelper
  rescue_from User::AuthorizationError, with: :deny_access

  def deny_access
    redirect_to(controller: 'sessions', action: 'login') and return
  end
end