Active Record预加载多态关联问题:查询关联主体活跃的Card
当然可以实现你想要的功能!问题出在Active Record对多态关联的预加载限制上——它确实不允许直接通过includes(:cardable)配合关联表的条件进行查询,不过我们有几种靠谱的办法来解决这个问题:
方法1:拆分查询后用OR合并
我们可以分别查询关联活跃User的Card和关联活跃Organisation的Card,再用Active Record的or方法合并两个结果集,这样得到的还是一个可链式调用的Active Record关系对象:
# 查询关联活跃User的Card user_cards = Card.joins("INNER JOIN users ON cards.cardable_id = users.id AND cards.cardable_type = 'User'") .where(users: { active: true }) # 查询关联活跃Organisation的Card organisation_cards = Card.joins("INNER JOIN organisations ON cards.cardable_id = organisations.id AND cards.cardable_type = 'Organisation'") .where(organisations: { active: true }) # 合并两个查询结果 active_cards = user_cards.or(organisation_cards)
这种方式逻辑清晰,生成的SQL直观,还保留了Active Record关系对象的所有特性(比如分页、排序等)。
方法2:使用EXISTS子查询
如果你想写单一的查询语句,可以用SQL的EXISTS子句来检查关联的父模型是否处于活跃状态:
Card.where( "(cardable_type = 'User' AND EXISTS (SELECT 1 FROM users WHERE users.id = cards.cardable_id AND users.active = true)) OR (cardable_type = 'Organisation' AND EXISTS (SELECT 1 FROM organisations WHERE organisations.id = cards.cardable_id AND organisations.active = true))" )
这种方法直接在Card的查询中验证关联对象的状态,避开了多态预加载的限制,而且性能表现不错——尤其是当Card表数据量较大时,EXISTS子查询的效率通常比JOIN更高。
方法3:先筛选再预加载关联对象
如果你之后需要访问关联的cardable(User或Organisation),可以先筛选出符合条件的Card,再手动触发预加载,这样就不会触发多态预加载错误了:
# 先筛选出符合条件的Card(用上面任意一种方法) active_cards = Card.where(...) # 预加载关联的cardable对象 active_cards = active_cards.includes(:cardable)
此时Active Record已经知道这些Card对应的cardable_type,可以分别预加载User和Organisation的数据,不会再报错。
为什么你的原代码会报错?
当你尝试Card.includes(:cardable).where(cardable: {active: true})时,Active Record无法确定cardable到底指向users表还是organisations表——多态关联的特性就是可以指向多个不同的模型,所以它没办法生成对应的预加载SQL,因此抛出ActiveRecord::EagerLoadPolymorphicError。
内容的提问来源于stack exchange,提问作者stephenmurdoch

