Use cases
Sometimes, especially in non-CRUD domains the user actions cannot be directly mapped into the database entities along with their changesets. But still, from a frontend perspective they should be perceived as a whole. One typical example of such action, can be a registration process. It can be represented like this:
defmodule RegisterUser do
use StructCop
@email_regex ~r/./
contract do
field :id, :binary_id
field :email, :string
field :password, :string
field :first_name, :string
field :last_name, :string
end
@impl StructCop
def validate(changeset) do
import Ecto.Changeset
changeset
|> validate_required([:id, :email, :password, :first_name, :last_name])
|> validate_format(:email, @email_regex)
|> validate_length(:password, min: 8, max: 72)
end
def call(attrs) do
attrs
|> new!()
|> do_call()
end
defp do_call(%__MODULE__{} = cmd) do
# do stuff with coerced struct
end
end
Having this, allow us to keep a clear boundary between implementation details and things which depend on this module. Interface is overt and informative, a reader can check out contract definition for allowed fields and validations or just get an error message provided by new!/1
function.
Moreover, this aproach promotes fail fast philisophy. Fields in contracts are validated and in contrast to ordinary structs coerced into proper types. Code which uses structs constructed with StructCop, can safely assume that certain fields will have certain shape.
No more checking if a binary is really a binary, manually converting "4"
into an integer or what even worse, casting "2019-12-09T20:57:40.429625+00:00"
into a %DateTime{}
!