6

我试图根据用户角色隐藏部分视图。

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

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

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

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

如果有许多模型只能由管理员用户(例如 Promotion、User)删除,那么所有 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

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

根据 Rails 中的用户角色显示和隐藏

Ruby on Rails(三)隐藏部分视图

4

4 回答 4

8

我强烈推荐权威人士

它允许您为每个模型创建“策略”。对于您的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
于 2013-07-29T20:30:51.947 回答
2

因此,我想出了一种将 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。

于 2013-07-31T15:04:06.170 回答
1

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

于 2013-07-30T00:10:51.593 回答
0

使用 CanCan 和 Role gems,仍然需要一种方法来检查 Route 并查看“current_user”是否有权根据他们的角色访问该 Route - 然后根据该角色显示/隐藏。

这可以节省用户点击事物并被告知他们看不到它 - 或者我们必须编写每个项目的“如果”逻辑,指定哪些角色可以看到哪些列表项目(客户将定期更改,因为角色被更改/改进)围绕一个菜单中的每个链接(考虑一个引导菜单,其中包含 50 多个嵌套在具有 html 格式的组中的项目等),这太疯狂了。

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

但是在我们的菜单列表中,我们有路由助手——而不是“控制器/方法”信息,那么如何测试用户点击为每个链接中的“路径”指定的控制器操作的能力?

获取路径的控制器和方法(操作)(我的示例使用 'users_path' 路由助手)...

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"

并且由于“可以吗?” 方法需要一个符号来表示动作,而常量则需要模型,对于每个菜单项,我们得到这个:

    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 %>

现在我们可以更改谁可以从技能文件中查看此资源和链接,而无需再次弄乱菜单。但为了使这更清晰,我在应用程序控制器中提取了每个菜单项的查找:

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,我使用正则表达式查找/替换与捕获和通配符将其包装在一次通过的菜单项列表中的每个列表项周围。

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

于 2017-12-30T14:33:35.657 回答