我正在try 构建一个JobReport模型,该模型保存来自GoodJob作业(job)的返回值.我可以在其上建立关联的两个字段id和active_job_id有问题.Id字段设置为返回作业(job)类中的active_job_id:

# from good_job-3.12.1/app/models/good_job/job.rb
def id
  active_job_id
end

Good_jobs.active_job_id字段没有唯一性约束,将其设置为外键会引发postgres错误.

我怎样才能链接这两个表呢?


下面是我用来创建JOB_REPORTS表的迁移:

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

我的Good_job.rb初始值设定项包含:

GoodJob::Job.class_eval do
  has_one :job_report, dependent: :destroy
end

当我创建一个JobReport、将其绑定到一个作业(job)并保存它时,Postgres抱怨该id在Good_Jobs中不存在,因为它试图使用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
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': PG::ForeignKeyViolation: ERROR:  insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (ActiveRecord::InvalidForeignKey)                                                
DETAIL:  Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".                                          
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': ERROR:  insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (PG::ForeignKeyViolation)
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"

正如我前面提到的,如果我放弃ID列而切换到active_job_id列,Postgres说我不能将它用作外键b/c,没有唯一性约束.当然,我可以编辑GoodJob表,但我更喜欢使用GEM的插件形式,而不是篡改它来进行升级或诸如此类的事情.


编辑:我实现了Max的建议,但它仍在try 使用Good_jobs表的active_job_id列,而不是id列.

class JobReport < ApplicationRecord
  belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: 'good_job_id', primary_key: 'id'
end
irb(main):010:0> jr = JobReport.new; gj = GoodJob::Job.last
=> 
#<GoodJob::Job:0x00007f70ec430918                           
...                                                   
irb(main):011:0> jr.good_job = gj
=> 
#<GoodJob::Job:0x00007f70ec430918                           
...                                                   
irb(main):012:0> jr.save
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': PG::ForeignKeyViolation: ERROR:  insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (ActiveRecord::InvalidForeignKey)                      
DETAIL:  Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': ERROR:  insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (PG::ForeignKeyViolation)
DETAIL:  Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
irb(main):013:0> gj.id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):014:0> gj.active_job_id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):015:0> gj.attributes['id']
=> "edc27b66-975d-4017-a09f-2d0cec332a0c"

下面是这两个表的模式:

development=# \d good_jobs
                                    Table "public.good_jobs"
       Column        |              Type              | Collation | Nullable |      Default
---------------------+--------------------------------+-----------+----------+-------------------
 id                  | uuid                           |           | not null | gen_random_uuid()
 queue_name          | text                           |           |          |
 priority            | integer                        |           |          |
 serialized_params   | jsonb                          |           |          |
 scheduled_at        | timestamp(6) without time zone |           |          |
 performed_at        | timestamp(6) without time zone |           |          |
 finished_at         | timestamp(6) without time zone |           |          |
 error               | text                           |           |          |
 created_at          | timestamp(6) without time zone |           | not null |
 updated_at          | timestamp(6) without time zone |           | not null |
 active_job_id       | uuid                           |           |          |
 concurrency_key     | text                           |           |          |
 cron_key            | text                           |           |          |
 retried_good_job_id | uuid                           |           |          |
 cron_at             | timestamp(6) without time zone |           |          |
 batch_id            | uuid                           |           |          |
 batch_callback_id   | uuid                           |           |          |
Indexes:
    "good_jobs_pkey" PRIMARY KEY, btree (id)
    "index_good_jobs_on_cron_key_and_cron_at" UNIQUE, btree (cron_key, cron_at)
    "index_good_jobs_jobs_on_finished_at" btree (finished_at) WHERE retried_good_job_id IS NULL AND finished_at IS NOT NULL
    "index_good_jobs_jobs_on_priority_created_at_when_unfinished" btree (priority DESC NULLS LAST, created_at) WHERE finished_at IS NULL
    "index_good_jobs_on_active_job_id" btree (active_job_id)
    "index_good_jobs_on_active_job_id_and_created_at" btree (active_job_id, created_at)
    "index_good_jobs_on_batch_callback_id" btree (batch_callback_id) WHERE batch_callback_id IS NOT NULL
    "index_good_jobs_on_batch_id" btree (batch_id) WHERE batch_id IS NOT NULL
    "index_good_jobs_on_concurrency_key_when_unfinished" btree (concurrency_key) WHERE finished_at IS NULL
    "index_good_jobs_on_cron_key_and_created_at" btree (cron_key, created_at)
    "index_good_jobs_on_queue_name_and_scheduled_at" btree (queue_name, scheduled_at) WHERE finished_at IS NULL
    "index_good_jobs_on_scheduled_at" btree (scheduled_at) WHERE finished_at IS NULL
