【问题标题】:How to work with Rails N:M/HABTM relationships within ActiveRecord?如何在 ActiveRecord 中处理 Rails N:M/HABTM 关系?
【发布时间】:2020-11-09 22:19:49
【问题描述】:

使用 Ruby 2.7.0 和 Ruby on Rails 6.0.3.4

好的,假设我有两个模型 En 和 Em,我想在它们之间设置 N:M 关系。 Rails 文档有点稀缺,但它们advise the use 的create_join_table 迁移方法用于建立HABTM 关系。很快,运行rails g migration En Em 会给我一个迁移文件,其中包含

设置:

def change
  create_join_table :ens, :ems do |t|
    t.index [:en_id, :em_id]
    t.index [:em_id, :en_id]
  end
end

太好了。我还必须在我的 En 和 Em 模型中表明这种 N:M 关系,我这样做了:

class En < ApplicationRecord
  has_and_belongs_to_many :ems
end
class Em < ApplicationRecord
  has_and_belongs_to_many :ens
end

到目前为止看起来不错,我的简单测试也很有效:

m = Em.create

n = En.new
n.ems << m
n.save

En.find(n.id).ems # equals [em], unsurprisingly

问题

当我想用 ActiveRecord 做任何更花哨的事情时,比如通过 En 通过 Em 找到一些 En,我遇到了问题:

Em.where(ens: En.where(id: En.first)) #fails
En.where(ems: Em.where(id: Em.first)) #fails
Error:
ActiveRecord::UnknownPrimaryKey: Unknown primary key for table ems_ens in model En::HABTM_Ems.

因为create_join_table没有给我主键,所以我要自己加一个。没问题,再迁移一次就可以了,对吧?

def change
  add_column :ems_ens, :id, :primary_key
end
Error:
DRb::DRbRemoteError: PG::UndefinedColumn: ERROR:  column "en_id" does not exist
LINE 1: ...CT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM...

如果有帮助,我在这篇文章中做了quickly write a repo 来完全复制这种行为。 我肯定在这里遗漏了一些明显的东西,对吧? 我的工作代码也有类似的问题,但奇怪的是我遇到了更深层次的问题(比如En.where(ems: Em.where(ens: ...)),关系中的层次结构数据库不好玩)。

其他信息

由于一位评论者的请求,自动生成的schema.rb 和描述的表格。

ActiveRecord::Schema.define(version: 2020_11_09_210057) do
  enable_extension "plpgsql"

  create_table "ems", force: :cascade do |t|
    t.string "mane"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "ems_ens", force: :cascade do |t|
    t.bigint "en_id", null: false
    t.bigint "em_id", null: false
    t.index ["em_id", "en_id"], name: "index_ems_ens_on_em_id_and_en_id"
    t.index ["en_id", "em_id"], name: "index_ems_ens_on_en_id_and_em_id"
  end

  create_table "ens", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end
end
                            Table "public.ems_ens"
 Column |  Type  | Collation | Nullable |               Default               
--------+--------+-----------+----------+-------------------------------------
 en_id  | bigint |           | not null | 
 em_id  | bigint |           | not null | 
 id     | bigint |           | not null | nextval('ems_ens_id_seq'::regclass)
Indexes:
    "ems_ens_pkey" PRIMARY KEY, btree (id)
    "index_ems_ens_on_em_id_and_en_id" btree (em_id, en_id)
    "index_ems_ens_on_en_id_and_em_id" btree (en_id, em_id)

                                          Table "public.ems"
   Column   |              Type              | Collation | Nullable |             Default             
------------+--------------------------------+-----------+----------+---------------------------------
 id         | bigint                         |           | not null | nextval('ems_id_seq'::regclass)
 mane       | character varying              |           |          | 
 created_at | timestamp(6) without time zone |           | not null | 
 updated_at | timestamp(6) without time zone |           | not null | 
Indexes:
    "ems_pkey" PRIMARY KEY, btree (id)

                                          Table "public.ens"
   Column   |              Type              | Collation | Nullable |             Default             
------------+--------------------------------+-----------+----------+---------------------------------
 id         | bigint                         |           | not null | nextval('ens_id_seq'::regclass)
 name       | character varying              |           |          | 
 created_at | timestamp(6) without time zone |           | not null | 
 updated_at | timestamp(6) without time zone |           | not null | 
Indexes:
    "ens_pkey" PRIMARY KEY, btree (id)

【问题讨论】:

  • 你能从底层数据库中添加你的 schema.rb 的内容和一些 SQL DESCRIBE 语句吗?
  • 嗨@3DPrintScanner,我将这两个都添加到原始问题中

标签: ruby-on-rails ruby rails-activerecord


【解决方案1】:

这里的问题是您没有加入ens 关系,虽然where 方法足够聪明,知道它需要查找en_id 列,但它不够聪明,无法添加加入您的查询。结果,连接表不在 FROM 子句中,因此 WHERE 子句最终无效。要修复它,您只需添加joins(:ens)。

不正确(注意 FROM 子句如何没有加入您的连接表):

irb(main):026:0> Em.where(ens: En.where(id: En.first))
  En Load (0.9ms)  SELECT "ens".* FROM "ens" ORDER BY "ens"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Em Load (0.8ms)  SELECT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM "ens" WHERE "ens"."id" = $1) LIMIT $2  [["id", 1], ["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR:  column "en_id" does not exist)
LINE 1: ...CT "ems".* FROM "ems" WHERE "ems"."id" IN (SELECT en_id FROM...
            

已更正(注意 WHERE 如何保持不变——只有 INNER JOIN 部分是新的):

irb(main):027:0> Em.joins(:ens).where(ens: En.where(id: En.first))
  En Load (0.3ms)  SELECT "ens".* FROM "ens" ORDER BY "ens"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Em Load (1.0ms)  SELECT "ems".* FROM "ems" INNER JOIN "ems_ens" ON "ems_ens"."em_id" = "ems"."id" INNER JOIN "ens" ON "ens"."id" = "ems_ens"."en_id" WHERE "ems"."id" IN (SELECT en_id FROM "ens" WHERE "ens"."id" = $1) LIMIT $2  [["id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Em id: 1, mane: nil, created_at: "2020-11-10 03:40:07", updated_at: "2020-11-10 03:40:07">]>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    相关资源
    最近更新 更多