如何使用Postgres'带有Ecto的枚举类型

时间:2016-02-06 20:05:58

标签: postgresql enums elixir ecto

使用PostgreSQL,我们可以这样做:

CREATE TYPE order_status AS ENUM ('placed','shipping','delivered')

Ecto's official doc开始,没有原生类型来映射Postgres'枚举类型。此module为枚举结构提供自定义类型,但它映射到数据库中的整数。我可以轻松地使用该库,但我更喜欢使用数据库附带的本机枚举类型。

Ecto还提供了一种创建custom types的方法,但据我所知,自定义类型必须映射到本机Ecto类型......

任何人都知道是否可以在使用Ecto的架构中完成此操作?如果是,迁移将如何运作?

6 个答案:

答案 0 :(得分:24)

也许我做错了但是我刚刚创建了类型和字段:

field :post_status, :string

然后只是将字段设为字符串:

systemjs.config.js

它似乎有效。

答案 1 :(得分:9)

@JustMichael的小增强。如果需要回滚,可以使用:

def down do
  drop table(:posts)
  execute("drop type post_type")
end

答案 2 :(得分:5)

您需要为每个postgresql枚举创建一个Ecto类型。在架构定义中,您只需要类型为:string。在迁移中,您将类型设置为模块名称。但是,这可能会变得非常繁琐,所以我在我的项目中使用了Postgresql枚举中的以下宏:

defmodule MyDB.Enum do

  alias Postgrex.TypeInfo

  defmacro defenum(module, name, values, opts \\ []) do
    quote location: :keep do
      defmodule unquote(module) do

        @behaviour Postgrex.Extension

        @typename unquote(name)
        @values unquote(values)

        def type, do: :string

        def init(_params, opts), do: opts

        def matching(_), do: [type: @typename]

        def format(_), do: :text

        def encode(%TypeInfo{type: @typename}=typeinfo, str, args, opts) when is_atom(str), do: encode(typeinfo, to_string(str), args, opts)
        def encode(%TypeInfo{type: @typename}, str, _, _) when str in @values, do: to_string(str)
        def decode(%TypeInfo{type: @typename}, str, _, _), do: str

        def __values__(), do: @values

        defoverridable init: 2, matching: 1, format: 1, encode: 4, decode: 4

        unquote(Keyword.get(opts, :do, []))
      end
    end
  end

end

可能的用法:

import MyDB.Enum
defenum ColorsEnum, "colors_enum", ~w"blue red yellow"

ColorsEnum将是模块名称,"colors_enum"将是Postgresql内部的枚举名称:您需要添加一个语句以在数据库迁移中创建枚举类型。最后一个参数是枚举值列表。我使用了一个~w sigil,它将用空格分割字符串,以显示它的简洁程度。我还添加了一个子句,当它们通过Ecto模式时将原子值转换为字符串值。

答案 3 :(得分:4)

Ecto_enum现在支持postgres枚举类型https://github.com/gjaldon/ecto_enum#using-postgress-enum-type

答案 4 :(得分:4)

添加到@JustMichael和@swennemen所说的......从ecto 2.2.6开始,我们有Ecto.Migration.execute / 2,它需要一个向上和向下的arg。所以我们可以这样做:

execute("create type post_status as enum ('published', 'editing')", "drop type post_status")

change块内的迁移文件中,ecto将能够有效回滚。

答案 5 :(得分:3)

在答案和注释中总结所有零散的内容。有关使用的SQL命令的更多信息,请参见PostgreSQL手册中的"Enumerated Types"

Ecto 3.0.0及更高版本

Ecto 3.0.0起,有Ecto.Migration.execute/2表示“ 执行可逆SQL命令”,因此可以在change/0中使用它:

迁移

使用mix ecto.gen.migration create_orders生成迁移后:

defmodule CreateOrders do
  use Ecto.Migration

  @type_name "order_status"

  def change do    
    execute(
      """
      CREATE TYPE #{@type_name}
        AS ENUM ('placed','shipping','delivered')
      """,
      "DROP TYPE #{@type_name}"
     )

    create table(:orders) do
      add :order_status, :"#{@type_name}", null: false
      timestamps()
    end
  end
end

架构

这与“ Ecto 2.x.x及以下版本”下的内容相同。

Ecto 2.x.x及以下版本

迁移

使用mix ecto.gen.migration create_orders生成迁移后:

defmodule CreateOrders do
  use Ecto.Migration

  @type_name "order_status"

  def up do    
    execute(
      """
      CREATE TYPE #{@type_name}
        AS ENUM ('placed','shipping','delivered'})
      """)

    create table(:orders) do
      add :order_status, :"#{@type_name}", null: false
      timestamps()
    end
  end

  def down do
    drop table(:orders)
    execute("DROP TYPE #{@type_name}")
  end
end

架构

由于架构无法查看在迁移中创建的数据库类型,因此请使用Order.changeset/2中的Ecto.Changeset.validate_inclusion/4来确保输入有效。

defmodule Order do

  use Ecto.Schema
  import Ecto.Changeset

  schema "orders" do
    field :order_status, :string    
    timestamps()
  end

  def changeset(
    %__MODULE__{} = order,
    %{} = attrs
  ) do

    fields = [ :order_status ]

    order
    |> cast(attrs, fields)
    |> validate_required(fields)
    |> validate_inclusion(
         :order_status,
         ~w(placed shipping delivered)
       )
  end
end
相关问题