ruby-on-rails - 用户可以 "read"范围记录的常见方法是什么?

标签 ruby-on-rails ruby-on-rails-3 performance activerecord authorization

我正在使用 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 ,以便加载所有可读文章,但是当有大量记录时,出于性能原因,这没有什么好处(也许,另一种解决方案可能是将所有文章ids可读的地方存储起来一个用户,但我认为这不是解决该问题的常见/简单的解决方案)。

那么,有什么方法可以以高性能且“真正”正确的方式完成我想要做的事情(也许,通过完全改变上述方法)?强>

最佳答案

这取决于您的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/

相关文章:

ruby-on-rails - 我的脚本会发送很多请求吗?

ruby-on-rails - 如何在 Rails Controller 中返回 HTTP 204

ruby-on-rails - 未定义方法 `each' 为 nil :NilClass?

mysql - lamp 和 mysql 的限制,性能?

c++ - block 拼图求解C++算法

ruby-on-rails - 冲突的 Devise 和 restful Controller 路由 | rails 5.2

ruby-on-rails - 如何使 ActiveModel 成为散列或数组?

ruby-on-rails - 在 Rails 中使用 google-maps-for-rails gem 时设置标记颜色

ruby-on-rails - ruby on rails find_or_initialize

sql - 棘手的 postgresql 查询优化(带排序的不同行聚合)