首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如果after_initialize回调返回false,则返回nil对象

如果after_initialize回调返回false,则返回nil对象
EN

Stack Overflow用户
提问于 2015-08-19 00:14:39
回答 3查看 1.1K关注 0票数 12

我的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。

让我们以Department模型为例:

代码语言:javascript
运行
复制
# 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

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

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

但是,看起来after_initialize无法阻止返回原始对象。它确实允许我重置对象的属性值,但不能省去它。

有人知道怎么做到这一点吗?

编辑:

非常感谢到目前为止提供的所有答案和评论;希望这个问题的扩展版本能澄清一些事情。

EN

回答 3

Stack Overflow用户

发布于 2015-08-21 14:31:54

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

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

(如果这篇文章读得太多,跳到'D‘点)

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

A.用户拥有他拥有的部门列表,并且只有他自己可以访问这些部门

您可以简单地将其实现为与Active Record Associationshas_many/belongs_to关联

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

同样简单的是,您可以使用Active Record Associations实现has_and_belongs_to_many关联。您需要创建web逻辑,以便应用程序的管理员可以添加/编辑/删除访问权限。

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

大多数人会求助于cancanpunditother等授权解决方案

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

让我们通过一个简单的例子来看一下。我希望管理员能够访问整个数据库记录;普通用户只能访问状态=打开的部门,并且只能在操作时间(例如上午8点到下午6点)访问部门。我编写了一个实现权限逻辑的作用域。

代码语言:javascript
运行
复制
# 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方法链接功能。但是你也可以引发一个异常并从你的控制器中拯救它,然后重定向到一个“未授权”的页面。

票数 5
EN

Stack Overflow用户

发布于 2015-08-19 00:34:57

这不是after_initialize回调的用途。相反,您可以只定义一个做同样事情的方法。例如,将此代码放入您的Department模型中,它应该会达到您正在寻找的结果:

代码语言:javascript
运行
复制
def self.get_first
  check_permission ? first : nil
end

更新

我不确定这样的东西有多安全,但您可以直接覆盖all方法,因为其他查询方法都是基于它的。

代码语言:javascript
运行
复制
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这样的方法,因为肯定会有意想不到的副作用。

一种实用的替代方案是在DepartmentUser之间创建has_and_belongs_to_many关系。下面是如何设置它:

user.rb

代码语言:javascript
运行
复制
class User < ActiveRecord::Base
  has_and_belongs_to_many :departments
  ...
end

department.rb

代码语言:javascript
运行
复制
class Department < ActiveRecord::Base
  has_and_belongs_to_many :users
  ...
end

然后在终端中运行以下命令:

代码语言:javascript
运行
复制
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)仅返回属于该用户的相应部门,否则将抛出异常。

票数 1
EN

Stack Overflow用户

发布于 2015-08-23 19:24:42

如果check权限为false,可以使用before_create回调停止创建记录。只需在check_permission筛选器中返回false,将不会创建记录。

代码语言:javascript
运行
复制
 class Department < ActiveRecord::Base

  before_create :check_permission

  private

  def check_permission
    # return false if permission is not allowed 
  end

end
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32077572

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档