ruby-on-rails - 如果 after_initialize 回调返回 false,则返回 nil 对象

标签 ruby-on-rails activerecord callback rails-activerecord

我的 Rails 应用程序有许多形成层次结构的模型。例如:零售商 > 部门 > 产品类别 > 产品 > 评论。

业务需求是高权限用户可以与新的或现有的“普通”用户“共享”层次结构中的任何单个元素。如果没有与他们共享对象,普通用户就无权查看(或执行任何其他操作)层次结构中任何级别的任何对象。

共享过程包括选择共享是否授予对目标对象的只读、读取更新或完整 CRUD 的权限。

共享任何对象都会授予该对象和层次结构中所有较低级别对象的 R/O、R/W 或 CRUD 权限,以及该对象的所有直接祖先的 R/O 权限。对象集合有机地增长,因此权限系统仅通过记录 user_id、共享的 object_id 和共享的性质(R/O、CRUD 等)来工作。由于此层次结构中的对象数量一直在增长,因此在 DB 中为每个用户/对象组合创建明确的权限记录是不切实际的。

相反,在用户请求周期开始时,ApplicationController收集所有权限记录(用户 X 对部门 #5 具有 CRUD 权限)并将它们保存在内存中的哈希中。权限模型知道当任何对象传递给它时如何评估散列 - Permission.allow?(:show, Department#5) 将根据用户的权限散列的内容返回 true 或 false。

让我们以部门模型为例:

# app/models/department.rb
class Department < ActiveRecord::Base

  after_initialize :check_permission

  private

  def check_permission
    # some code that returns true or false 
  end

end

check_permission方法返回 true,我想要 Department.first正常恢复数据库中的第一条记录,但是,如果 check_permission返回false,我要返回nil .

现在,我有一个解决方案,即默认范围触发权限检查,但这会导致查询数量增加 2 倍,对于具有大量对象的类,内存问题和时间/性能问题肯定会出现。

我的目标是使用 after_initialize预先授权对象的回调。

然而,似乎 after_initialize无法阻止返回原始对象。它确实允许我重置对象属性的值,但不能免除它。

有谁知道如何实现这一目标?

编辑:

非常感谢迄今为止提供的所有答案和评论;希望这个问题的扩展版本可以澄清事情。

最佳答案

基本上,您需要在返回数据库查询结果之前检查访问权限(或权限)。您正在尝试将此逻辑集成到您的模型中。

这是可能的,但不是您在问题中描述的设计。在 ActiveRecord 适配器方法(例如 firstalllast 等...)中直接实现这一点并不干净。你需要重新考虑你的设计。

(如果阅读太多,请跳到点“D”)

您有多种选择,这都取决于您定义权限的方式。我们来看几个案例:

A. 用户拥有他拥有的部门列表,只有他可以访问它们

您可以简单地将其实现为 has_many/belongs_toActive Record Associations 的关联

B. 用户和部门是独立的(换句话说:没有前面案例中描述的所有权),并且可以为每个用户和每个部门单独设置权限。

再次简单,你可以实现一个 has_and_belongs_to_manyActive Record Associations 的关联.您将需要创建 Web 逻辑,以便您的应用程序管理员可以添加/编辑/删除访问权限。

C. 更复杂的情况:现有的授权库

大多数人会转向授权解决方案,例如cancan , punditother

D. 当这些授权库超出您的需求时(实际上,在我的大多数项目中都是我的情况),我发现通过 rails scoping 实现授权满足我的所有需求。

让我们通过一个简单的例子来看看。我希望管理员能够访问整个数据库记录;普通用户只能在营业时间(例如上午 8 点至下午 6 点)访问 status = open 的部门。我写了一个实现我的权限逻辑的范围

# Class definition
class Department
  scope :accessible_by -> (user) do
    # admin user have all access, always
    if user.is_admin?
      all
    # Regular user can access only 'open' departments, and only
    # if their request is done between 8am and 6pm
    elsif Time.now.hour >= 8 and Time.now.hour <= 18
      where status: 'open'
    # Fallback to return ActiveRecord empty result set
    else
      none
    end
  end
end

# Fetching without association
Department.accessible_by(current_user)

# Fetching through association
Building.find(5).departments.accessible_by(current_user)

定义范围迫使我们在代码中的任何地方使用它。您可以考虑“忘记”通过范围并直接访问模型的风险(即编写 Department.all 而不是 Department.accessible_by(current_user) )。因此,这就是为什么您必须在规范中(在 Controller 或功能级别)切实测试您的权限。

注意在这个例子中我们不返回 nil当权限失败时(如您在问题中提到的),而是一个空的结果集。它通常会更好,因此您可以保留 ActiveRecord 方法链接功能。但是您也可以引发异常并将其从您的 Controller 中拯救出来,然后重定向到“未授权”页面。

关于ruby-on-rails - 如果 after_initialize 回调返回 false,则返回 nil 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32077572/

相关文章:

ruby-on-rails - Rails Admin 如何显示下拉字段

ruby-on-rails - HashMap block 中的调用函数

ruby-on-rails - 如何调用 ActiveRecord::Base 的加载方法?

ruby-on-rails - Rails - Activerecord : Find all records which have a count on has_many association with certain attributes

ruby-on-rails - 我想在内容之间插入图像

javascript - 为什么我的部分 Rails 渲染不正确?

ruby-on-rails - 使用引用对象树查询结果 - Ruby on Rails

javascript - 如何使用 for 循环实现 map() 函数?

javascript - 是什么导致 javascript 函数不运行它的回调

android - 如何在android中实现监听器