Rails配置has_one through多态关联自定义外键不生效问题
问题原因
has_one :through 关联上配置的foreign_key、primary_key参数不会穿透中间关联生效,Rails只会按照through链路上每一层关联本身定义的键规则生成JOIN条件。
你之前的配置里,source: :assignable 直接读取Portal::Item模型上多态关联assignable的默认规则:关联目标的主键是对应模型的id字段,所以永远会生成profiles.id = portal.items.assignable_id的JOIN条件,外层配置的foreign_key: :portal_account_id完全不会被中间链路识别,自然不生效。
正确配置方案
推荐用符合Rails关联约定的方式实现,不需要写原生SQL,后续预加载、条件查询都不会出兼容问题:
- 首先在中间模型
Portal::Item上补充一个专门映射到Profile的关联,明确指定两个表之间的键对应关系,同时过滤多态类型:
# app/models/portal/item.rb module Portal class Item < ApplicationRecord self.table_name_prefix = 'portal.' belongs_to :assignable, polymorphic: true # 新增关联:通过assignable_id关联到Profile的portal_account_id字段 has_one :assignable_profile, -> { where(assignable_type: 'Account') }, class_name: 'Profile', primary_key: :assignable_id, foreign_key: :portal_account_id end end
- 修改Product模型上的
portal_item_profile关联,将source指向刚才定义的中间关联即可,不需要额外配置外键、类型参数:
# app/models/product.rb class Product < ApplicationRecord has_one( :portal_item, class_name: 'Portal::Item', foreign_key: :external_product_id, dependent: :restrict_with_exception, ) has_one :portal_item_profile, through: :portal_item, source: :assignable_profile end
配置完成后执行查询,生成的SQL和你预期的完全一致:
SELECT "profiles".* FROM "profiles" INNER JOIN "portal"."items" ON "profiles"."portal_account_id" = "portal"."items"."assignable_id" AND "portal"."items"."assignable_type" = 'Account' WHERE "portal"."items"."external_product_id" = 4 LIMIT 1
备选方案(不修改中间模型)
如果不方便修改Portal::Item的代码,也可以直接在Product模型上手动写关联条件,绕过through的默认规则:
class Product < ApplicationRecord has_one( :portal_item, class_name: 'Portal::Item', foreign_key: :external_product_id, dependent: :restrict_with_exception, ) has_one :portal_item_profile, -> { joins("INNER JOIN portal.items ON profiles.portal_account_id = portal.items.assignable_id AND portal.items.assignable_type = 'Account' AND portal.items.external_product_id = products.id") }, class_name: 'Profile' end
这个方案可以实现同样的查询效果,但因为用了原生SQL,后续做复杂查询、表别名替换、预加载的时候容易出现兼容问题,优先选第一种方案。
内容的提问来源于stack exchange,提问作者taras
相关产品推荐
相关产品推荐

