首页 > 解决方案 > 使用 Rails 仅触发特定操作的某些验证并设计

问题描述

我有和管理员模型与设计:

class Administrator < ApplicationRecord
     validate :max_num_admins, on: :create
     devise :invitable, :database_authenticatable,
         :recoverable, :rememberable, :validatable
     
       def max_num_admins
         self.errors.add(:base,
          I18n.t(
           'activerecord.errors.models.administrator.max_reached'
          )
         ) if Administrator.count > 3
        end
      end

“帐户所有者”可以通过管理仪表板添加管理员,这具有邀请管理员的效果(设计邀请方法邀请!):

def create
  resource = resource_class.new(resource_params)
  authorize_resource(resource)
  if resource.valid?
    Administrator.invite!(resource_params)
    redirect_to(
      [namespace, resource],
      notice: translate_with_resource('create.success')
    )
  else
    render :new, locals: {
      page: Administrate::Page::Form.new(dashboard, resource)
    }
  end
end

如您所见,我想检查我的记录是否有效,特别是如果该帐户的管理员记录不超过 3 条。问题是当我打电话valid?resource(管理员)时,我总是得到错误,因为在这个阶段,邀请管理员的唯一必要参数是有效的电子邮件,但会触发设计验证,因此我会因为未设置密码而收到错误消息。

什么是保持电子邮件和 max_num_admins 验证触发此操作而不是密码的一种干净方法?

标签: ruby-on-railsvalidationdevisedevise-invitable

解决方案


验证链是一个回调链,验证链的结果errors毕竟是(返回trueif errors.empty?),没有办法过滤(或fail early),所以当你的模型设置设计时,所有设计的验证都添加到验证链中,没有过滤掉它们但删除设计的方法。所以我想出了一个想法,你应该定义一个special validation范围,该范围将附加在验证链的末尾并强制执行errors.clear(这样它就会忽略以前的设计验证),看看下面的代码:

class User < ActiveRecord::Base
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable,
         :confirmable
    
 validate :max_num_admins, on: [:create, :special] # should be the last validation below devise validations

 def max_num_admins
   if self.validation_context == :special
     errors.clear # that mean previous validations (not only devise) be ignored
   end
     
   self.errors.add(:base,
     I18n.t(
      'activerecord.errors.models.administrator.max_reached'
     )
   ) if Administrator.count > 3
 end
end

然后

def create
  # ...
  if resource.valid?(:special) # validate at special scope
  end
end

if resource.valid? # this will work as normal

更新

无需触及任何现有的验证,您可以在验证链的末尾添加一个验证specialslice!用于检查您想要检查的那些错误:

class User
 # ...
 validate :max_num_admins, on: [:create, :special]
 validate :special, on: [:special]

 def max_num_admins  
   self.errors.add(:max_num_admins,
     I18n.t(
      'activerecord.errors.models.administrator.max_reached'
     )
   ) if Administrator.count > 3
 end

 private
 def special
   # removes all errors except the given keys. 
   # so if you want to check email and max_num_admins
   errors.slice!(
    :email, 
    :max_num_admins
   )
 end
end

然后

if resource.valid?(:special) # validate email and max_num_admins
if resource.valid? # check as normal password, email, ..., max_num_admins

注意弃用警告:ActiveModel::Errors#slice!已弃用并将在 Rails 6.2 中删除。


推荐阅读