Referenced by:
    TABLE "job_reports" CONSTRAINT "fk_rails_6135bfd69e" FOREIGN KEY (good_job_id) REFERENCES good_jobs(id)

development=# \d job_reports
                                          Table "public.job_reports"
   Column    |              Type              | Collation | Nullable |                 Default
-------------+--------------------------------+-----------+----------+-----------------------------------------
 id          | bigint                         |           | not null | nextval('job_reports_id_seq'::regclass)
 report      | text                           |           |          |
 good_job_id | uuid                           |           |          |
 created_at  | timestamp(6) without time zone |           | not null |
 updated_at  | timestamp(6) without time zone |           | not null |
Indexes:
    "job_reports_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "fk_rails_6135bfd69e" FOREIGN KEY (good_job_id) REFERENCES good_jobs(id)

推荐答案

idprimary_key设置的值,即使他们没有override id方法,他们也是setting,primary_keyactive_job_id.

>> GoodJob::Job.last.id
=> "d781edac-1932-4d52-bfaa-61e4d80be5e8"
>> puts GoodJob::Job.instance_method(:id).source
    def id
      active_job_id
    end

>> GoodJob::Job.remove_method(:id)
# now `id` method comes from `ActiveRecord`
# https://github.com/rails/rails/blob/v7.0.4.2/activerecord/lib/active_record/attribute_methods/primary_key.rb#L18
>> puts GoodJob::Job.instance_method(:id).source
      def id
        _read_attribute(@primary_key)
      end

# `id` still returns `active_job_id`
>> GoodJob::Job.last.id
=> "d781edac-1932-4d52-bfaa-61e4d80be5e8"
# because
> GoodJob::Job.instance_variable_get("@primary_key")
=> "active_job_id"

ActiveRecord tries hard to use primary_key setting whenever id is mentioned:
https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/PrimaryKey.html


我有几个变通办法:

class JobReport < ApplicationRecord
  belongs_to :good_job, class_name: "OkJob", foreign_key: :good_job_id
end

class OkJob < GoodJob::Job
  self.primary_key = :id

  def id
    attributes["id"]
  end

  has_one :job_report, foreign_key: :good_job_id, dependent: :destroy
