如何在 Ecto 模式中存储多维数组?
How to store a multidimensional array in an Ecto schema?
假设我希望将一组坐标 ([[x1,y1], [x2,y2]]
) 存储到 Postgres 中。什么是首选数据类型?文档允许
形式的数组
:coordinates, {:array, :float}
但这只对一维数组有用。
您可以使用
field :coordinates, {:array, {:array, :float}}
但这不是最佳解决方案。看起来很糟糕,并允许将类似 [[1.0]]
的内容插入数据库,这显然是不协调的。我更喜欢自定义类型。
#lib/coordinates.ex
defmodule Coordinates do
@behaviour Ecto.Type
def type, do: {:array, :float}
def cast([l1, l2] = coordinates) when is_list(l1) and length(l1) == 2 and is_list(l2) and length(l2) == 2 do
flattened_list = coordinates |> List.flatten
cond do
Enum.all?(flattened_list, &(is_float(&1))) ->
{:ok, list}
# add additional [integer, string, ...] to float transformations here if necessary
# Enum.all?(flattened_list, &(is_float(&1) || is_integer(&1))) ->
# normalized = flattened_list |> Enum.map(&(&1 / 1)) |> Enum.split(2) |> Tuple.to_list
#
# {:ok, normalized}
true ->
:error
end
end
def cast(_), do: :error
def load(list) when is_list(list) and length(list) == 4 do
two_dimensional_list = list |> Enum.split(2) |> Tuple.to_list
{:ok, two_dimensional_list}
end
def dump(list) when is_list(list) and length(list) == 2 do
flattened_list = coordinates |> List.flatten
{:ok, flattened_list}
end
def dump(_), do: :error
end
#web/models/your_model.ex
schema "your_model" do
field :coordinates, Coordinates
end
根据文档 Ecto.Type 行为需要实现 4 个函数。
type should output the name of the DB type
cast should receive any type and output your custom Ecto type
load should receive the DB type and output your custom Ecto type
dump should receive your custom Ecto type and output the DB type
上面例子中最重要的是转储和加载(一维和二维列表之间的转换)和很多守卫(确保无效数据将 return :error)
我建议阅读完整的 Ecto.Type
文档:
https://hexdocs.pm/ecto/Ecto.Type.html
很有帮助。
假设我希望将一组坐标 ([[x1,y1], [x2,y2]]
) 存储到 Postgres 中。什么是首选数据类型?文档允许
:coordinates, {:array, :float}
但这只对一维数组有用。
您可以使用
field :coordinates, {:array, {:array, :float}}
但这不是最佳解决方案。看起来很糟糕,并允许将类似 [[1.0]]
的内容插入数据库,这显然是不协调的。我更喜欢自定义类型。
#lib/coordinates.ex
defmodule Coordinates do
@behaviour Ecto.Type
def type, do: {:array, :float}
def cast([l1, l2] = coordinates) when is_list(l1) and length(l1) == 2 and is_list(l2) and length(l2) == 2 do
flattened_list = coordinates |> List.flatten
cond do
Enum.all?(flattened_list, &(is_float(&1))) ->
{:ok, list}
# add additional [integer, string, ...] to float transformations here if necessary
# Enum.all?(flattened_list, &(is_float(&1) || is_integer(&1))) ->
# normalized = flattened_list |> Enum.map(&(&1 / 1)) |> Enum.split(2) |> Tuple.to_list
#
# {:ok, normalized}
true ->
:error
end
end
def cast(_), do: :error
def load(list) when is_list(list) and length(list) == 4 do
two_dimensional_list = list |> Enum.split(2) |> Tuple.to_list
{:ok, two_dimensional_list}
end
def dump(list) when is_list(list) and length(list) == 2 do
flattened_list = coordinates |> List.flatten
{:ok, flattened_list}
end
def dump(_), do: :error
end
#web/models/your_model.ex
schema "your_model" do
field :coordinates, Coordinates
end
根据文档 Ecto.Type 行为需要实现 4 个函数。
type should output the name of the DB type
cast should receive any type and output your custom Ecto type
load should receive the DB type and output your custom Ecto type
dump should receive your custom Ecto type and output the DB type
上面例子中最重要的是转储和加载(一维和二维列表之间的转换)和很多守卫(确保无效数据将 return :error)
我建议阅读完整的 Ecto.Type
文档:
https://hexdocs.pm/ecto/Ecto.Type.html
很有帮助。