Dan*_*fan 12 activerecord ruby-on-rails 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的适配器方法来实现这一点(如first,all,last等...).你需要重新考虑你的设计.
(如果读数过多,请跳至'D')
您有多种选择,这些选择都取决于您的权限的定义方式.我们来看几个案例:
A.用户拥有他所拥有的部门列表,只有他可以访问它们
您可以简单地将其实现为与Active Record Associations的has_many/ belongs_to关联
B.用户和部门是独立的(换句话说:没有如前一种情况所述的所有权),并且可以为每个用户和每个部门单独设置权限.
再简单一次,您可以实现has_and_belongs_to_many与Active Record Associations的关联.您需要创建Web逻辑,以便应用程序的管理员可以添加/编辑/删除访问权限.
C.更复杂的情况:现有的授权库
D.当这些授权库超出您的需求时(实际上,我在大多数项目中的情况),我发现通过rails scoping实现授权可以满足我的所有需求.
让我们通过一个简单的例子来看.我希望管理员能够访问整个数据库记录; 和常规用户只能访问status = open且仅在运营时间(例如上午8点至下午6点)的部门.我写了一个实现我的权限逻辑的范围
# 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)).因此,您必须在规范中(在控制器或功能级别)严格测试您的权限.
注意在此示例中,我们不会nil在权限失败时(如您在问题中提到的那样)返回,而是返回空结果集.通常更好,因此您保持ActiveRecord方法链接功能.但您也可以提出异常并从控制器中拯救它,然后重定向到"未授权"页面.
这不是after_initialize回调的用途。相反,您可以定义一个执行相同操作的方法。例如,将其放入您的Department模型中,它应该达到您正在寻找的结果:
def self.get_first
  check_permission ? first : nil
end
更新
我不太确定这样的方法有多安全,但您可以覆盖该all方法,因为其他查询方法都基于它。
class Department < ActiveRecord::Base
  def self.all
    check_permission ? super : super.none
  end
  private
  def self.check_permission
    # some code that returns true or false 
  end
end
不过,您最好使用一些授权框架。
更新2
多考虑一下这个问题,我强烈建议使用不同的方法。您确实不应该像这样重写方法,all因为肯定会产生意想不到的副作用。
一个实用的替代方案是在和has_and_belongs_to_many之间创建关系。设置方法如下:DepartmentUser
用户.rb
class User < ActiveRecord::Base
  has_and_belongs_to_many :departments
  ...
end
部门.rb
class Department < ActiveRecord::Base
  has_and_belongs_to_many :users
  ...
end
然后在终端中运行这些命令:
rails g migration CreateJoinTableDepartmentsUsers departments users
rake db:migrate
现在您可以使用以下命令将用户添加到部门@department.users << @user,或将部门添加到具有 的用户@user.departments << @department。这应该可以实现您正在寻找的功能。
@user.departments将仅返回该用户的部门,@user.departments.first将返回该用户的第一个部门或nil没有任何@user.departments.find(1)部门,并且仅当该部门属于该用户时才返回相应的部门,否则抛出异常。
| 归档时间: | 
 | 
| 查看次数: | 880 次 | 
| 最近记录: |