【问题标题】:Polymorphic has many though with STI in rails多态性虽然有很多 STI 在轨
【发布时间】:2017-06-13 19:59:01
【问题描述】:

我在 Rails 协会遇到了不寻常的情况,并创建了简单的示例来证明这一点。 假设我们有 4 个模型: 1) 列表.rb

class List < ApplicationRecord;end

2) 显示.rb

class Show < List;end

3)joined_table.rb

class JoinedTable < ApplicationRecord
  belongs_to :listable, polymorphic: true
  belongs_to :work
end

4) 工作.rb

class Work < ApplicationRecord
  has_many :joined_tables, dependent: :destroy
  has_many :lists, through: :joined_tables, source: :listable, source_type: 'List'
  has_many :shows, through: :joined_tables, source: :listable, source_type: 'Show'
end

因此我们有了下一个 schema.rb

create_table "joined_tables", force: :cascade do |t|
  t.integer  "listable_id"
  t.string   "listable_type"
  t.integer  "work_id"
  t.datetime "created_at",    null: false
  t.datetime "updated_at",    null: false
end

create_table "lists", force: :cascade do |t|
  t.string   "type"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

create_table "works", force: :cascade do |t|
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

现在当你得到所有信息后,谁能解释我为什么要尝试

Work.first.shows

它生成

 Show Load (0.2ms)  SELECT "lists".* FROM "lists" INNER JOIN "joined_tables" ON "lists"."id" = "joined_tables"."listable_id" WHERE "lists"."type" IN ('Show') AND "joined_tables"."work_id" = ? AND "joined_tables"."listable_type" = ?  [["work_id", 1], ["listable_type", "Show"]]

这看起来像是一个正确的请求,但是当我尝试这样做时

Work.first.lists

它生成

  List Load (0.1ms)  SELECT "lists".* FROM "lists" INNER JOIN "joined_tables" ON "lists"."id" = "joined_tables"."listable_id" WHERE "joined_tables"."work_id" = ? AND "joined_tables"."listable_type" = ?  [["work_id", 1], ["listable_type", "List"]]

这是不一样的,并且缺少检查列表。类型。

我知道这是一种糟糕的架构,并且可能应该在 Work 中使用关联范围,而将 JoinedTable 中的多态性放在一边,但这只是我在项目中遇到的问题。我也很想听听替代解决方案,但最重要的是我想知道为什么 rails 会生成不同的查询。

附:在示例中,我使用的是 rails 5 和 SQLite。但在使用 rails 4 和 postgres 的实际项目中,情况更糟。如果我先做 Work.first.shows 然后 Work.first.lists 它会生成

  List Load (0.4ms)  SELECT "lists".* FROM "lists" INNER JOIN "list_works" ON "lists"."id" = "list_works"."listable_id" WHERE "list_works"."work_id" = $1 AND "list_works"."listable_type" IN ('List', 'Show')  [["work_id", 50]]

但如果我立即执行 Work.first.lists 它会生成

  List Load (0.8ms)  SELECT "lists".* FROM "lists" INNER JOIN "list_works" ON "lists"."id" = "list_works"."listable_id" WHERE "list_works"."work_id" = $1 AND "list_works"."listable_type" = 'List'  [["work_id", 50]]

感谢您的帮助!

【问题讨论】:

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


    【解决方案1】:

    Work.first.lists 不会生成lists.type,因为List 是层次结构中的基类,因此列表是lists 表中所有类型的所有记录。另外,请记住,所有 List 记录在 DB 中都会有 type = nil,因为 List 是基类。

    此外,当您使用 STI 的多态关联时,它默认总是将基类的名称分配给多态关联的*_type 列。在您的情况下,JoinedTable 中的 listable_type 将始终为 "List"

    第二个问题是因为当您首先执行Work.first.lists 时,Rails 不知道有子Show 类,因为那时它还没有加载。您应该通过访问它来使其自动加载(它发生在您执行Work.first.shows 时),或者使用类似require_dependency 的东西。

    这里有一个适合您的解决方案:

    class Work < ApplicationRecord
      has_many :joined_tables, dependent: :destroy
      has_many :lists, -> { where(type: nil) }, through: :joined_tables, source: :listable, source_type: "List"
      has_many :shows, -> { where(type: "Show") }, through: :joined_tables, source: :listable, source_type: "List"
    end
    

    【讨论】:

    • STI 基类将其类名存储在type 列中,与任何子类相同。
    • 感谢@chumakoff 的解释!很有意义。只是想澄清一件事,这是否意味着要正确使用这样的架构,应该有一些基类,并且 List 和 Show 都应该继承自它?
    • 另一个问题是它应该如何以正确的方式完成?我认为应该有 has_many :lists, -> { where('lists.type = \'List\'') },通过: :joined_tables has_many :shows, -> { where('lists.type = \'Show\ '') }, through: :joined_tables, source: :list in Work 和 belongs_to :list, foreign_key: 'listable_id' injoined table 这让我们不用在连接表中使用多态性并正常工作。也许你有更好的方法@chumakoff?
    • 在您的情况下,您不需要单独的 List 和 Show 基类,但我认为如果您拥有它,那会更好,并且将来可能会节省大量时间和精力.我编辑了答案并添加了一个可行的解决方案。请从头到尾再读一遍。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多