Struct sem Ecto
defmodule Rocketpay.Accounts.Transactions.Response do
alias Rocketpay.Account
defstruct [:from_account, :to_account]
def build(%Account{} = from_account, %Account{} = to_account) do
%__MODULE__{
from_account: from_account,
to_account: to_account
}
end
end
Ecto Multi Faz mais de 2 Operações no Banco de dados em somente uma chamada
defmodule Rocketpay.Accounts.Operation do
alias Ecto.Multi
alias Rocketpay.Account
def call(%{"id" => id, "value" => value}, operation) do
operation_name = account_operation_name(operation)
Multi.new()
|> Multi.run(operation_name, fn repo, _changes -> get_account(repo, id) end)
|> Multi.run(operation, fn repo, changes ->
account = Map.get(changes, operation_name)
update_balance(repo, account, value, operation)
end)
end
defp get_account(repo, id) do
case repo.get(Account, id) do
nil -> {:error, "Account not found"}
account -> {:ok, account}
end
end
defp update_balance(repo, account, value, operation) do
account
|> operation(value, operation)
|> update_account(repo, account)
end
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> handle_cast(balance, operation)
end
defp handle_cast({:ok, value}, balance, :deposit), do: Decimal.add(balance, value)
defp handle_cast({:ok, value}, balance, :withdraw), do: Decimal.sub(balance, value)
defp handle_cast(:error, _balance, _operation), do: {:error, "Invalid operation value"}
defp update_account({:error, _reason} = error, _repo, _account), do: error
defp update_account(value, repo, account) do
params = %{balance: value}
account
|> Account.changeset(params)
|> repo.update()
end
defp account_operation_name(operation) do
"account_#{Atom.to_string(operation)}" |> String.to_atom()
end
end
Multi.merge
defmodule Rocketpay.Accounts.Transaction do
alias Ecto.Multi
alias Rocketpay.Accounts.Operation
alias Rocketpay.Accounts.Transactions.Response, as: TransactionResponse
alias Rocketpay.Repo
def call(%{"from" => from_id, "to" => to_id, "value" => value}) do
withdraw_params = build_params(from_id, value)
deposit_params = build_params(to_id, value)
Multi.new()
|> Multi.merge(fn _changes -> Operation.call(withdraw_params, :withdraw) end)
|> Multi.merge(fn _changes -> Operation.call(deposit_params, :deposit) end)
|> run_transaction
end
defp build_params(id, value), do: %{"id" => id, "value" => value}
defp run_transaction(multi) do
case Repo.transaction(multi) do
{:error, _operation, reason, _changes} ->
{:error, reason}
{:ok, %{deposit: to_account, withdraw: from_account}} ->
{:ok, TransactionResponse.build(from_account, to_account)}
end
end
end
Defdelegate + Padrão Facede (Fachada)
lib/rocketpay_web/rocketpay.ex
defmodule Rocketpay do
alias Rocketpay.Users.Create, as: UserCreate
alias Rocketpay.Accounts.{Deposit, Withdraw, Transaction}
defdelegate create_user(params), to: UserCreate, as: :call
defdelegate deposit(params), to: Deposit, as: :call
defdelegate withdraw(params), to: Withdraw, as: :call
defdelegate transaction(params), to: Transaction, as: :call
end
Exemplo de view
defmodule RocketpayWeb.UsersView do
alias Rocketpay.{Account, User}
def render("create.json", %{
user: %User{
account: %Account{id: account_id, balance: balance},
id: id,
name: name,
nickname: nickname
}
}) do
%{
message: "user Created",
user: %{
id: id,
name: name,
nickname: nickname,
account: %{
id: account_id,
balance: balance
}
}
}
end
end