我正在使用 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)
但是,当我想(通常在我的 Controller index
操作中)通过检索恰好 10 个对象来执行如下操作时
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 的记录数(上例中默认为 10 条记录);实际上,它“确实”不会检索用户可读的所有对象,因为当您尝试进一步确定相关的 ActiveRecord::Relation
“where”/“with其中”使用了 readed_by_user
范围方法(例如,当您还通过添加进一步的 SQL 查询子句的 title
来搜索文章时),它将限制 记录到那些 where("articles.id IN (?)", readed_article_ids)
(也就是说,它“限制”/“限制”检索到的和可读对象的数量为前 10 个和所有当按标题
搜索时,用户可读的其他文章将被忽略)。为了使 read_by_user
方法能够与其他作用域方法正常工作,该问题的解决方案可能是不破坏
block ,以便加载所有可读文章,但是当有大量记录时,出于性能原因,这没有什么好处(也许,另一种解决方案可能是将所有文章id
s可读的地方存储起来一个用户,但我认为这不是解决该问题的常见/简单的解决方案)。
那么,有什么方法可以以高性能且“真正”正确的方式完成我想要做的事情(也许,通过完全改变上述方法)?强>
最佳答案
这取决于您的readed_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 查询和一些逻辑来评估您的 readed_bu_user
那么您可以将其转换为一个纯 SQL 查询。
关于ruby-on-rails - 用户可以 "read"范围记录的常见方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11074008/