Skip to content

Instantly share code, notes, and snippets.

@imranismail
Last active December 27, 2016 09:10
Show Gist options
  • Select an option

  • Save imranismail/1d854716b66502086e300f8a71f6aace to your computer and use it in GitHub Desktop.

Select an option

Save imranismail/1d854716b66502086e300f8a71f6aace to your computer and use it in GitHub Desktop.
Enum for Ecto
defmodule Enumecto do
defmacro defenum(module, values) do
values = atomify(values)
quote bind_quoted: binding() do
[_|type_parts] =
__MODULE__
|> Module.concat(module)
|> Module.split()
type =
type_parts
|> Enum.map_join("_", &Macro.underscore/1)
|> String.to_atom()
defmodule Module.concat(__MODULE__, module) do
@behaviour Ecto.Type
@values values
@string_lookup for value <- @values, into: %{}, do: {Atom.to_string(value), value}
@atom_lookup for value <- @values, into: %{}, do: {value, Atom.to_string(value)}
@valid_values @values ++ Map.keys(@string_lookup)
def type, do: unquote(type)
def cast(term) when is_binary(term), do: Map.fetch(@string_lookup, term)
def cast(term) when term in @valid_values, do: {:ok, term}
def cast(_), do: :error
def load(term) when is_binary(term), do: Map.fetch(@string_lookup, term)
def dump(term) when is_atom(term), do: Map.fetch(@atom_lookup, term)
def dump(term) when term in @valid_values, do: {:ok, term}
def dump(_), do: :error
def values, do: @values
def valid_values, do: @valid_values
def create do
Ecto.Migration.execute("CREATE TYPE #{type()} AS ENUM (#{escaped_values()})")
end
def drop do
Ecto.Migration.execute("DROP TYPE #{type()}")
end
def alter do
Enum.each(@values, fn value ->
Ecto.Migration.execute("ALTER TYPE #{type()} ADD VALUE IF NOT EXISTS '#{value}'")
end)
end
defp escaped_values do
Enum.map_join(@values, ", ", &"'#{&1}'")
end
end
end
end
defp atomify(values) do
if Enum.all?(values, &is_atom/1) do
values
else
Enum.map(values, &String.to_atom/1)
end
end
end
defmodule Data.Migrations.CreatePostTable do
use Ecto.Migration
def up do
Data.Post.Status.create
create table(:posts) do
add :title, :string
add :body, :string
add :status, Data.Post.Status.type
timestamps()
end
end
def down do
drop table(:posts)
Data.Post.Status.drop
end
end
defmodule Data.Post do
use Ecto.Schema
import Enumecto
defenum Status, [
:draft,
:unpublished,
:published
]
schema "posts" do
field :status, Post.Status
field :title, :string
field :body, :string
timestamps()
end
@allowed [
:status,
:title,
:body
]
def changeset(schema, params \\ %{}) do
schema
|> cast(params, @allowed)
end
defdelegate statuses, to: Post.Status, as: :values
def by_status(queryable, status) do
from(queryable, where: ^has_status(status))
end
defp has_status(status) do
if is_list(status) do
dynamic([q], q.status in ^status)
else
dynamic([q], q.status == ^status)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment