【问题标题】:Rails 3 ActiveRecord validation based on user permissions基于用户权限的 Rails 3 ActiveRecord 验证
【发布时间】:2010-10-18 04:38:10
【问题描述】:

我正在将代码从构建在非标准自定义 PHP 框架中的应用程序转移到 Ruby on Rails(第 3 版)中。在 PHP 版本中,所有控制器都非常胖,模型很瘦,我一直不同意,所以我很喜欢 Rails 在模型级别进行验证的方式,这可能是这些胖控制器中发生的事情的 90%目前。

我面临的一个问题,但不确定如何解决,是基于谁对模型进行更改而不同的验证规则。例如,管理员或记录的原始创建者应该能够执行诸如将记录标记为已删除(软删除)之类的操作,而其他人则不应该这样做。

class Something < ActiveRecord::Base
  ...
  validates :deleted, :owned_by_active_user => true
  ...
end

class OwnedByActiveUserValidator < ActiveModel::EachValidator
  validate_each(record, attr_name, attr_value)
    # Bad idea to have the model know about things such as sessions?
    unless active_user.admin? || active_user.own?(record)
      record.errors.add :base, "You do not have permission to delete this record"
    end
  end
end

由于模型本身(理论上)不知道进行更改的用户,那么做这种事情的“rails 方式”是什么?我应该将活动用户设置为记录上的虚拟属性(实际上并未保存到数据库),还是应该只在控制器中执行这些检查?我不得不承认,让活动用户的模型检查权限确实感觉很奇怪,并且在测试模型时增加了复杂性。

我热衷于在模型中保留尽可能多的内容的一个原因是,我想同时提供 API(通过 OAuth 访问)和网站,而不需要复制太多代码,例如这些类型权限检查。

【问题讨论】:

    标签: ruby-on-rails validation activerecord permissions ruby-on-rails-3


    【解决方案1】:

    处理授权或将授权委托给授权层实际上是控制器的工作。模型不应该知道也不必关心当前登录的人以及他/她的权限是什么——这是控制器的工作,或者控制器委派给的任何身份验证帮助层。

    您应该通过newcreateupdate_attributes:deleted 分配到attr_accessible 中。控制器应该单独检查认证用户的授权,如果认证用户被授权,则单独调用deleted=

    有几个授权库和框架可以帮助授权或充当授权层,例如cancan

    【讨论】:

    • 谢谢,我必须同意你的看法。我的模型应该按照他们所说的去做(只要业务逻辑允许他们这样做)。我的控制者应该决定谁告诉他们。我会尽可能地抽象出血淋淋的细节。
    • Dogma... 我知道这是通常的做法,但在我看来,如果您将模型视为您的数据 API/业务,将访问控制直接放入模型中是一个优雅的解决方案规则层。它还避免了在接触同一模型的多个控制器中重复相同的信息。并且为此目的使用验证器似乎也很方便——可能会产生诸如“抱歉,您无权编辑该字段”之类的错误。但是,该字段不应该首先显示。希望有一个解决方案可以让表单构建器进行自省。
    • 您可以创建表单模型(不是数据库支持的)类来实现验证和表单构建器所需的方法。
    【解决方案2】:

    我会在我的控制器中使用 before_filter 来解决这个问题,而不是在我的模型中使用验证。

    class SomethingController < ApplicationController
      before_filter :require_delete_permission, :only => [:destroy]
    
      def destroy
        # delete the record
      end
    
      private
    
      def require_delete_permission
        unless current_user.is_admin || record.owner == current_user
          flash[:error] = 'You do not have delete permissions'
          redirect_to somewhere
        end
      end
    end
    

    【讨论】:

    • 我在这里也没有得到反对意见。您的建议实际上就是接受的答案所说的。
    • 除非他在闪存中添加消息...不会使记录保存无效
    【解决方案3】:

    我在 Rails 2.3 中遇到了同样的问题,最后想出了这个解决方案。在您的模型中,您定义了一些属性,具体取决于您打开/关闭验证。您可以根据控制器可用的日期(例如您的情况下的用户权限)设置此属性,如下所示:

    Class Model < ActiveRecord::Base
       attr_accessor :perform_validation_of_field1 #This is an attribute which controller will use to turn on/off some validation logic depending on the current user
    
       validates_presence_of :field1, :if => :perform_validation_of_field1
       #This validation (or any similar one) will occur only if controller sets model.perform_validation_of_field1 to true.
    end
    
    Class MyController < ActionController::Base
       def update
         @item = Model.find(params[:id])
         @item.update_attribute(params[:item])
    
         #The controller decides whether to turn on optional validations depending on current user privileges (without the knowledge of internal implementation of this validation logic)
         @item.perform_validation_of_field1 = true unless active_user.admin?
    
         if @item.save
            flash[:success] = 'The record has been saved'
            redirect_to ...
         else
            flash.now[:error] = 'The record has not passed validation checks'
            render :action => :edit
         end
       end
    

    我认为在 Rails 3 中可以以类似的方式完成。

    【讨论】:

    • 我不得不说,这在第一印象中听起来有点随意,但如果它对你有用,那就太好了;)
    • 这种方法有什么问题。没有什么是偶然的(对不起,不知道如何用这个词用英语做形容词:)。是的,我输入了一个代码,但没有检查它。我希望你能明白这一点。这个想法是在模型中定义的验证宏上使用 :if 或 :unless 参数。所以验证逻辑的某些部分可以打开/关闭。控制器层知道如何为模型启用/禁用特定的验证逻辑,但对验证逻辑的实现一无所知。
    • 当然,您可以将验证选项组合成更大的块(无需为每个验证定义一个属性)。因此,请查看api.rubyonrails.org/classes/ActiveRecord/Validations/… 并特别关注验证宏的 :if 和 :unless 参数,然后再决定此代码仅对我有用:)
    • 另外我想指出我的食谱是对您问题的一般回答 - 如何从模型外部控制验证的某些部分。在删除列的特定情况下,我喜欢Justice的回答。
    • “没有什么偶然的”,就像我们说“没有什么奇怪的”或“这有什么相关的?” ;)
    猜你喜欢
    • 2023-02-04
    • 2017-11-15
    • 1970-01-01
    • 2014-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    相关资源
    最近更新 更多