2016-02-06 4 views
11

С PostgreSQL, мы можем сделать что-то вроде этого:Как использовать Postgres' перечисляемого типа с Ecto

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

От Ecto's official doc, нет родного типа для отображения в Postgres' перечисляемого типа. Этот module предоставляет настраиваемый тип для перечисляемых структур, но он сопоставляет целое число в базе данных. Я мог бы легко использовать эту библиотеку, но я бы предпочел использовать собственный нумерованный тип, который поставляется с базой данных.

Ecto обеспечивает также способ создания custom types, но, насколько я могу видеть, пользовательский тип должен отображать в родной типа экто ...

Любой знает, если это может быть сделано в схеме с Ecto ? Если да, то как будет работать миграция?

ответ

5

Вам нужно создать тип 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 name internal to Postgresql: вам нужно будет добавить инструкцию для создания типа перечисления в ваших миграциях базы данных. Конечным аргументом является список значений перечисления. Я использовал сигалью ~w, которая разделит строку пробелом, чтобы показать, насколько это может быть кратким. Я также добавил предложение, которое преобразует значения атомов в строковые значения, когда они проходят через схему Ecto.

+0

Спасибо за ваш ответ! Это выглядит многообещающе, но я не вижу, как использовать ColorsEnum в схеме. (Я получаю часть миграции). Когда я добавляю поле в схему, какой тип я должен использовать? ': string'? –

+0

Да, вы должны использовать ': string' в фактическом определении схемы. Причина, по которой тип Enum необходим для 'postgrex', чтобы сделать свою вещь, состоит в том, что он сопоставляет внутренний postgresql' oid' с типом Elixir, который является строкой в ​​этом случае. – asonge

+0

@asonge Хотя я уважаю ваш эликсир Фу, мне лично не нужен тип Ecto для каждого перечисления postgresql. Может быть, я скучаю, но зачем вам это нужно? Просто используйте строки и валидатор изменений. Https://hexdocs.pm/ecto/Ecto.Changeset.html#validate_inclusion/4 –

22

Может быть, я сделал что-то неправильно, но я просто создал тип и поле, как это:

# 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 

и это похоже на работу.

+10

Для тех, кто знаком с каркасом, решение JustMichael работает, но я думал, что добавлю, где код должен идти. Первый раздел кода находится в файле миграции внутри блока '' 'change do'''. Второй блок находится внутри файла модели внутри блока '' 'schema do'''. – jeffreymatthias

+1

Что происходит, когда вы передаете строку, которая не является «опубликована» или «редактируется»? Какая ошибка возникает? –

+1

@TerenceChow DB скорее всего поднимет что-то, и ваша операция с БД завершится неудачно. – JustMichael

7

Небольшое улучшение для @JustMichael. Если вам необходимо откатить, вы можете использовать:

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

Да! Хорошая точка зрения! –

3

добавление к тому, что сказали @JustMichael и @swennemen ... от экто 2.2.6 мы имеем экто .Migration.execute/2, который принимает вверх и вниз аргумент. Таким образом, мы можем сделать:

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

В нашей миграции файла внутри change блока, и экто сможет эффективно откатить.

Смежные вопросы