Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #16 from gjaldon/pg-enum-type
Pg enum type
- Loading branch information
Showing
11 changed files
with
357 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
defmodule EctoEnum.Postgres do | ||
@moduledoc false | ||
|
||
def defenum(module, type, list) do | ||
list = if Enum.all?(list, &is_atom/1) do | ||
list | ||
else | ||
Enum.map(list, &String.to_atom/1) | ||
end | ||
|
||
quote do | ||
type = unquote(type) |> Macro.escape | ||
list = unquote(list) |> Macro.escape | ||
|
||
defmodule unquote(module) do | ||
@behaviour Ecto.Type | ||
alias EctoEnum.Postgres | ||
|
||
@atom_list list | ||
@atom_string_map for atom <- list, into: %{}, do: {atom, Atom.to_string(atom)} | ||
@string_atom_map for atom <- list, into: %{}, do: {Atom.to_string(atom), atom} | ||
@valid_values list ++ Map.values(@atom_string_map) | ||
|
||
def type, do: unquote(type) | ||
|
||
def cast(term) do | ||
Postgres.cast(term, @valid_values, @string_atom_map) | ||
end | ||
|
||
def load(value) when is_binary(value) do | ||
Map.fetch(@string_atom_map, value) | ||
end | ||
|
||
def dump(term) do | ||
Postgres.dump(term, @valid_values, @atom_string_map) | ||
end | ||
|
||
# Reflection | ||
def __enum_map__(), do: @atom_list | ||
def __valid_values__(), do: @valid_values | ||
|
||
def create_type() do | ||
types = Enum.join(unquote(list), ", ") | ||
sql = "CREATE TYPE #{unquote type} AS ENUM (#{types})" | ||
Ecto.Migration.execute sql | ||
end | ||
|
||
def drop_type() do | ||
sql = "DROP TYPE #{unquote type}" | ||
Ecto.Migration.execute sql | ||
end | ||
end | ||
end | ||
end | ||
|
||
|
||
def cast(atom, valid_values, _) when is_atom(atom) do | ||
if atom in valid_values do | ||
{:ok, atom} | ||
else | ||
:error | ||
end | ||
end | ||
def cast(string, _, string_atom_map) when is_binary(string) do | ||
Map.fetch(string_atom_map, string) | ||
end | ||
def cast(_, _, _), do: :error | ||
|
||
|
||
def dump(atom, _, atom_string_map) when is_atom(atom) do | ||
Map.fetch(atom_string_map, atom) | ||
end | ||
def dump(string, valid_values, _) when is_binary(string) do | ||
if string in valid_values do | ||
{:ok, string} | ||
else | ||
:error | ||
end | ||
end | ||
def dump(_, _, _), do: :error | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
defmodule EctoEnumTest do | ||
use ExUnit.Case | ||
|
||
import Ecto.Changeset | ||
import EctoEnum | ||
defenum StatusEnum, registered: 0, active: 1, inactive: 2, archived: 3 | ||
|
||
defmodule User do | ||
use Ecto.Schema | ||
|
||
schema "users" do | ||
field :status, StatusEnum | ||
end | ||
end | ||
|
||
alias Ecto.Integration.TestRepo | ||
|
||
test "accepts int, atom and string on save" do | ||
user = TestRepo.insert!(%User{status: 0}) | ||
user = TestRepo.get(User, user.id) | ||
assert user.status == :registered | ||
|
||
user = Ecto.Changeset.change(user, status: :active) | ||
user = TestRepo.update! user | ||
assert user.status == :active | ||
|
||
user = Ecto.Changeset.change(user, status: "inactive") | ||
user = TestRepo.update! user | ||
assert user.status == "inactive" | ||
|
||
user = TestRepo.get(User, user.id) | ||
assert user.status == :inactive | ||
|
||
TestRepo.insert!(%User{status: :archived}) | ||
user = TestRepo.get_by(User, status: :archived) | ||
assert user.status == :archived | ||
end | ||
|
||
test "casts int and binary to atom" do | ||
%{changes: changes} = cast(%User{}, %{"status" => "active"}, ~w(status), []) | ||
assert changes.status == :active | ||
|
||
%{changes: changes} = cast(%User{}, %{"status" => 3}, ~w(status), []) | ||
assert changes.status == :archived | ||
|
||
%{changes: changes} = cast(%User{}, %{"status" => :inactive}, ~w(status), []) | ||
assert changes.status == :inactive | ||
end | ||
|
||
test "raises when input is not in the enum map" do | ||
error = {:status, "is invalid"} | ||
|
||
changeset = cast(%User{}, %{"status" => "retroactive"}, ~w(status), []) | ||
assert error in changeset.errors | ||
|
||
changeset = cast(%User{}, %{"status" => :retroactive}, ~w(status), []) | ||
assert error in changeset.errors | ||
|
||
changeset = cast(%User{}, %{"status" => 4}, ~w(status), []) | ||
assert error in changeset.errors | ||
|
||
assert_raise Ecto.ChangeError, custom_error_msg("retroactive"), fn -> | ||
TestRepo.insert!(%User{status: "retroactive"}) | ||
end | ||
|
||
assert_raise Ecto.ChangeError, custom_error_msg(:retroactive), fn -> | ||
TestRepo.insert!(%User{status: :retroactive}) | ||
end | ||
|
||
assert_raise Ecto.ChangeError, custom_error_msg(5), fn -> | ||
TestRepo.insert!(%User{status: 5}) | ||
end | ||
end | ||
|
||
test "reflection" do | ||
assert StatusEnum.__enum_map__() == [registered: 0, active: 1, inactive: 2, archived: 3] | ||
assert StatusEnum.__valid_values__() == [0, 1, 2, 3, | ||
:registered, :active, :inactive, :archived, | ||
"active", "archived", "inactive", "registered"] | ||
end | ||
|
||
test "defenum/2 can accept variables" do | ||
x = 0 | ||
defenum TestEnum, zero: x | ||
end | ||
|
||
def custom_error_msg(value) do | ||
"`#{inspect value}` is not a valid enum value for `EctoEnumTest.StatusEnum`." <> | ||
" Valid enum values are `[0, 1, 2, 3, :registered, :active, :inactive, :archived," <> | ||
" \"active\", \"archived\", \"inactive\", \"registered\"]`" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
defmodule Ecto.Integration.Migration do | ||
use Ecto.Migration | ||
|
||
def change do | ||
create table(:users) do | ||
add :status, :integer | ||
end | ||
|
||
execute "CREATE TYPE status AS ENUM ('registered', 'active', 'inactive', 'archived')" | ||
create table(:users_pg) do | ||
add :status, :status | ||
end | ||
end | ||
end |
Oops, something went wrong.