Jea*_*tte 14 postgresql enums elixir ecto
使用PostgreSQL,我们可以这样做:
CREATE TYPE order_status AS ENUM ('placed','shipping','delivered')
从Ecto的官方文档来看,没有本地类型可以映射Postgres的枚举类型.此模块为枚举结构提供自定义类型,但它映射到数据库中的整数.我可以轻松地使用该库,但我更喜欢使用数据库附带的本机枚举类型.
Ecto还提供了一种创建自定义类型的方法,但据我所知,自定义类型必须映射到本机Ecto类型...
任何人都知道这是否可以在Ecto的架构中完成?如果是,迁移将如何运作?
NoD*_*ame 28
也许我做错了什么,但我刚刚创建了类型和字段:
# creating the database type
execute("create type post_status as enum ('published', 'editing')")
# creating a table with the column
create table(:posts) do
  add :post_status, :post_status, null: false
end
然后只是使字段成为一个字符串:
field :post_status, :string
它似乎工作.
swe*_*men 10
@JustMichael的小增强.如果需要回滚,可以使用:
def down do
  drop table(:posts)
  execute("drop type post_type")
end
总结答案和评论中的所有点点滴滴。有关使用的 SQL 命令的更多信息,请参阅PostgreSQL 手册中的“枚举类型”。
Ecto 3.0.0 及以上从Ecto3.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.xx 及以下”相同。
Ecto 2.xx 及以下生成迁移后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
因为schema无法看到迁移中创建的数据库类型,使用Ecto.Changeset.validate_inclusion/4inOrder.changeset/2来保证输入有效。
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
您需要为每个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内部的枚举名称:您将需要添加一个语句来在数据库迁移中创建枚举类型.最后一个参数是枚举值列表.我使用了一个~wsigil,它将用空格分割字符串,以显示它是多么简洁.我还添加了一个子句,当它们通过Ecto模式时将原子值转换为字符串值.
Ecto_enum 现在支持 postgres 枚举类型https://github.com/gjaldon/ecto_enum#using-postgress-enum-type
从@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将能够有效回滚。