View Source TypedEnum

A simple library to ease using enum-like fields in Ecto.Schemas.

Installation

Add typed_enum to your list of dependencies in mix.exs:

def deps do
  [
    {:typed_enum, "~> 0.1"}
  ]
end

Docs: https://hexdocs.pm/typed_enum.

Installation  | Why  | Usage  | About  | Copyright

Why ?

Although Ecto ships (since 3.5) with an Enum type that allows you to easily limit the possible values a field can have while providing casting between "strings" to their :atoms representations there are still some issues that you might run into:

  • If you need to share the type between schemas then you need to duplicate or keep the the declarations in multiple schemas somehow in synch
  • You can't define enums with integers as their database layer representation
  • It doesn't create a proper @type for use in specs
  • The field "type" is declared in the schema itself, which in practical terms isn't a problem, but conceptually the type of a field should be independent from the schema that uses it
  • You can't cast individual parameters outside of schema changeset.

Usage

After adding typed_enum as a dependency, declare a module for your type using the TypedEnum use macro for defining your enum:

defmodule ExampleTypeString do
  use TypedEnum, values: [:val_1, :val_2]
end

This defines an Ecto.Type ExampleTypeString that can assume two values, :val_1 or :val_2 and is stored in the database as a string. You can cast values independently with ExampleTypeString.cast("val_1") and include it in schemas as:

defmodule SomeSchema do
  use Ecto.Schema

  schema("some_table_schema") do
    belongs_to(:user, User)

    field(:some_field, ExampleTypeString, default: :val_2)
  end
end

This means you can share it between schemas and not worry about keeping it in synch. If you want to use integers as its underlying representation then declare it as:

defmodule ExampleTypeInteger do
  use TypedEnum, values: [val_1: 1, val_2: 2]
end

Both versions allow and accept values in atom and String form. The integer version also allows as integers obviously.

Ecto equality testing is true between the different formats.

If you need to define special clauses to handle specific values you can define cast/1, dump/1 and defp get_term/1.

defmodule ExampleCallerModule do
  use TypedEnum, values: [val_1: 1, val_2: 2]

  def cast("some_prop"), do: {:ok, :val_1}
  def dump("some_prop"), do: {:ok, 1}
  defp get_term("some_prop"), do: :val_1
end

This would allow you to convert params/values in certain formats even if you don't want to allow storing or treat them as valid internally, which can be useful to deal with legacy api/endpoints/databases/sources while allowing you to use normalised values throughout your application code.

About

Cocktail Logo

© rooster image in the cocktail logo

Copyright [2021-] [Micael Nussbaumer]

Permission is hereby granted, free of charge, to any person obtaining a copy of this 
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, 
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to the following 
conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.