如何在 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
很有帮助。