【问题标题】:Make Rails's Validation Helpers return with false让 Rails 的验证助手返回 false
【发布时间】:2014-04-16 19:42:21
【问题描述】:

嗯,我有这个模型的简化版Rails 3.2.13):

class Transfer < ActiveRecord::Base
  attr_accessible :from,:to,:total
  validates_presence_of :from,:to,:total

  before_validation :positive_numbers, on: :create
  before_validation :check_enough_balance, on: :create
  after_validation :update_balances

  private
  def positive_numbers
    unless self.total>0
      errors.add(:total,"should be greater than 0")
      return false
    end
  end
  def check_enough_balance
    @sender=User.find(self.from)
    @receiver=User.find(self.to)
    unless @sender.enough_balance(self.total)
      errors.add(:base,"Not enough credit")
      return false
    end
  end
  def update_balances
    @sender.balance -= self.total
    @receiver.balance += self.total
    @sender.save
    @receiver.save
  end
  def another_action
    puts 'does something'
  end

end

每当total&lt;0,实例返回falseerrors 数组正确填充,another_action 回调不会被调用。

我想知道如何使用 Rails 的内置验证助手来获得相同的行为,这就是我尝试过的方法:

class Transfer < ActiveRecord::Base
  attr_accessible :from,:to,:total
  validates_presence_of :from,:to,:total
  validates_numericality_of :total, greater_than: 0

  before_validation :check_enough_balance, on: :create
  after_validation :update_balances
  private

  def check_enough_balance
    @sender=User.find(self.from)
    @receiver=User.find(self.to)
    unless @sender.enough_balance(self.total)
      errors.add(:base,"Not enough credit")
      return false
    end
  end
  def update_balances
    @sender.balance -= self.total
    @receiver.balance += self.total
    @sender.save
    @receiver.save
  end
end
class User<ActiveRecord::Base
  attr_accessible :username
  validates_presence_of  :username, :balance

  def enough_balance(amount)
    self.balance >= amount
  end

end

但是在这种情况下,由于验证助手没有 return false 调用以下自定义验证 check_enough_balance,我希望它的行为完全相同,并且我相信使用验证助手在某种程度上更加优雅和简洁。

【问题讨论】:

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


    【解决方案1】:

    before_validation 回调的字面意思是“在检查验证之前运行此方法”。如果您希望验证在 another_action 之前运行,请考虑将其移至不同的回调。根据您的示例,我认为您可能需要 after_validation 回调,但还有其他受支持的回调也可能效果更好。

    after_validation :another_action, on: :create
    

    您可以在此处找到支持的回调的完整列表:ActiveRecord::Callbacks

    无论您的其他规则做什么,验证规则都应该是有意义的独立单元。通常最好运行所有验证规则并收集所有组合错误,以便用户可以同时修复所有问题。

    对于您的具体情况,检查用户是否可以覆盖总共 0 或更少似乎完全没问题。这只是另一个独立于其他验证规则的验证规则。也就是说,考虑将它从回调转移到实际的验证器中:

    class Transfer < ActiveRecord::Base
      ...
      validate :enough_balance
    
      private
    
      def enough_balance
        unless User.find(self.from).enough_balance(self.total)
          errors.add(:base, "Not enough credit")
        end
      end
    end
    

    如果您由于某些原因(例如性能)不需要执行该检查,则通过将条件更改为以下内容,可以很容易地再次检查无效条件而不同时处理错误:

    unless self.total <= 0 || User.find(self.from).enough_balance(self.total)
    

    您可以在此处找到有关自定义验证器的更多信息:Active Record Validations - Custom Validators

    保存记录不应作为验证的一部分进行(包括回调之前和之后)。例如,如果我们只想手动检查记录是否有效,我们都不希望出现这种副作用:

    transfer = Transfer.new(...)
    if transfer.valid?
      # Stuff gets saved?!?!
      ...
    end
    

    改为使用before_saveafter_save 回调来更新相关记录。只有当ActiveRecord 决定可以保存记录时,这些回调才会运行。如果作为save 调用的一部分验证失败,这些回调将不会运行。

    after_save :update_balances
    

    此外,以这种方式执行额外保存时,通常最好使用save! 方法而不是save 方法并将所有内容包装在事务中。 save 应在显式检查返回值时使用。 save! 应该在您假设一切都处于您期望的状态时使用。 save! 引发的异常可用于回滚您在该事务中所做的所有其他更改。

    一些例子:

    transfer = Transfer.new(...)
    
    if transfer.save # Good!
      ...
    
    if transfer.save! # Bad, causes an exception when you might expect false
      ...
    
    transfer.save # Bad, can silently fail
    
    transfer.save! # Good, raises an exception if it unexpectedly fails
    
    # All changes will be rolled back if any of the `save!` calls raise exceptions
    Transfer.transaction do
      transfer.save!
      something_else.save!
      yet_another_thing.save!
    end
    

    您通常会将您的 Transfer.transaction 调用放在控制器中的 transfer.savetransfer.save! 调用周围。

    有关交易的更多信息可以在这里找到:ActiveRecord::Transactions::ClassMethods

    【讨论】:

    • 这是个好主意。但我忘了提到,我还需要在 before 验证之前调用该回调。我编辑了这个例子,所以以前的another_action 现在是check_enough_balance,它检查了足够的信用。感谢您的回复。
    • @vint-i-vuit 我认为在您的示例中,您应该将 check_enough_balance 视为另一个验证器。有关更多信息,请参阅我的编辑。
    • 再次感谢您的回复。我编辑了扩展示例的问题(我想我不应该从一开始就对其进行简化),以显示为什么我希望验证在验证失败后立即停止。如您所见,我有一个after_validation 方法,一旦所有验证通过且没有错误,我就想运行该方法。我使用验证助手最接近的东西是after_validation :update_users_balance, if: :all_valid,其中def all_valid; self.errors.empty?; end。然而,出于某种原因,这保存新的@transfer,即使它无效。 ://
    • @vint-i-vuit 我添加了一些与验证/保存相关的更多信息。希望对您有所帮助。
    • 这确实很有用,我明天仔细看看并分享一些反馈,谢谢:)
    猜你喜欢
    • 1970-01-01
    • 2012-05-06
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2011-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多