Skip to content

Instantly share code, notes, and snippets.

@romseguy
Created September 24, 2017 23:37
Show Gist options
  • Save romseguy/9eb9802f4f75f99fd4c39c16bd34c87b to your computer and use it in GitHub Desktop.
Save romseguy/9eb9802f4f75f99fd4c39c16bd34c87b to your computer and use it in GitHub Desktop.
register
# it also takes care of translating error messages with Gettext
defmodule ApiWeb.Middleware.ChangesetErrorFormatter do
import ApiWeb.ErrorHelpers
def call(%{errors: []} = res, _), do: res
def call(%{errors: errors} = res, _) do
formatted_errors = format_changeset_error(errors)
%{res | errors: formatted_errors}
end
def format_changeset_error(errors) when is_list(errors) do
cond do
Enum.all?(errors, &is_changeset/1) -> Enum.flat_map(errors, &format_changeset_error/1)
Enum.all?(errors, &is_bitstring/1) -> Enum.map(errors, &translate_error({&1, []}))
Enum.all?(errors, &is_map/1) -> errors
true -> Enum.flat_map(errors, &format_changeset_error/1)
end
end
def format_changeset_error(%Ecto.Changeset{} = changeset), do: format_changeset(changeset)
def format_changeset_error(error), do: error
defp format_changeset(str) when is_bitstring(str), do: str
defp format_changeset(changeset) do
changeset
|> interpolate_errors
|> Map.to_list
|> Enum.flat_map(fn {field, errors} -> field_errors_to_error(changeset, field, errors) end)
end
def field_errors_to_error(changeset, field, errors) do
field_name = Atom.to_string(field)
Enum.map(errors, fn error ->
value = error_field_value(changeset, field)
%{
field_name: field_name,
message: translate_error({error, value: value}),
value: value
}
end)
end
defp interpolate_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
end
@spec error_field_value(changeset :: Ecto.Changeset.t, field :: atom) :: any
defp error_field_value(changeset, field) do
case Ecto.Changeset.fetch_field(changeset, field) do
{_, value} -> value
:error -> nil
end
end
defp is_changeset(%Ecto.Changeset{} = _), do: true
defp is_changeset(_), do: false
end
# we use a middleware to handle the case where a changeset with errors is returned from the resolver
def middleware(middleware, _field, %Absinthe.Type.Object{identifier: :mutation}) do
middleware ++ [ApiWeb.Middleware.ChangesetErrorFormatter]
end
@desc "Register with email, username, and password"
field :register, type: :session do
arg :email, non_null(:string)
arg :username, non_null(:string)
arg :password, non_null(:string)
resolve &Resolvers.register/2
end
def register(attrs, _info) do
with {:ok, user} <- Api.Session.register(attrs),
{:ok, jwt, _ } <- Guardian.encode_and_sign(user, :access)
do
{:ok, %{token: jwt}}
else
{:error, changeset} -> {:error, changeset}
end
end
defmodule Api.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
import Helpers.Validation
@doc"""
ecto schema/struct for the users table
"""
schema "users" do
field :email, :string
field :password, :string, virtual: true
field :password_hash, :string
field :username, :string
timestamps()
end
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :username])
|> validate_required([:email])
end
def create_changeset(%User{} = user, attrs \\ %{}) do
user
|> update_changeset(attrs)
|> cast(attrs, [:password])
|> validate_required([:password])
|> validate_length(:password, min: 6, max: 100)
|> put_pass_hash
end
def update_changeset(%User{} = user, attrs \\ %{}) do
user
|> cast(attrs, [:email, :username])
|> validate_required([:email])
|> validate_length(:username, min: 1, max: 40)
|> validate_length(:email, min: 3)
|> unique_constraint(:username, message: "username has already been taken")
|> validate_email_format(:email)
|> unique_constraint(:email, message: "email has already been taken")
|> downcase_user_email
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment