Atomic Habits: a personal summary
Here is my summary of Atomic Habits based on each chapter, and I hope this information can give you an idea of what the book is about, so you can read it yourself and get all of the benefits.
Sometimes we want to store some type of data in the project's database that currently is not available as a valid internal data type to be persisted.
For example, IP addresses, that are structured as tuples and Postgres does not support them. Take as an example the functions from :inet
module, that comes with Elixir through Erlang:
:inet.getifaddrs()
:inet.parse_address('127.0.0.1')
:inet.ntoa({127, 0, 0, 1})
In all of them, the IP address data is returned as ip_address
type, that it is an union type of ip4_address
or ip6_address
.
# ip4_address
{0..255, 0..255, 0..255, 0..255}
# ip6_address
{0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535}
Another example is how %Plug.Conn{}
holds IP data:
# Other Plug.Conn fields omitted for clarity.
%Plug.Conn{
...
remote_ip: {127, 0, 0, 1},
...
}
In the most of cases, storing IP addresses as strings in the database is enough, as we usually display as strings as well, but we need to have a way to translate back and forth tuple to string every time.
One of the solutions is rely on the functions in the module :inet
and create some kind of helper function to do the translation from tuple to string every time we want to create or update an IP field. This can become painful in the long-term and error prone.
A better solution is leverage Ecto.Type
behaviour that allows us to add custom types in our application and to define how the custom type will interact with the database (for reads and writes).
Below is the module that implements an Ecto.Type
for IP addresses, the comments in the @doc
explain each callback:
defmodule MyApp.Ecto.IP do
@moduledoc """
Implements Ecto.Type behavior for storing IP (either v4 or v6) data that originally comes as tuples.
"""
@behaviour Ecto.Type
@doc """
Defines what internal database type is used.
"""
def type, do: :string
@doc """
As we don't have any special casting rules, simply pass the value.
"""
def cast(value), do: {:ok, value}
@doc """
Loads the IP as string from the database and coverts to a tuple.
"""
def load(value) do
value
|> to_charlist()
|> :inet.parse_address()
end
@doc """
Receives IP as a tuple and converts to string. In case IP is not a tuple returns an error.
"""
def dump(value) when is_tuple(value) do
ip =
value
|> :inet.ntoa()
|> to_string()
{:ok, ip}
end
def dump(_), do: :error
end
In the schema you want to use the custom IP type you simply use the module that implements it as field type:
defmodule MyApp.Request do
use Ecto.Schema
schema "requests" do
field(:remote_ip, MyApp.IP)
# other fields omitted for clarity.
end
# remaining code omitted for clarity.
end
I hope this post can demonstrate one the nice things about Ecto that is extensibility. It helps us to represent well the application specifics without being too dependent of the database current features or driving us to creative workarounds.