用户可以“阅读”的范围记录的常用方法是什么?

我正在使用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) 

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

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

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

@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 

在这个时候,上面的代码是我能想到的最“高效的妥协”,即使它有一些陷阱:它将检索到的对象的数量“ 限制 ”到给定id s的给定数量的记录(10条记录在上面的例子中默认); 实际上, 它“确实”不会检索用户可读的所有对象,因为当您尝试进一步扩展相关的ActiveRecord::Relation “where”/“with”时使用的readable_by_user范围方法(例如,当您还会按title搜索文章添加另一个SQL查询子句),它会将记录限制在那些where("articles.id IN (?)", readable_article_ids) (即,它“限制”/“限制”检索的数量)当按title搜索时,将忽略前10个可读对象和用户可读的所有其他文章。 为了使得readable_by_user方法能够正确使用其他范围方法,该问题的解决方案可能是不要break块以便加载所有可读文章,但是当存在大量记录时,由于性能原因这是不利的(也许,另一个解决方案可能是存储用户可读的所有文章id ,但我认为这不是解决问题的常见/简单解决方案)。

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

这取决于你的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查询。

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

康康gem具有此function。 事实上,从版本1.4开始,它会自动调整查询范围,以返回当前用户可以访问的对象。

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

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

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

在“show”操作中,我重新计算记录的授权公司列表,使用它来执行授权检查,然后再次存储。

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

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