如何在 Ecto 中使用 Postgres 的枚举类型
How to use Postgres' enumerated type with Ecto
使用 PostgreSQL,我们可以做这样的事情:
CREATE TYPE order_status AS ENUM ('placed','shipping','delivered')
From Ecto's official doc, there is no native type to map the Postgres' enumerated type. This module 为枚举结构提供自定义类型,但它映射到数据库中的整数。我可以很容易地使用那个库,但我更喜欢使用数据库附带的本机枚举类型。
Ecto 还提供了一种创建方法 custom types,但据我所知,自定义类型必须映射到原生 Ecto 类型...
有人知道这是否可以在 Ecto 的架构中完成吗?如果是,迁移将如何进行?
您需要为每个 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
印记,它会用空格分割字符串,以显示它有多简洁。我还添加了一个子句,当它们通过 Ecto 模式时将原子值转换为字符串值。
也许我做错了什么,但我只是像这样创建了类型和字段:
# 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
它似乎有效。
@JustMichael 的小改进。如果需要回滚,可以使用:
def down do
drop table(:posts)
execute("drop type post_type")
end
Ecto_enum 现在支持 postgres 枚举类型 https://github.com/gjaldon/ecto_enum#using-postgress-enum-type
添加@JustMichael 和@swennemen 所说的...从 ecto 2.2.6 开始,我们有 Ecto.Migration.execute/2,它需要一个 up 和一个 down arg。所以我们可以这样做:
execute("create type post_status as enum ('published', 'editing')", "drop type post_status")
在我们的迁移文件中change
块里面,ecto就可以有效回滚了。
总结了答案和评论中的所有点点滴滴。有关使用的 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
架构
因为schema看不到迁移中创建的数据库类型,所以在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
使用 PostgreSQL,我们可以做这样的事情:
CREATE TYPE order_status AS ENUM ('placed','shipping','delivered')
From Ecto's official doc, there is no native type to map the Postgres' enumerated type. This module 为枚举结构提供自定义类型,但它映射到数据库中的整数。我可以很容易地使用那个库,但我更喜欢使用数据库附带的本机枚举类型。
Ecto 还提供了一种创建方法 custom types,但据我所知,自定义类型必须映射到原生 Ecto 类型...
有人知道这是否可以在 Ecto 的架构中完成吗?如果是,迁移将如何进行?
您需要为每个 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
印记,它会用空格分割字符串,以显示它有多简洁。我还添加了一个子句,当它们通过 Ecto 模式时将原子值转换为字符串值。
也许我做错了什么,但我只是像这样创建了类型和字段:
# 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
它似乎有效。
@JustMichael 的小改进。如果需要回滚,可以使用:
def down do
drop table(:posts)
execute("drop type post_type")
end
Ecto_enum 现在支持 postgres 枚举类型 https://github.com/gjaldon/ecto_enum#using-postgress-enum-type
添加@JustMichael 和@swennemen 所说的...从 ecto 2.2.6 开始,我们有 Ecto.Migration.execute/2,它需要一个 up 和一个 down arg。所以我们可以这样做:
execute("create type post_status as enum ('published', 'editing')", "drop type post_status")
在我们的迁移文件中change
块里面,ecto就可以有效回滚了。
总结了答案和评论中的所有点点滴滴。有关使用的 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
架构
因为schema看不到迁移中创建的数据库类型,所以在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