我一直在阅读polymorphic associations in Ecto,我同意这样的观点,即在表格之间使用本机数据库参考是有利的。
但是,大多数情况下会引用多态belongs_to
,而不是相反(has_many
)。我仍然不确定如何妥善处理它。
就我而言,它是关于Phoenix后端API的,我有一个Page
模型,其数量为Widgets
。每个小部件都有自己的表和模型,因为它需要存储和返回不同的字段。假设我们有一个TwoColumnWidget
和一个ThreeColumnWidget
,它们都有page_id
模型的引用Page
。
我如何理想地建模这个,现在我有一个中间Widget
模型(带数据库),它有一个列用于每个可能的窗口小部件类型,并选择一个id存在于其中一个列。这对我来说非常讨厌,因为我必须在数据库中为每个需要同步的小部件存储一个额外的行,以及为每个具体小部件获取正确的序列化器/视图的麻烦。
由于我的域有很多这种类型的关系,我想找到一个更好的解决方案来简化进一步的开发。有什么指针吗?
答案 0 :(得分:1)
我假设您的意思是每个窗口小部件类型都有自己的表(架构),我会回答这个问题。由于您似乎在窗口小部件和页面之间存在一对多关系而不是多对多关系,因此您可能需要考虑一个简单的映射数组方法,假设您使用支持它的数据库,如postgress。
您需要注意,地图使用字符串键而不是原子键保存在数据库中。因此,如果我们将结构保存到数据库中,当您将其读回时,它将是一个带有字符串键的映射。所以你需要把它重新放回到这样的地图中:
defmodule MyApp.Utils do
def cast(%{} = schema, params) do
struct schema, map_to_atom_keys(params)
end
def cast(module, params) when is_atom(module), do: cast(module.__struct__, params)
def map_to_atom_keys(%{} = params) do
Enum.reduce(params, %{}, fn({k, v}, acc) ->
Map.put(acc, to_atom(k), v)
end)
end
defp to_atom(key) when is_atom(key), do: key
defp to_atom(key) when is_binary(key), do: String.to_existing_atom(key)
def item_type(%{} = item), do: item_type(item.__struct__)
def item_type(item) do
Module.split(item)
|> Enum.reverse
|> hd
|> to_string
end
end
您可以使用小部件表执行类似操作,其中变量数据存储为地图。如果存储结构名称,则可以将其转换回结构。
defmodule Widget do
schema "widgets" do
field :embedded_type, :string
belongs_to :page, Page
field :widget, :map
end
def changeset(struct, params) do
struct
|> cast(params, [:embedded_type, :page_id, :widget])
|> handle_widget(params)
end
defp handle_widget(changeset, %{widget: widget}) do
changeset
|> put_change(:embedded_type, widget.__struct__ |> inspect)
end
end
然后您可以使用上面的代码将其转换回来。
您还可以使用embedded_schema创建每个窗口小部件类型,并使用Ecto为您执行转换。