如何在Rails 4上给定用户角色隐藏部分视图

时间:2013-07-29 20:12:56

标签: ruby-on-rails views admin helpers

我正在尝试隐藏部分视图,具体取决于用户角色。

因此,假设我只希望管理员能够销毁产品。除了控制器中用于防止常规用户销毁记录的代码外,我还会在视图中执行以下操作:

<% if current_user.admin? %>
  <%= link_to 'Delete', product, method: :delete %>
<% end %>

以前的代码有效,但是容易出现遗漏错误,这可能会导致普通用户看到不允许执行的操作的链接。

此外,如果我稍后决定新角色(例如“版主”)可以删除产品,我将不得不找到显示删除链接的视图,并添加允许版主查看的逻辑。

如果有很多模型只能由管理员用户删除(例如推广,用户),那么所有ifs的维护都会非常具有挑战性。

有更好的方法吗?也许使用帮手,或类似的东西?我正在寻找像这样的东西:

<%= destroy_link 'Delete', product %> # Only admins can see it
<%= edit_link 'Edit', promotion %> # Again, only admins see this link
<%= show_link 'Show', comment %> # Everyone sees this one

我发现这两个问题与我的相似,但没有人回答我的问题:

Show and hide based on user role in rails

Ruby on Rails (3) hiding parts of the view

4 个答案:

答案 0 :(得分:6)

我强烈推荐 pundit

它允许您为每个模型创建“策略”。对于Product模型,您的ProductPolicy可能看起来像这样

class ProductPolicy < ApplicationPolicy
  def delete?
    user.admin?
  end
end

在你看来,你可以做这样的事情

<% if policy(@post).delete? %>
  <%= link_to 'Delete', product, method: :delete %>
<% end %>

如果您希望稍后添加moderator角色,只需修改政策方法

即可
class ProductPolicy < ApplicationPolicy
  def delete?
    user.admin? || user.moderator?
  end
end

答案 1 :(得分:1)

CanCan是另一个宝石,可让您为每个用户角色定义“能力”。 在视图中,您可以使用if can? :delete, @post之类的内容来检查 用户可以删除该特定帖子。

答案 2 :(得分:1)

所以我想出了一种将IF移出视图的方法。首先,我覆盖了application_helper.rb中的link_to帮助器:

def link_to(text, path, options={})
  super(text, path, options) unless options[:admin] and !current_user.admin?
end

然后在我的观点中,我将其用作:

<%= link_to 'Edit Product', product, admin: true, ... %>

这可以防止普通用户看到管理员链接,但对于其他内容包含内容的html标签,例如div,表等,仍然需要if。

答案 3 :(得分:0)

使用CanCan和角色宝石,还需要一种检查路线并查看&#34; current_user&#34;有权根据角色访问该路线 - 然后根据该角色显示/隐藏。

这可以节省用户点击事物并告知他们无法看到它 - 或者我们不得不写每个项目&#34;如果&#34;逻辑指定哪些角色可以在一个菜单中的每个链接周围看到哪些列表项(客户将定期更改,角色被更改/改进)(考虑使用html嵌套在组中的50多个项目的引导菜单格式化等),这是疯了。

如果我们必须在每个菜单项周围放置if-logic,请通过检查我们已在Ability文件中定义的角色/权限,为每个项目使用完全相同的逻辑。

但是在我们的菜单列表中,我们有路由助手 - 而不是&#34;控制器/方法&#34; info,以及如何测试用户是否能够点击为&#34;路径&#34;指定的控制器操作?在每个链接?

获取路径的控制器和方法(操作)(我的示例使用&#39; users_path&#39; route-helper)......

Rails.application.routes.recognize_path(app.users_path)
        => {:controller=>"users", :action=>"index"}

只获取控制器名称

    Rails.application.routes.recognize_path(app.users_path)[:controller]
        => "users"

能力使用模型进行细分,因此从控制器名称转换为模型(假设使用默认命名)...

    Rails.application.routes.recognize_path(app.users_path)[:controller].classify
        => "User"

获取动作名称

    Rails.application.routes.recognize_path(app.users_path)[:action]
        => "index"

因为&#34;可以?&#34;方法需要一个符号用于操作,而常量用于模型,对于我们得到的每个菜单项:

    path_hash = Rails.application.routes.recognize_path(app.users_path)
    model = path_hash[:controller].classify.constantize
    action = path_hash[:action].to_sym

然后使用我们现有的Abilty系统来检查current_user是否可以访问它,我们必须将操作作为符号传递,将模型作为常量传递,所以......

    <% if can? action model %>
        <%= link_to "Users List", users_path %>
    <% end %>

现在,我们可以更改谁可以从Ability文件中看到此资源和链接,而不会再次弄乱菜单。但是为了使这个更清洁,我在app-controller中为每个菜单项提取了查找:

def get_path_parts(path)
  path_hash = Rails.application.routes.recognize_path(path)
  model_name = path_hash[:controller].classify.constantize
  action_name = path_hash[:action].to_sym
  return [model_name, action_name]
end
helper_method :get_path_parts

...所以我可以在视图中执行此操作(为简单起见,我从链接中取出了所有html格式,这里):

<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
    <%= link_to "Users Listing", users_path %>
<% end %>

...并且为了使这不是整天打字这些每菜单项if-wraps,我使用正则表达式查找/替换捕获和通配符来包装在菜单项列表中的每个列表项周围一遍。

它远非理想,我可以做更多的事情来让它变得更好,但我没有空余时间来写下角色/ CanCan的其余部分系统。我希望这部分可以帮助别人。