多态嵌入式JSON值类型?

时间:2016-05-21 16:13:55

标签: elixir phoenix-framework ecto

我有一个用户可以发送自定义数据的应用程序,它将存储在我的应用程序中。目前我这样做:

defmodule MyApp.CustomField do
  use Ecto.Schema
  import Ecto.Changeset
  alias MyApp.{ Time }


  defmodule ValueTypes do
    def c_INTEGER, do: "integer"
    def c_BOOLEAN, do: "boolean"
    def c_STRING,  do: "string"


    def c_TRUE,    do: "true"
    def c_FALSE,   do: "false"
  end


  embedded_schema do
    field :field
    field :value
    field :type
  end


  @required_fields ~w( field value )
  @optional_fields ~w( type )


  def changeset(model, params \\ :empty) do
    {:ok, value, type} = cast(model.field, model.value)
    params = %{value: value, type: type}

    model
    |> cast(params, @required_fields, @optional_fields)
    |> validate_length(:field, min: 1, max: 20)
    |> validate_length(:value, min: 1, max: 255)
    |> put_change(:type, type)
  end

  # date in unix timestamp
  def cast(field, value) when is_integer(value) do
    value = Integer.to_string(value)
    case String.slice(field, -3, 3) do
      "_at" -> {:ok, value, ValueTypes.c_DATE}
      _     -> {:ok, value, ValueTypes.c_INTEGER}


    end
  end

  def cast(field, value) when is_boolean(value), do: {:ok, (if value, do: ValueTypes.c_TRUE, else: ValueTypes.c_FALSE), ValueTypes.c_BOOLEAN}
  def cast(field, value) when is_binary(value),  do: {:ok, value, ValueTypes.c_STRING}
  def cast(field, value), do: {:error, "Invalid field value"}


end

目前我将所有内容保存为字符串并保留Type字段以在我的应用程序和数据库之间转换数据类型。此外,我必须将所有内容转换为字符串,因为Ecto(AFAIK)不支持在嵌入式JSON中使用多态类型。

关于索引字段和值是否有问题?

我想过使用自定义Ecto类型,但这是不可能的,因为我依赖于我的强制转换函数中的两个值(当投出日期时,我会看到字段名称末尾为“_at”)

有没有更好的方法来实现这一目标?

1 个答案:

答案 0 :(得分:1)

如果您使用Postgres作为您的数据库,那么您想要的行为已经存在。您需要在架构中使用:map数据类型。你可以这样做:

defmodule MyApp.CustomField do
  use Ecto.Schema
  import Ecto.Changeset
  alias MyApp.{ Time }

  embedded_schema do
    field :data, :map
  end
end

然后你可以%CustomField{data: %{"field" => "custom", "value" => 1}} |> Repo.insert!。此处1将在data列的JSON中存储为整数。当您从数据库获取此记录时,Ecto将使用像Poison这样的JSON序列化程序来解码JSON数据,它将在Elixir中以整数形式返回1