【问题标题】:How to handle schemas polymorphism in Phoenix?如何处理 Phoenix 中的模式多态性?
【发布时间】:2018-08-25 23:15:52
【问题描述】:

在 Phoenix 中处理多态关联的推荐方法似乎是添加一个包含对其他模式的引用的中间模式:

所以如果我想用不同种类的动物创建模式,我会这样做:

defmodule Animal do
  use Ecto.Model

  schema "animals" do
    belongs_to(:dog, Dog)
    belongs_to(:cat, Cat)
    belongs_to(:owner, PetOwner)
  end
end

defmodule Dog do
  use Ecto.Model

  schema "dogs" do
  end
end

defmodule Cat do
  use Ecto.Model

  schema "cats" do
  end
end

defmodule PetOwner do
  use Ecto.Model

  schema "pet_owners" do
    has_one(:pet, Animal)
  end
end

但我也可以拥有包含二进制字段和类型的 PetOwner 模式:

defmodule Dog do
  use Ecto.Model

  schema "dogs" do
  end
end

defmodule Cat do
  use Ecto.Model

  schema "cats" do
  end
end

defmodule PetOwner do
  use Ecto.Model

  schema "pet_owners" do
    field(:pet, :binary)
    field(:pet_type, :integer)
  end
end

或者甚至只是对所有者模式中的所有动物有一个可为空的引用:

defmodule Dog do
  use Ecto.Model

  schema "dogs" do
    belongs_to(:owner, PetOwner)
  end
end

defmodule Cat do
  use Ecto.Model

  schema "cats" do
    belongs_to(:owner, PetOwner)
  end
end

defmodule PetOwner do
  use Ecto.Model

  schema "pet_owners" do
    has_one(:cat, Cat)
    has_one(:dog, Dog)
  end
end

第一种方法似乎增加了模式的复杂性。不同方法的优缺点是什么?

编辑:假设宠物主人只能拥有一只宠物,如果架构允许多只宠物,则验证在变更集中完成。

【问题讨论】:

  • 我可能会选择最后一个。没有额外的复杂性,不会失去完整性并且简单明了。

标签: elixir phoenix-framework polymorphic-associations ecto


【解决方案1】:

我花了很多时间阅读博客文章和类似问题的答案。我也在 E​​lixir 的话语中问了这个问题:https://elixirforum.com/t/how-to-handle-schemas-polymorphism-in-phoenix/13269/24,得到了很好的回答。

重复的一点是,这个问题更像是一个 SQL 问题,而不是 Phoenix 或 Ecto 问题。 确实,Ecto 提供了一些方法来解决这个问题,但是这个问题的解决应该从“如何处理关系数据库中的多态关联”开始。

请注意,如果您在这里寻找解决“belong_to”多态关联的解决方案(即,如果一个具体表属于两个或多个多态表),则在 Ecto 的文档中有一个entire section。此答案适用于“has_many”多态关联。

以下是ndac_todoroki 在 Elixir 论坛上回复我的帖子时所写的不同方法的比较,所有功劳归他所有:

单表继承

这是当你有一张大桌子(动物)并且每个具体的动物桌子都是该桌子的一个细分。您不会有多个表。

优点

  • 您将只有一张桌子。
  • 获取所有动物非常容易
  • 它可以在 Ecto 中轻松实现,因为所有具体的动物模块都可以具有彼此不同的架构,但引用同一个大表。

缺点

  • 如果您希望每种动物都有唯一的字段,则到处都会得到 NULL 列(但在使用具体模块的架构时您不在乎:它不会出现。)

类表继承

这是当您有一个基表(= Animal)时,每个具体的动物表都有对基表的引用以及它们的唯一字段。 (动物{id:1,出生:“20180101”,接种疫苗:真}动物{id:2,出生:“20111225”,接种疫苗:假/猫{animal_id:1,颜色:“棕色”}蛇{animal_id:2 , 长度:150})

优点

  • 如果您只需要基本信息,列出所有动物非常容易
  • 没有 NULL 列

缺点

  • 虽然从具体模块获取(动物的)完整信息非常容易(您只需 JOIN :animal),但从基础模块获取完整信息却不是(您不知道要加入什么)。
  • 除非您编写某种类似于 Rails 中的多态表(animal_type 和 animal_id)的逻辑,在 Ecto 文档中提到“不可取”。

具体表继承

不会创建动物表。每个具体的动物表都将包含所有基本信息 + 它们的独特信息。如果您想确保所有动物都是动物,这将很好,但您不直接使用 Animals。 (我宁愿创建一个名为 Animal 的协议而不是这样做)

优点

  • 获取混凝土动物时无需加入
  • 适用于 Ecto 的抽象表(Animal 将是一个抽象表)

缺点

  • 迁移时需要小心,不要破坏整个继承
  • 唯一的断言必须完全在应用程序逻辑上完成
  • 尤其是 id 在所有动物表中必须是唯一的,因此最好使用 :binary_ids。
  • 在 Animal 中使用键进行的任何搜索都应该遍历所有表(呃)

抽象表

这在 Ecto 文档中有所描述。 (Sample repo) 仅使用它似乎对您的使用没有太大帮助,但如果您正在查看 STI 或 CTI,它可能有助于实施。

创建类表继承时,每个表都引用一个 Animal 基表。使用抽象表,您可以将其拆分为每个具体动物表的多个基表,例如 cat 表将引用 cat_base,snake 将引用到 snake_base,其中 cat_base 和 snake_base 将具有相同的列。然后我们会创建一个抽象表 animal,当你 cat |> Animal.add_base_animal_info() 时会创建一个 cat_base。

优点

  • 您可以从 Animals 中检索具体动物

缺点

  • 你需要加入每个具体的 *_base 表来做 list_animals

我认为这介于类表继承和具体表继承之间。

嵌入数据

嵌入可以在 Postgres 和 MongoDB 等中完成。你可以有一个单独的动物表,其中包含一个接受地图的字段(字段:详细信息,:地图)。然后定义许多具体的动物模块,其模式引用该动物表,有一个 embeds_one :details, CatDetails,您可以在其中为 CatDetails 定义一个 embed_schema。 (这个例子是带有嵌入的 STI)

优点

  • 桌子会很干净
  • 没有空值
  • 列出所有动物很容易
  • 使用具体动物的模式存储数据将验证您通过的地图的形状

缺点

  • 内部地图的形状:无法在 DB 级别验证详细信息
  • 在嵌入中搜索字段可能不好(取决于您使用的数据库)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 2011-05-09
    • 1970-01-01
    • 2013-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多