Rails 在非主键上添加外键

kst*_*tis 3 activerecord ruby-on-rails activemodel

在 Rails 5.1 中我有以下关系:

\n\n
class RootArea < ApplicationRecord\n  has_many :common_areas\nend\n\nclass CommonArea < ApplicationRecord\n  belongs_to :root_area, foreign_key: \'area_id\', optional: true\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

以下是他们的迁移:

\n\n
create_table "root_areas", force: :cascade do |t|\n  t.integer "area_id"\n  t.string "localname"\n  t.string "globalname"\n  t.datetime "created_at", null: false\n  t.datetime "updated_at", null: false\nend\n\ncreate_table "common_areas", force: :cascade do |t|\n  t.integer "area_id"\n  t.string "localname"\n  t.string "globalname"\n  t.integer :root_area_id\n  t.datetime "created_at", null: false\n  t.datetime "updated_at", null: false\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

我的目标是“连接”common_areasroot_areas-area_id不仅仅是id- ,以便单个根区域条目可以“拥有”多个公共区域。

\n\n

例如美国(RootArea)有加利福尼亚州(CommonArea)、德克萨斯州(CommonArea)等。

\n\n

我尝试使用迁移和外键来执行此操作,如下所示:

\n\n
add_foreign_key :common_areas, :root_areas, primary_key: :area_id\n
Run Code Online (Sandbox Code Playgroud)\n\n

虽然一开始这似乎很有效,但最终失败了:

\n\n
>>> RootArea.create!(area_id: 1111, \n                     localname: \xe2\x80\x99test\', \n                     globalname: \xe2\x80\x99test\') # OK\n\n>>> CommonArea.create!(area_id: 9999, \n                       localname: \xe2\x80\x99test\', \n                       globalname: \xe2\x80\x99test\') # OK\n\n>>> RootArea.first.common_areas << CommonArea.first # Error\n\n=> # SQL (44.3ms)  UPDATE "common_areas" SET "root_area_id" = $1, "updated_at" = $2 WHERE "common_areas"."id" = $3  [["root_area_id", 19], ["updated_at", "2018-02-20 14:45:52.545450"], ["id", 1]]\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出表明 Rails 尝试将CommonArea\'s 属性设置root_area_idRootArea\'s 主键(id而不是area_id)。

\n\n

例如,在上面的示例中,生成的 sql 语句应该设置root_area_id1111而不是19

\n

kst*_*tis 7

今晚晚些时候,我从 Rails IRC 频道寻求了一些进一步的帮助,最终用户dionysus69向我推荐了这篇文章,它与我正在寻找的内容非常相似。为了供将来参考,这是最终的解决方案:

class RootArea < ApplicationRecord
  has_many :common_areas, foreign_key: 'root_area_id', primary_key: 'area_id'
end

class CommonArea < ApplicationRecord
  belongs_to :root_area, foreign_key: 'root_area_id', primary_key: 'area_id', optional: true
end
Run Code Online (Sandbox Code Playgroud)

现在我可以成功地做到

>>  RootArea.first.common_areas << CommonArea.first
Run Code Online (Sandbox Code Playgroud)

并已root_area_id设置为正确的area_id值而不是id.