Rails:id方法非标准时如何构建GoodJob与JobReport的关联
问题:GoodJob重写id后如何关联JobReport模型
我要做一个JobReport模型存储GoodJob任务的返回值,但遇到两个棘手问题:
- GoodJob::Job的
id方法被重写了,返回的是active_job_id而非数据库表的真实主键:
# 来自good_job-3.12.1/app/models/good_job/job.rb def id active_job_id end
good_jobs表的active_job_id没有唯一性约束,没法把它设为外键(PostgreSQL会报错)。
我现有的代码如下,保存时总是触发外键约束错误:
迁移文件
class CreateJobReports < ActiveRecord::Migration[7.0] def change create_table :job_reports do |t| t.text :report t.uuid :good_job_id t.timestamps end add_foreign_key :job_reports, :good_jobs, column: :good_job_id, primary_key: :id end end
JobReport模型
class JobReport < ApplicationRecord belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: 'id' end
GoodJob初始化器
GoodJob::Job.class_eval do has_one :job_report, dependent: :destroy end
报错详情
关联保存时,PostgreSQL提示good_job_id对应的键不存在——因为ActiveRecord用了被重写的id(也就是active_job_id),而不是数据库里的真实主键:
irb(main):001:0> jr = JobReport.new; gj = GoodJob::Job.last => #<GoodJob::Job:0x00007ff6950cda30 ...> irb(main):002:0> jr.good_job = gj => #<GoodJob::Job:0x00007ff6950cda30 ...> irb(main):003:0> jr.save PG::ForeignKeyViolation: ERROR: insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" DETAIL: Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs". irb(main):004:0> gj.id => "fdc02e75-a06a-4727-b790-9a846f61ed7d" irb(main):005:0> gj.active_job_id => "fdc02e75-a06a-4727-b790-9a846f61ed7d" irb(main):006:0> gj.attributes["id"] => "edc27b66-975d-4017-a09f-2d0cec332a0c"
尝试给关联加primary_key: 'id'后,问题还是没解决。
解决方法
核心就是绕过重写的id方法,直接用数据库表的真实主键。下面是几种可行的方案:
方案1:给GoodJob添加真实ID访问器
在GoodJob的初始化器里加一个获取真实主键的方法,然后关联时指定用这个方法:
# config/initializers/good_job.rb GoodJob::Job.class_eval do # 直接读取属性哈希,绕过重写的id方法 def real_id self[:id] end has_one :job_report, dependent: :destroy, foreign_key: :good_job_id, primary_key: :real_id end
然后修改JobReport模型:
class JobReport < ApplicationRecord belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: :good_job_id, primary_key: :real_id end
这样关联时就会使用数据库的真实主键,而不是被重写的id。
方案2:手动赋值真实ID
如果关联配置还是有问题,可以在保存JobReport时直接指定真实的good_job_id:
jr = JobReport.new(report: "任务执行结果...") gj = GoodJob::Job.last jr.good_job_id = gj.attributes['id'] # 或者用上面定义的gj.real_id jr.save!
这种方法简单直接,不需要修改关联配置,适合快速解决问题。
方案3:重写JobReport的关联赋值方法
在JobReport模型里重写good_job=方法,强制使用真实ID:
class JobReport < ApplicationRecord belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: :good_job_id def good_job=(job) self.good_job_id = job.attributes['id'] super(job) # 保持关联对象的引用 end end
同时修正GoodJob的关联:
GoodJob::Job.class_eval do has_one :job_report, dependent: :destroy, foreign_key: :good_job_id, primary_key: :id do # 用lambda绕过重写的id方法 def find(*) super.where(arel_table[:good_job_id] == proxy_association.owner.attributes['id']) end end end
这个方案更彻底,能确保所有关联操作都使用真实主键。
内容的提问来源于stack exchange,提问作者Mitch
相关产品推荐
相关产品推荐

