Ответ 1
Ecto_enum теперь поддерживает тип перечисления postgres https://github.com/gjaldon/ecto_enum#using-postgress-enum-type
В PostgreSQL мы можем сделать что-то вроде этого:
CREATE TYPE order_status AS ENUM ('placed','shipping','delivered')
Из Ecto official doc, нет никакого родного типа для сопоставления перечисляемого типа Postgres. Этот модуль предоставляет настраиваемый тип для перечисляемых структур, но он сопоставляет целое число в базе данных. Я мог бы легко использовать эту библиотеку, но я бы предпочел использовать собственный нумерованный тип, который поставляется с базой данных.
Ecto предоставляет также способ создания настраиваемых типов, но, насколько я вижу, пользовательский тип должен отображать собственный тип Ecto...
Кто-нибудь знает, можно ли это сделать в схеме с Ecto? Если да, как будет работать миграция?
Ecto_enum теперь поддерживает тип перечисления postgres https://github.com/gjaldon/ecto_enum#using-postgress-enum-type
Возможно, я сделал что-то не так, но я только что создал тип и поле вроде этого:
# 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 для каждого перечисления postgresql. В определении схемы вы просто имеете тип :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"
будет именем enum, внутренним для Postgresql: вам нужно будет добавить инструкцию для создания типа перечисления в ваших миграциях базы данных. Конечным аргументом является список значений перечисления. Я использовал сигилу ~w
, которая разделит строку на пробел, чтобы показать, насколько это может быть кратким. Я также добавил предложение, которое преобразует значения атомов в строковые значения, когда они проходят через схему Ecto.
добавив к тому, что @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 сможет эффективно откатиться.
Обобщая все кусочки здесь и там в ответах и комментариях. См. "Перечисляемые типы" в руководстве по PostgreSQL, чтобы узнать больше об используемых командах SQL.
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
Поскольку схема не может видеть тип базы данных, созданный в процессе миграции, используйте Ecto.Changeset.validate_inclusion/4
в Order.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