2

我正在使用 Ruby on Rails 3.2.2,我想知道当必须检查用户是否具有“读取”记录“列表”中存在的记录的适当授权时,什么是常用方法。也就是说,此时我有以下内容:

class Article < ActiveRecord::Base
  def readable_by_user?(user)
    # Implementation of multiple authorization checks that are not easy to
    # translate into an SQL query (at database level, it executes a bunch of
    # "separate" / "different" SQL queries).

    ... # return 'true' or 'false'
  end
end

通过使用上面的代码,我可以对单个文章对象执行授权检查:

@article.readable_by_user?(@current_user)

但是,当我想index通过准确检索 10 个对象来制作(通常在我的控制器操作中)类似以下内容时

Article.readable_by_user(@current_user).search(...).paginate(..., :per_page => 10)

我仍然必须对每个对象执行授权检查。那么,我可以做些什么来以“智能”/“高性能”的方式Article对记录的“列表”(对象数组)执行授权检查?也就是说,例如,我是否应该加载(可能通过创建的数据对它们进行排序,将 SQL 查询限制为 10 条记录,......)然后迭代每个对象以执行授权检查?还是我应该做一些不同的事情(也许使用一些 SQL 查询技巧、一些 Ruby on Rails 工具或其他东西)?Article.all

@Matzi 回答后更新

我试图“手动”检索用户可读的文章,例如使用以下find_each方法:

# Note: This method is intended to be used as a "scope" method
#
#   Article.readable_by_user(@current_user).search(...).paginate(..., :per_page => 10)
#
def self.readable_by_user(user, n = 10)
  readable_article_ids = []

  Article.find_each(:batch_size => 1000) do |article|
    readable_article_ids << article.id if article.readable_by_user?(user)

    # Breaks the block when 10 articles have passed the readable authorization 
    # check.
    break if readable_article_ids.size == n
  end

  where("articles.id IN (?)", readable_article_ids)
end

这时候,上面的代码是我能想到的最“性能折中”了,即使它有一些陷阱:它将检索到的对象的数量“限制”在给定ids 的给定记录数量(默认为 10 条记录)在上面的例子中);实际上,它“真的”不会检索用户可读的所有对象,因为当您尝试进一步确定使用范围方法的相关ActiveRecord::Relation“where”/“with which”readable_by_user时(例如,当您还搜索文章时title添加进一步的 SQL 查询子句),它会将记录限制为那些where("articles.id IN (?)", readable_article_ids)(即,它“限制”/“限制”由用户搜索时将被忽略title)。为了使该readable_by_user方法与其他范围方法一起正常工作,解决该问题的方法可能是 break阻止以加载所有可读文章,但是当有很多记录时,出于性能原因(也许,另一种解决方案可能是将id用户可读的所有文章存储在某处,但我认为这不是解决问题的常见/简单解决方案)。

那么,有一些方法可以以一种高效且“真正”正确的方式完成我想做的事情(也许,通过改变上述方法)?

4

5 回答 5

3

这取决于你的readable_by_user功能。如果它很容易翻译成 SQL,那么它就是前进的方向。如果它比这更复杂,那么您很可能必须手动进行检查。

更新:为了阐明为可读列表创建 SQL 查询的意义,我举了一个例子。假设文章对给定用户的可读性取决于以下因素:

  • 用户自己的文章 ( SELECT a.user == ? FROM Articles a WHERE a.id = ?)
  • 文章对所有人开放(SELECT a.state == 0 FROM Articles a WHERE a.user = ?
  • 用户是有权访问文章的组的成员

sql:

SELECT max(g.rights) > 64
FROM Groups g 
JOIN Groups_users gu on g.id = ug.group_id
WHERE gu.id = ?
  • 用户被分配给给定的文章

sql:

SELECT 1
FROM Articles_users au
WHERE au.article_id = ? AND au.user_id = ?

这些可以总结在以下查询中:

def articles_for_user(user) 
  Articles.find_by_sql(["
    SELECT a.*
    FROM Articles a
    LEFT OUTER JOIN Articles_users au on au.article_id = a.id and au.user_id = ?
    WHERE a.user_id = ? 
       OR au.user_id = ?
       OR 64 <= (SELECT max(g.rights) 
                 FROM Groups g 
                 JOIN Groups_users gu on g.id = ug.group_id
                 WHERE gu.id = ?)
  ", user.id, user.id, user.id, user.id])
end

这肯定是一个复杂的查询,但却是最有效的解决方案。数据库应该做数据库的东西,如果你只使用 SQL 查询和一些逻辑来评估你的readable_bu_user,那么你可以将它转换成一个纯 SQL 查询。

于 2012-06-17T19:10:42.783 回答
1

我认为您应该寻找declarative_authorization gem。使用它的with_permissions_to方法,您可以轻松地执行此类数据库查询。例如:Article.with_permissions_to(:read).limit(10).offset(20)

于 2012-06-17T19:30:47.847 回答
0

我在目前正在使用的系统上遇到了同样的问题。

我发现最有效的方法是实现一个预先计算每条记录的授权状态的批处理作业。我使用了类似的东西accessible_by_companies并存储了一个包含所有可以访问这些记录的公司代码的数组,但accessible_by_users如果这是你的情况,你也可以使用。

在“显示”动作上,我重新计算了授权公司列表以供记录,用它来执行授权检查,并再次存储。

我使用ElasticSearch来存储预先计算的值以及执行查询和列表所需的所有数据。只有在查看记录或批处理作业时才会触及数据库。这种方法有很大的性能提升,试一试。

于 2012-06-20T04:21:07.600 回答
0

为了获得最佳性能,我建议在会话中存储用户可读文章的列表 - 用户不会在会话中更改,您可以单独考虑刷新频率和/或条件。假设您的 Articles.list() 可以通过 id 过滤,您需要做的就是将用户可读的 id 列表传递给 Articles.list() 功能。用户可读列表更新:您应该相对不频繁地更新它 - 每次搜索最多一次,您不想在每次页面加载时刷新完整列表,原因很简单,新结果可能会出现在用户已经出现的页面中无论如何滚动。

于 2012-06-26T08:22:24.070 回答
0

cancan gem具有此功能。事实上,从 1.4 版开始,它会自动限定您的查询范围以返回当前用户可以访问的对象。

有关更多信息,请参阅此页面:https ://github.com/ryanb/cancan/wiki/Fetching-Records

于 2012-06-17T20:56:32.043 回答