使 Rails 的验证助手返回 false



好吧,我有一个模型的简化版本(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时,实例返回一个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,我希望它的行为完全相同,并且我相信使用验证助手在某种程度上更优雅和简洁。

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

after_validation :another_action, on: :create

您可以在这里找到支持回调的完整列表:ActiveRecord::回调。

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

对于您的特定情况,检查用户是否可以覆盖总共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)

您可以在此处找到有关自定义验证器的更多信息:活动记录验证-自定义验证器

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

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

相关内容

  • 没有找到相关文章

最新更新