使Rails的验证助手返回错误

时间:2014-04-16 19:42:22

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

好吧,我已经获得了模型的简化版 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<0,实例返回false时,errors数组都会正确填充,并且不会调用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,我希望它的行为完全相同,我相信使用验证帮助程序的方式更多优雅而简洁。

1 个答案:

答案 0 :(得分:2)

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.transactiontransfer.save来电时将transfer.save!来电置于控制器中。

有关交易的更多信息,请访问:ActiveRecord::Transactions::ClassMethods