A simple library to ease using enum-like fields in Ecto.Schemas.
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.
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.
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.
© 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.