Rails after_create_commit关联记录报错:期望记录实例得到false
问题场景
Booking模型中定义了如下关联与回调逻辑:
belongs_to :tour, optional: true after_create_commit :add_to_tour def add_to_tour tour = Tour.where(experience: experience, time: time).first_or_create tour.bookings <<! self end
注:代码中的感叹号为调试时临时添加
业务方法中编写的逻辑如下:
booking = Booking.home_guide.where(pipedrive_deal_id: params['id']).first_or_initialize if booking.update!( # 业务参数 )
执行代码时抛出异常:
ActiveRecord::AssociationTypeMismatch (Booking(#71120) expected, got false which is an instance of FalseClass(#5700))
已知Booking记录已成功提交至数据库(否则回调不会触发),疑问点为false值的来源,以及是否需要重载booking记录解决问题。
错误原因
问题完全出在回调里的tour.bookings <<! self写法,是Ruby运算符优先级导致的解析错误:
- 一元取反运算符
!的优先级高于二元追加运算符<< - Ruby不存在
<<!这个内置关联操作方法,因此这行代码不会被解析为「调用带bang后缀的追加方法传入self」,而是直接被解析为tour.bookings << (!self) - Ruby中除了
nil和false外所有对象都是真值,对Booking实例self取反的结果固定为false - 最终相当于给
tour.bookings关联集合追加了一个false值,自然会抛出类型不匹配异常,期望拿到Booking实例却收到了FalseClass实例。
after_create_commit确实是在数据库记录提交完成后才会触发,因此看到Booking记录已经落库是正常现象——报错发生在提交后的回调逻辑里,不会回滚之前的插入操作。
解决方案
不需要重载booking记录,只需要删掉调试时误加在错误位置的感叹号即可,修正后的回调代码:
def add_to_tour tour = Tour.where(experience: experience, time: time).first_or_create tour.bookings << self end
内容的提问来源于stack exchange,提问作者wachichornia
相关产品推荐
相关产品推荐

