Rails ActiveRecord带lambda的belongs_to关联静默失败排查
user_id: 1,
...
project_id: 1>
irb(main):003> chat.project_user
=> nil
irb(main):004> ProjectUser.find_by(user_id:1, project_id:1)
ProjectUser Load (0.2ms) SELECT "project_users".* FROM "project_users" WHERE "project_users"."user_id" = ? AND "project_users"."project_id" = ? LIMIT ? [["user_id", 1], ["project_id", 1], ["LIMIT", 1]]
=>
#<ProjectUser:0x0000000115bed0a0
id: 1,
project_id: 1,
user_id: 1,
...>
而关联的另一端`has_many`则运行正常: ```ruby has_many :chats, lambda { |project_user| unscope(:where).where(project_id: project_user.project_id, user_id: project_user.user_id) }
请问出现这种情况的原因是什么?
Rails的belongs_to关联默认会尝试通过外键关联到目标模型的主键(通常是id),即便你在lambda里指定了复合条件,Rails内部还是会自动添加一个project_user_id = NULL的查询条件(因为Chat模型里没有这个字段),导致查询结果为空,甚至不会执行你期望的SQL。
要解决这个问题,需要明确告诉Rails这个belongs_to关联不需要使用默认的外键,而是通过复合字段来匹配,需要加上primary_key和foreign_key参数:
belongs_to :project_user, lambda { |chat| unscope(:where).where(project_id: chat.project_id, user_id: chat.user_id) }, primary_key: [:project_id, :user_id], foreign_key: [:project_id, :user_id]
另外需要注意,Rails原生对复合主键的支持有限:
- 若使用Rails 6.1及以上版本,上述写法可正常工作;
- 若为更早版本,需借助
composite_primary_keysgem来实现复合主键关联。
内容的提问来源于stack exchange,提问作者MarkC

