好吧,我已经获得了模型的简化版( 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
,我希望它的行为完全相同,我相信使用验证帮助程序的方式更多优雅而简洁。
答案 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_save
和after_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.save
来电时将transfer.save!
来电置于控制器中。
有关交易的更多信息,请访问:ActiveRecord::Transactions::ClassMethods