如何在 Ecto 模式中存储多维数组?

o_o*_*o-- 5 elixir ecto

假设我希望将一组坐标 ( [[x1,y1], [x2,y2]]) 存储到 Postgres 中。首选的数据类型是什么?该文档允许采用以下形式的数组

:coordinates, {:array, :float}
Run Code Online (Sandbox Code Playgroud)

但这仅对一维数组有用。

ama*_*lai 7

您可以使用

field :coordinates, {:array, {:array, :float}}
Run Code Online (Sandbox Code Playgroud)

但这不是最好的解决方案。看起来很糟糕,并允许将类似的内容[[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
Run Code Online (Sandbox Code Playgroud)

根据文档 Ecto.Type 行为期望实现 4 个功能。

type 应输出 DB 的名称 类型
转换应接收任何类型并输出 您的自定义 Ecto 类型
加载应接收 DB 类型并输出 您的自定义 Ecto 类型
转储应接收您的自定义 Ecto 类型并输出 DB 类型

上面的示例中最重要的是转储和加载(一维列表和二维列表之间的转换)以及大量防护(确保无效数据将返回:错误)

我建议阅读完整Ecto.Type文档: https: //hexdocs.pm/ecto/Ecto.Type.html
这非常有帮助。