end
>> jr = JobReport.new; gj = OkJob.last; jr.good_job = gj; jr.save!
  OkJob Load (2.3ms)  SELECT "good_jobs".* FROM "good_jobs" WHERE "good_jobs"."retried_good_job_id" IS NULL ORDER BY "good_jobs"."id" DESC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.4ms)  BEGIN
  JobReport Create (1.2ms)  INSERT INTO "job_reports" ("report", "good_job_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["report", nil], ["good_job_id", "5301c9c7-2863-46cc-b8ea-7c959ed26474"], ["created_at", "2023-02-21 08:55:43.967530"], ["updated_at", "2023-02-21 08:55:43.967530"]]
  TRANSACTION (2.8ms)  COMMIT
=> true
>> jr.good_job_id == gj.attributes["id"]
=> true

这是我的第一次try ,结果有点尴尬:

class JobReport < ApplicationRecord
  # works when you're reading the association
  belongs_to :good_job, class_name: "GoodJob::Job",
    foreign_key: :good_job_id,
    primary_key: :id,
    optional: true
  # but doesn't work when writing it, 
  # so this monstrosity takes care of it:
  belongs_to :good_job_writer, class_name: "GoodJob::Job",
    foreign_key: :good_job_id,
    primary_key: :id_attribute,
    optional: true
  alias_method :good_job=, :good_job_writer=
end

module GoodJobJobDecorator
  # doesn't work at all
  # def self.prepended base
  #   base.has_one :job_report, primary_key: :id_attribute, foreign_key: :good_job_id, dependent: :destroy
  # end                                       ^
  #          i thought this was supposed to read from `id_attribute` method
  #          but it doesn't. oh, well ¯\_(ツ)_/¯

  def job_report
    JobReport.where(good_job_id: id_attribute).first
  end
  # `dependent: :destroy` is just a callback, you can add it manually

  def id_attribute
    attributes["id"]
  end
end
GoodJob::Job.prepend(GoodJobJobDecorator)
>> jr = JobReport.new; gj = GoodJob::Job.last; jr.good_job = gj; jr.save!
  GoodJob::Job Load (2.3ms)  SELECT "good_jobs".* FROM "good_jobs" WHERE "good_jobs"."retried_good_job_id" IS NULL ORDER BY "good_jobs"."active_job_id" DESC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.4ms)  BEGIN
  JobReport Create (1.3ms)  INSERT INTO "job_reports" ("report", "good_job_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["report", nil], ["good_job_id", "5301c9c7-2863-46cc-b8ea-7c959ed26474"], ["created_at", "2023-02-21 09:12:59.543168"], ["updated_at", "2023-02-21 09:12:59.543168"]]
  TRANSACTION (2.9ms)  COMMIT
=> true
>> jr.good_job_id == gj.attributes["id"]
=> true
>> JobReport.last.good_job
=> #<GoodJob::Job:0x00007ff789df8c60>

您当然可以跳过使用关联,这是有限制的,但使设置更简单:

class JobReport < ApplicationRecord
  def good_job
    GoodJob::Job.where(id: good_job_id).first
  end

  def good_job= job
    self.good_job_id = job.attributes["id"]
  end
end
>> jr = JobReport.new; gj = GoodJob::Job.last; jr.good_job = gj; jr.save!
  GoodJob::Job Load (2.3ms)  SELECT "good_jobs".* FROM "good_jobs" WHERE "good_jobs"."retried_good_job_id" IS NULL ORDER BY "good_jobs"."active_job_id" DESC LIMIT $1  [["LIMIT", 1]]
  TRANSACTION (0.4ms)  BEGIN
  JobReport Create (1.2ms)  INSERT INTO "job_reports" ("report", "good_job_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["report", nil], ["good_job_id", "5301c9c7-2863-46cc-b8ea-7c959ed26474"], ["created_at", "2023-02-21 09:32:52.589234"], ["updated_at", "2023-02-21 09:32:52.589234"]]
  TRANSACTION (3.0ms)  COMMIT
=> true
>> jr.good_job_id == gj.attributes["id"]
=> true

Ruby-on-rails相关问答推荐

在Ruby on Rails中获取堆栈级别太深错误(&Q)

使用FactoryBot测试ActiveStorage的RSpec请求规范

Heroku、Selenium、Chome和Chromedriver的复杂问题(抱歉,无法简单描述,请阅读详细信息)

如何让删除链接响应 Rails 7 中的 turbo_stream 和 html?

如何在生成的 HTML 本身中显示部分名称

如何在哪里查询 Rails 模型中定义的方法

在 Rails 3 中,如何在路由中使用锚点作为 ID?

Rails:如何小写非英文字符串?

如何使用 capistrano deploy 定位特定的提交 SHA

什么是 Ruby 中的 Python 文档字符串?

Heroku 错误 R14(超出内存配额):我该如何解决?

如何显示 RSpec 测试生成的 SQL 查询日志(log)?

关闭 Firefox 中文本字段的自动完成功能

控制器 helper_method

Rails:进行不可逆转的迁移是不是很糟糕?

如何使用 Rails 3 获取请求的目标控制器和操作?

我是否必须手动卸载所有依赖的 gem?

Vagrant上的Rails 4.2服务器端口转发不起作用

从 Rails 控制器获取主机名

错误:无法在 Mavericks 上构建 gem 原生扩展