多态嵌入式结构

时间:2016-10-23 21:52:40

标签: elixir ecto

我想在Postgres中存储一个树结构,我希望在树的每个节点上嵌入一个任意的Elixir结构,如下所示:

defmodule Node do
  use Ecto.Schema

  schema "nodes" do
    belongs_to :parent_node, Node
    embeds_one :struct, ArbitraryDataType
  end
end

但是,我认为embeds_one需要指定一个特定的结构数据类型,这对我的案例不起作用。有办法解决这个问题吗?

我的备份计划是使用两个字段,一个用于struct type,另一个用于struct fields,如下所示:

defmodule Node do
  use Ecto.Schema

  schema "nodes" do
    belongs_to :parent_node, Node
    field :struct_type, :string
    field :fields, :map
  end
end

为了首先保存记录,我需要使用__struct__字段来确定结构类型。然后,在从数据库中检索节点后,我将使用类似以下的逻辑来重建原始结构:

Enum.reduce(
  retrieved_node.fields,
  String.to_atom("Elixir.#{retrieved_node.struct_type}") |> struct,
  fn {k,v}, s -> Map.put(s, String.to_atom(k), v) end
)

2 个答案:

答案 0 :(得分:1)

以下库带来了对多态嵌入的支持:

https://github.com/mathieuprog/polymorphic_embed

答案 1 :(得分:0)

我最近解决了一个类似的问题,据我所知,您有两个选择。要么你...

使用自定义Ecto.Type

这使您可以精确控制要在字段中编码的数据类型。通过这样做,您可以相对轻松地保留结构的模块和字段。

可能的实现可能看起来像这样:

defmodule EctoStruct do
  use Ecto.Type

  def type, do: :map

  def cast(%_{} = struct), do: {:ok, struct}
  def cast(_), do: :error

  def dump(%module{} = struct) do
    data = %{
      "module" => Atom.to_string(module),
      "fields" => Map.from_struct(struct)
    }

    {:ok, data}
  end

  def load(%{"module" => module, "fields" => fields}) do
    module = String.to_existing_atom(module)
    fields = Enum.map(fields, fn {k, v} -> {String.to_existing_atom(k), v} end)

    {:ok, struct!(module, fields)}
  rescue
    _ -> :error
  end
end

使用此功能,您可以“仅”在架构中使用field :my_struct, EctoStruct

或者您...

重新考虑您对数据库的选择

树是固有连接的数据结构。根据您的确切要求以及您的树的深度,使用Postgres遍历该树可能会变得非常缓慢,非常快。

虽然我解决了前面提到的问题,但我很早就谈到了性能问题,必须使用递归联接和实例化视图来保持接近可用的响应时间。

从那时起,我切换到图形数据库(Neo4j),我的性能问题完全消失了。使用Labels也可以轻松地使您将各种不同的结构类型编码到树中。

根据您的特定要求,可能值得考虑。