无需包装方法:在AASM的after回调中终止事件并返回布尔值
问题场景
现有如下AASM状态机代码:
aasm column: :status, whiny_transitions: false do state :requested, initial: true state :approved event :approve, after: :after_approve do transitions from: :requested, to: :approved end end def after_approve raise "This is a test" end
希望调用obj.approve!时,方法能返回true或false而非抛出异常,以便在模型中捕获异常、添加对象错误后在控制器中处理。尝试过返回false、常规错误处理等方式,均无法在不抛出异常的情况下终止after回调;目前仅能通过包装方法临时解决:
def approve_with_errors approve! rescue StandardError => e errors.add(:base, "Error: #{e}") false end
已知在Rails中,before回调可通过throw(:abort)终止回调链;after回调中抛出ActiveRecord::RecordInvalid.new(self)可终止回调链且异常不向上传递。已在AASM仓库提交issue #835,现寻求无需包装方法的解决方案。
解决方案
方案1:使用around事件回调包裹逻辑
利用AASM的around事件回调,将状态转换与after逻辑统一包裹,在回调内捕获异常、回滚状态并添加错误,最终返回false。此方式无需额外包装方法,直接通过不带!的事件方法调用即可:
aasm column: :status, whiny_transitions: false do state :requested, initial: true state :approved event :approve do transitions from: :requested, to: :approved around do |_event, block| begin block.call # 执行状态转换 after_approve # 执行原after回调逻辑 true rescue StandardError => e errors.add(:base, "Error: #{e}") # 回滚状态(因状态已完成转换,需手动重置) self.status = :requested save(validate: false) false end end end end def after_approve raise "This is a test" end
调用时使用obj.approve(不带!),方法会返回false,同时模型错误已添加,状态也会回滚至转换前的requested。
方案2:修改after回调为抛出ActiveRecord异常
结合Rails回调特性,在after回调中抛出ActiveRecord::RecordInvalid异常,同时利用AASM的非!方法特性(whiny_transitions: false配置下返回布尔值)。注意此方式需确保状态转换后的数据能被回滚,建议结合事务使用:
aasm column: :status, whiny_transitions: false do state :requested, initial: true state :approved event :approve, after: :after_approve do transitions from: :requested, to: :approved end end def after_approve errors.add(:base, "This is a test") raise ActiveRecord::RecordInvalid.new(self) end
调用obj.approve时,方法会返回false,模型错误已添加;但需注意:状态转换已完成,若需回滚状态,需额外在异常捕获逻辑中手动重置(可结合Rails事务包裹调用)。
说明
AASM中带!的事件方法(如approve!)设计初衷就是在转换失败或回调出错时抛出异常,若要避免抛出异常,建议使用不带!的版本配合上述方案。
内容的提问来源于stack exchange,提问作者Paul Odeon

