使控制器方法可供查看,但略有不同?

Making controller methods available to view, but slightly differently?

在我的 header 中,我有一个 link 到我不希望任何人访问的材料页面,所以我需要在我的视图中设置一个条件和一个 before_filter 在我的 MaterialsController 中。

我为视图编写了一个成功的辅助方法,但本着 DRY 的精神,我只想在 ApplicationController 中编写该方法一次,并使用 helper_method:[=14 使其对视图可用=]

应用程序控制器:

helper_method :user_is_admin_or_teacher_or_student_with_a_class

def user_is_admin_or_teacher_or_student_with_a_class
  if user_signed_in? and ( current_user.admin || current_user.type == "Teacher" || ( (current_user.type == "Student") and current_user.groups.any? ) )
  else redirect_to root_path, alert: "You are not authorised to view the Materials page."
  end
end

这在我的 MaterialsController 中完美运行:

before_action :user_is_admin_or_teacher_or_student_with_a_class, only: [:index, :show]

达到了预期的效果。

转到辅助方面,我把它放在我的视图中 (_header.html.erb):

<% if user_is_admin_or_teacher_or_student_with_a_class %>
  <li><%= link_to "Materials", materials_path %></li>
<% end %>

但是当尝试在浏览器中加载我的主页时,我收到 'this page has a redirect loop' 浏览器错误。我认为这与控制器方法中的 redirect_to root_path 命令有关。

我的粗略解决方案是从 ApplicationController 中删除 helper_method 声明并在 ApplicationHelper 中编写一个几乎相同的方法:

def user_is_admin_or_teacher_or_student_with_a_class?
    user_signed_in? and ( current_user.admin || current_user.type == "Teacher" || ( (current_user.type == "Student") and current_user.groups.any? ) )
end

这行得通,但不是 DRY。我怎样才能把它擦干并只编写一次方法并在控制器和视图中使用它?

您 root_path 指向您正在重定向的同一控制器。更改重定向路径或您的根路径

# routes.rb
Rails.application.routes.draw do
  root change_whatevers_here
end

redirect_to change_whatevers_here, alert: "You are not authorised to view the Materials page."

我会拆分逻辑。你可以把这个方法放在你的模型中(我假设它是用户):

class User < ActiveRecord::Base
  # ...

  def can_view_materials?
    # note no need for parentheses here if '&&' is used instead of 'and' operator
    admin || type == "Teacher" || type == "Student" && groups.any?
  end

  # ...
end    

然后在 MaterialsController:

before_action :require_authorization_to_view_materials, only: [:index, :show]

def require_authorization_to_view_materials
  unless user_signed_in? && current_user.can_view_materials?
    redirect_to root_path, alert: "You are not authorised to view the Materials page."
  end
end

最后,在您看来:

<% if user_signed_in? && current_user.can_view_materials? %>
  <li><%= link_to "Materials", materials_path %></li>
<% end %>

这只是您方法的完善版本。可以通过引入额外的授权逻辑、用户角色等其他一些可能更好的方法来实现。但这完全取决于您的解决方案的复杂程度以及您是否真的需要它。

请注意,控制器方法中没有辅助方法 ;)

如果您真的想创建一个 controller/view 通用方法来检查用户权限,您可以在 ApplicationController:

中执行此操作
helper_method :user_can_view_materials?
def user_can_view_materials?
  user_signed_in? && current_user.can_view_materials?
end

并在 MaterialsController 中:

def require_authorization_to_view_materials
  redirect_to root_path, alert: "You are not authorised to view the Materials page." unless user_can_view_materials?
end

并且在视图中:

<% if user_can_view_materials? %>
  <li><%= link_to "Materials", materials_path %></li>
<% end %>