Rails 5.0升级至5.1问题:含自定义Lambda的has_many through生成无效查询
你遇到的这个问题是Rails 5.1对关联作用域的处理逻辑发生了变化——在5.0中,through关联的作用域只会应用到最终关联的表上,但5.1开始,作用域会被同时应用到中间表的JOIN条件中,而此时目标表(invoices)还没有被关联,导致SQL报错。
解决方案:用Arel替代字符串SQL条件
避免直接写字符串形式的WHERE条件,改用Arel来构建条件,这样Rails能明确识别这个条件属于目标表(BuyerInvoice对应的invoices表),只会在关联该表时添加条件,不会错误地放到中间表的JOIN环节里。
修改你的TransportOrder模型关联代码如下:
class TransportOrder < ApplicationRecord has_many :invoice_lines, as: :invoicable has_many :transport_invoices, -> { where(BuyerInvoice.arel_table[:id].not_eq(0)) }, through: :invoice_lines, source: :invoice, source_type: "BuyerInvoice" end
如果BuyerInvoice是Invoice的子类,也可以直接用Invoice.arel_table,效果一致:
-> { where(Invoice.arel_table[:id].not_eq(0)) }
另一种方案:使用模型作用域+明确表引用
如果你更习惯用字符串条件,也可以在条件中通过关联名称来明确指定表(不过Arel的方式更优雅且不易出错):
首先在BuyerInvoice中定义作用域:
class BuyerInvoice < Invoice scope :valid_id, -> { where("#{table_name}.id <> 0") } end
然后在TransportOrder中调用这个作用域:
has_many :transport_invoices, -> { valid_id }, through: :invoice_lines, source: :invoice, source_type: "BuyerInvoice"
这里用table_name动态获取表名,避免硬编码,同时明确条件是针对当前模型对应的表,Rails会正确将这个条件应用到目标表的JOIN环节。
为什么之前的写法会失效?
在Rails 5.1的关联构建逻辑中,当你给through关联添加字符串形式的作用域时,Rails无法判断这个条件属于哪个关联节点,会错误地将其追加到中间表(invoice_lines)的JOIN条件中,而此时invoices表还没有被JOIN,导致PostgreSQL报错"missing FROM-clause entry for table 'invoices'"。
用Arel或带表名的条件,能让Rails明确条件所属的表,从而只在关联该表时添加对应的WHERE条件。
内容的提问来源于stack exchange,提问作者Henrik N

