多态嵌入结构
Polymorphic embedded structs
我想在 Postgres 中存储一个树结构,我希望在树的每个节点上嵌入一个任意的 Elixir 结构,像这样:
defmodule Node do
use Ecto.Schema
schema "nodes" do
belongs_to :parent_node, Node
embeds_one :struct, ArbitraryDataType
end
end
但是,我认为 embeds_one
需要指定特定的结构数据类型,这不适用于我的情况。有解决办法吗?
我的备份计划是使用两个字段,一个用于结构 type
,一个用于结构 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
)
我最近解决了一个类似的问题,据我所知,您有两种选择。或者你...
使用自定义 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
有了这个,您可以 "simply" 在您的架构中使用 field :my_struct, EctoStruct
。
或者你...
重新考虑您对数据库的选择
树是一种内在连接的数据结构。根据您的确切要求和树的深度,使用 Postgres 遍历所述树可能会变得非常慢非常快。
虽然我解决了前面提到的问题,但我很早就遇到了性能问题,必须使用递归连接和物化视图来保持接近可用的响应时间。
从那时起,我切换到图形数据库 (Neo4j),我的性能问题完全消失了。这也很容易让您通过使用 Labels
.
将各种不同的结构类型编码到您的树中
根据您的特殊要求,这可能值得考虑。
以下库带来了对多态嵌入的支持:
我想在 Postgres 中存储一个树结构,我希望在树的每个节点上嵌入一个任意的 Elixir 结构,像这样:
defmodule Node do
use Ecto.Schema
schema "nodes" do
belongs_to :parent_node, Node
embeds_one :struct, ArbitraryDataType
end
end
但是,我认为 embeds_one
需要指定特定的结构数据类型,这不适用于我的情况。有解决办法吗?
我的备份计划是使用两个字段,一个用于结构 type
,一个用于结构 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
)
我最近解决了一个类似的问题,据我所知,您有两种选择。或者你...
使用自定义 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
有了这个,您可以 "simply" 在您的架构中使用 field :my_struct, EctoStruct
。
或者你...
重新考虑您对数据库的选择
树是一种内在连接的数据结构。根据您的确切要求和树的深度,使用 Postgres 遍历所述树可能会变得非常慢非常快。
虽然我解决了前面提到的问题,但我很早就遇到了性能问题,必须使用递归连接和物化视图来保持接近可用的响应时间。
从那时起,我切换到图形数据库 (Neo4j),我的性能问题完全消失了。这也很容易让您通过使用 Labels
.
根据您的特殊要求,这可能值得考虑。
以下库带来了对多态嵌入的支持: