# EnumType

Generates Enumerated type modules that can be used as values and matched in code. Creates proper types so Dialyzer will be able to check bad calls.

## Why?

Something we have often wanted in Elixir was an enumerated type. The benefits we wanted are:

 - compile time type checking
 - easy pattern matching
 - not Ecto specific, but has Ecto support

## Installation

The package can be installed
by adding `enum_type` to your list of dependencies in `mix.exs`:

def deps do
    {:enum_type, "~> 1.1.0"}

## Changelog

 - `v1.1.2` Replace `@behaviour Ecto.Type` with `use Ecto.Type`
 - `v1.1.1` Fix warning for Elixir 1.10
 - `v1.1.0` Adds types to the enum. Dialyzer might start complaining.

## Creating and using an Enum

An enum type is created as its own module with each value of an an enum being a child module. The enum reference can then be used
as you would use any module name in Elixir. Since actual modules are created, this also means the module names are valid references
that can be called. Enum types can be defined anywhere `defmodule` can be used.

defmodule MyApp do
  use EnumType

  defenum Color do
    value Red, "red"
    value Blue, "blue"
    value Green, "green"

    default Blue

  @spec do_something(color :: Color.t) :: String.t
  def do_something(Color.Red), do: "got red"
  def do_something(Color.Blue), do: "got blue"
  def do_something(Color.Green), do: "got green"

MyApp.Color.Blue == MyApp.Color.default
"green" == MyApp.Color.Green.value
"got red" == MyApp.do_something(MyApp.Color.Red)

## Enum Type Functions

* `values`              - List of all enum values in the order they are defined. `["red", "blue"]`
* `enums`               - List of all enum value modules that are defined in the order defined. `[MyApp.Color.Red, MyApp.Color.Blue]`
* `options`             - List of tuples with the module name and the value. `[{MyApp.Color.Red, "red}, {MyApp.Color.Blue, "blue"}]`
* `from`                - Converts a value to an option module name. `MyApp.Color.Red == MyApp.Color.from("red")`
* `value`               - Converts a option module into the value. `"red" == MyApp.Color.value(MyApp.Color.Red)`

## Enum Option Functions

* `value`               - The value of the enum option. `MyApp.Color.Red.value`

## Custom Functions

Since both the enum type and options are both modules, any custom code that can be added to a module can also be added to these code blocks.

import EnumType

defenum Color do
  value Red, "red" do
    def statement, do: "I'm red"

  value Blue, "blue" do
    def statement, do: "I'm blue"

  value Green, "green" do
    def statement, do: "I'm green"

  default Blue

  @spec do_something(color :: Color.t)
  def do_something(Color.Red), do: "got red"
  def do_something(Color.Blue), do: "got blue"
  def do_something(Color.Green), do: "got green"

  def statement(color), do: "I'm #{color.value}"

"got blue" == Color.do_something(Color.Blue)
"I'm green" == Color.Green.statement
"I'm red" == Color.statement(Color.Red)

## Ecto Type Support

If `Ecto` is included in your project, additional helpers functions will be compiled in that implement the `Ecto.Type` behaviour callbacks.
When using Ecto, a type must be specified for the enum values that is supported by Ecto. All values provided by the Enum Type must be the same
Ecto basic type defined. By default, the type is `:string`.

defmodule Subscriber do
  use Ecto.Schema
  use EnumType

  import Ecto.Changeset

  # For database field defined as a string.
  defenum Level do
    value Basic, "basic"
    value Premium, "premium"

    default Basic

  # For database field defined as an integer.
  defenum AgeGroup, :integer do
    value Minor, 0
    value Adult, 1
    value NotSpecified, 2

    default NotSpecified

  schema "subscribers" do
    field :name,            :string
    field :level,           Level,        default: Level.default
    field :age_group,       AgeGroup,     default: AgeGroup.default

  changeset(schema, params) do
    |> cast(params, [:name, :level, :age_group])
    |> Level.validate(:level, message: "Invalid subscriber type")
    |> AgeGroup.validate(:age_group)

When working with an enum type in an Ecto schema, always use the module name of the value you wish to use. The name can
be included in any query or changeset params.

## Using with Absinthe

Absinthe provides a means to define enums and map to other values. When using Absinthe with an Ecto schema that also uses `EnumType`,
you will need to map to the enum option module name and not the underlying value that will be stored in the database.

enum :subscriber_level do
  value :basic, as: Subscriber.Level.Basic
  value :premium, as: Subscriber.Level.Premium

object :subscriber do
  field :level, :subscriber_level

Absinthe will produce an upper case value based upon its own enum through the graphql interface. "BASIC" or "PREMIUM".

Outbound or inbound will be mapped correctly to and from the EnumType module name value.

## Dialyzer

Enum types and values are modules under the hood. Since module names are just atoms in Elixir, we don't get compile-time type checks. However, Dialyzer will be able to spot type errors. For example, code like the following:

defenum MyEnum do
    value One, "one"
    value Two, "two"
    value Three, "three"

@spec foo(thing :: MyEnum.t()) :: atom()
def foo(MyEnum.One), do: :one_here
def foo(MyEnum.Two), do: :two_here
def foo(MyEnum.Three), do: :three_here

def bad_func() do

Dialyzer will complain that `bad_func()` doesn't return because `foo(MyEnum.NotASubtype)` breaks the contract.