Last active
June 17, 2019 03:47
-
-
Save esdras/9059de7c285f401f0385b5746742092c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyApp.Users.Input do | |
@moduledoc false | |
@type params :: map() | Keyword.t() | |
alias MyApp.Utils | |
alias MyApp.Users.{User} | |
defmodule Create do | |
@moduledoc """ | |
Input for `MyApp.Users.create/1` | |
""" | |
use MyApp.Schema | |
alias MyApp.Users.Input | |
embedded_schema do | |
field(:name, :string) | |
field(:email, :string) | |
field(:password, :string) | |
end | |
@doc """ | |
Validate params and create a new input object | |
## Examples | |
iex> empty_params = [] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(empty_params) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[name: "can't be blank", email: "can't be blank", password: "can't be blank"] | |
iex> invalid_email_params = [name: "Foo Bar", email: "invalid123.com", password: "123abc"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(invalid_email_params) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[email: "has invalid format"] | |
iex> password_with_no_digit = [name: "Foo Bar", email: "foo@bar.com", password: "abcdefg"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(password_with_no_digit) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[password: "should have at least one digit"] | |
iex> password_with_no_letters = [name: "Foo Bar", email: "foo@bar.com", password: "1234567"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(password_with_no_letters) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[password: "should have at least one non digit character"] | |
iex> valid_params = [name: "Foo Bar", email: "foo@bar.com", password: "123abc"] | |
...> MyApp.Users.Input.Create.validate(valid_params) | |
{:ok, %MyApp.Users.Input.Create{ | |
name: "Foo Bar", | |
email: "foo@bar.com", | |
password: "123abc" | |
}} | |
""" | |
@spec validate(Input.params()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} | |
def validate(params) do | |
params = normalize_params(params) | |
cast(%__MODULE__{}, params, [:name, :email, :password, :invitation_token]) | |
|> validate_required([:name, :email, :password]) | |
|> validate_password() | |
|> validate_format(:email, Utils.email_regex()) | |
|> case do | |
%Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)} | |
cs -> {:error, cs} | |
end | |
end | |
@spec changeset(map() | Keyword.t()) :: Ecto.Changeset.t() | |
def changeset(params) do | |
params = normalize_params(params) | |
cast(%__MODULE__{}, params, [:name, :email, :password, :password_confirmation]) | |
end | |
def validate_password(cs) do | |
cs | |
|> validate_format(:password, ~r/\d+/, message: "should have at least one digit") | |
|> validate_format(:password, ~r/\D+/, message: "should have at least one non digit character") | |
|> validate_length(:password, min: 6) | |
|> validate_confirmation(:password) | |
end | |
end | |
defmodule Update do | |
@moduledoc """ | |
Input for `MyApp.Users.update/2` | |
""" | |
use MyApp.Schema | |
alias MyApp.Users.Input | |
embedded_schema do | |
field(:name, :string) | |
end | |
@doc """ | |
Validate params and create a new input object for | |
`MyApp.Users.update/2` | |
## Examples | |
iex> empty_params = [] | |
...> alias MyApp.Users.{Input, User} | |
...> {:error, cs} = Input.Update.validate(%User{}, empty_params) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[name: "can't be blank"] | |
iex> valid_params = [name: "Foo Bar"] | |
...> alias MyApp.Users.{Input, User} | |
...> Input.Update.validate(%User{}, valid_params) | |
{:ok, %MyApp.Users.Input.Update{ | |
name: "Foo Bar" | |
}} | |
""" | |
@spec validate(%User{}, Input.params()) :: | |
{:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} | |
def validate(record, params) do | |
existing_values = | |
record | |
|> Map.from_struct() | |
|> Map.take(__MODULE__.__schema__(:fields)) | |
|> normalize_params | |
params = | |
existing_values | |
|> Map.merge(normalize_params(params)) | |
cast(%__MODULE__{}, params, [:name]) | |
|> validate_required([:name]) | |
|> case do | |
%Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)} | |
cs -> {:error, cs} | |
end | |
end | |
end | |
defmodule ResetPassword do | |
@moduledoc """ | |
Input for `MyApp.Users.reset_password/3` | |
""" | |
use MyApp.Schema | |
alias MyApp.Users.Input | |
alias MyApp.Users.Input.Commons | |
embedded_schema do | |
field(:password, :string) | |
field(:password_confirmation, :string) | |
end | |
@doc """ | |
Validate params and create a new input object | |
## Examples | |
iex> empty_params = [] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(empty_params) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[name: "can't be blank", email: "can't be blank", password: "can't be blank"] | |
iex> invalid_email_params = [name: "Foo Bar", email: "invalid123.com", password: "123abc"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(invalid_email_params) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[email: "has invalid format"] | |
iex> password_with_no_digit = [name: "Foo Bar", email: "foo@bar.com", password: "abcdefg"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(password_with_no_digit) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[password: "should have at least one digit"] | |
iex> password_with_no_letters = [name: "Foo Bar", email: "foo@bar.com", password: "1234567"] | |
...> {:error, cs} = MyApp.Users.Input.Create.validate(password_with_no_letters) | |
...> Enum.map(cs.errors, fn {k, {msg, _}} -> {k, msg} end) | |
[password: "should have at least one non digit character"] | |
iex> valid_params = [name: "Foo Bar", email: "foo@bar.com", password: "123abc"] | |
...> MyApp.Users.Input.Create.validate(valid_params) | |
{:ok, %MyApp.Users.Input.Create{ | |
name: "Foo Bar", | |
email: "foo@bar.com", | |
password: "123abc" | |
}} | |
""" | |
@spec validate(Input.params()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} | |
def validate(params) do | |
params = normalize_params(params) | |
cast(%__MODULE__{}, params, [:password, :password_confirmation]) | |
|> validate_required([:password, :password_confirmation]) | |
|> Commons.validate_password() | |
|> case do | |
%Changeset{valid?: true} = cs -> {:ok, apply_changes(cs)} | |
cs -> {:error, cs} | |
end | |
end | |
@spec changeset(Input.params()) :: Ecto.Changeset.t() | |
def changeset(params) do | |
params = normalize_params(params) | |
cast(%__MODULE__{}, params, [:password, :password_confirmation]) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# User Schema, this maps to a table in the database. | |
defmodule MyApp.Users.User do | |
use MyApp.Schema | |
schema "users" do | |
field(:name, :string) | |
field(:email, :string) | |
end | |
def changeset(params) do | |
changeset(%__MODULE__{}, params) | |
end | |
def changeset(struct, params) do | |
struct | |
|> change(params) | |
|> validate_required([:name, :email]) | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MyApp.Users do | |
@doc """ | |
Creates a user | |
Check `MyApp.Users.Input.Create` for the expected params | |
This function will: | |
1. Create a user | |
4. Return `{:ok, %User{}}` if the provided arguments are valid and everything went well | |
5. Return `{:error, Ecto.Changeset{}}` if the provided arguments are invalid | |
6. Raise an exception if an error occurs after the validation step | |
## Examples | |
invalid = %{name: "Foo", email: "foo@bar.com", password: nil} | |
Users.create(session, vendor, invalid) | |
{:error, %Ecto.Changeset{}} | |
params = %{name: "Foo", email: "foo@bar.com", password: "password42", password_confirmation: "password42"} | |
Users.create(params) | |
{:ok, %User{}} | |
""" | |
@spec create( | |
map() | Keyword.t | |
) :: {:ok, %User{}} | {:error, Ecto.Changeset.t()} | no_return() | |
def create(params) do | |
with {:validate_params, {:ok, input}} <- {:validate_params, Input.Create.validate(params)}, | |
{:validate_schema, %{valid?: true} = cs} <- | |
{:validate_schema, User.changeset(Map.from_struct(input))}, | |
{:insert, {:ok, user}} <- | |
{:insert, Repo.insert(cs) do | |
{:ok, user} | |
else | |
{:validate_params, {:error, cs}} -> | |
{:error, cs} | |
error -> | |
error | |
end | |
|> case do | |
{:ok, result} -> {:ok, result} | |
{:error, cs} -> {:error, cs} | |
error -> raise(inspect(error)) | |
end | |
end | |
@doc """ | |
Updates a user | |
Check `MyApp.Users.Input.Update` for the expected params | |
This function should not be used to update the user's email. | |
This function will: | |
1. Update a user | |
2. Return `{:ok, %User{}}` if the provided arguments are valid and everything went well | |
3. Return `{:error, Ecto.Changeset{}}` if the provided arguments are invalid | |
4. Raise an exception if an error occurs after the validation step | |
## Examples | |
Users.update(name: nil) | |
{:error, %Ecto.Changeset{}} | |
Users.update(name: "Foo Bar") | |
{:ok, %User{}} | |
""" | |
@type update_option :: {:tx_opts, Datomish.options()} | |
@spec update( | |
entity() | id_param(), | |
map() | Keyword.t | |
) :: | |
{:ok, %User{}} | |
| {:error, :not_found | Ecto.Changeset.t()} | |
| no_return() | |
def update(user_or_id, params) do | |
with {:get_record, %User{} = member} <- {:get_record, get(member_or_id)}, | |
{:validate_params, {:ok, input}} <- | |
{:validate_params, Input.Update.validate(member, params)}, | |
{:prepare_schema_input, input} <- {:prepare_schema_input, Map.from_struct(input)}, | |
{:validate_schema, %{valid?: true} = cs} <- | |
{:validate_schema, User.changeset(member, input)}, | |
{:update, {:ok, record}} <- {:update, Repo.update(cs)} do | |
{:ok, record} | |
else | |
{:validate_params, {:error, cs}} -> | |
{:error, cs} | |
{:get_record, nil} -> | |
{:error, :not_found} | |
error -> | |
error | |
end | |
|> case do | |
{:ok, result} -> {:ok, result} | |
{:error, error_or_cs} -> {:error, error_or_cs} | |
error -> raise(inspect(error)) | |
end | |
end | |
end |
De onde vem a funcão normalize_params
?
Esqueci de excluir esta função, ela é um helper que torna todas as keys de um Map em string. Pra casos onde o Map venha com algumas keys de tipos diferentes (Atom e String) por exemplo. Não é importante pro exemplo, mas aqui está ela:
def normalize_params(params, opts \\ []) do
as = Keyword.get(opts, :as, :map)
params =
params
|> struct_to_enum
|> Enum.into(%{})
|> stringify_keys
case as do
:map -> params
:list -> params |> Enum.into([])
end
end
def atomize_keys(map) when is_map(map) do
Enum.reduce(map, %{}, fn {k, v}, acc -> Map.put(acc, to_atom(k), v) end)
end
def stringify_keys(map) when is_map(map) do
Enum.reduce(map, %{}, fn {k, v}, acc -> Map.put(acc, to_string(k), v) end)
end
def struct_to_enum(%{__struct__: _} = struct) do
if Enumerable.impl_for(struct) do
else
Map.from_struct(struct)
end
end
def struct_to_enum(other), do: other
def to_atom(v) when is_binary(v), do: String.to_atom(v)
def to_atom(v) when is_atom(v), do: v
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Este Gist está incompleto, é só um exemplo de como eu valido cada action...