Elixir:小数转换并且不允许负数
Elixir: Decimal cast and dont allow negative numbers
你好,我收到一个值,这个值不能为负,只能为正,我想 return 如果它是负的则自定义错误,如果它是负的则继续处理管道。
我现在有这个:
def call(%{"id" => id, "value" => value}, operation) do
Multi.new()
|> Multi.run(:account, fn repo, _changes -> get_account(repo, id) end)
|> Multi.run(:update_balance, fn repo, %{account: account} ->
update_balance(repo, account, value, operation)
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!"}
正如我在上一条评论中提到的,Decimal
是一种不同的“类型”,它大于整数。
幸运的是,Decimal
提供了 gt?
、lt?
和 compare
函数,您可以使用这些函数将 Decimal
与整数进行比较。一个稍微干净的解决方案,尽可能少地改变你的代码,是在 handle_cast
之前添加一个小的额外函数和 handle_cast
的另一个定义。像这样:
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> maybe_positive?()
|> handle_cast(balance, operation)
end
defp maybe_positive?({:ok, value}) do
case Decimal.compare?(value, 0) do
:lt -> {:error, :negative_number}
:gt -> {:ok, value}
end
end
defp maybe_positive?(error), do: error
defp handle_cast({:error, :negative_number}, _balance, _operation) do
{:error, "Number must be positive"}
end
defp handle_cast({:ok, value}, balance, :deposit), do: Decimal.add(balance, value)
...
这样,如果它是负数,它将通过 handle_cast
的 {:error, :negative_number}
案例,如果它是正数或 cast
由于某种原因失败,它将通过您已经拥有的其他 handle_cast
个验证
虽然@sbacarob 的回答完全正确,但为了方便起见,我会post这样做。
Decimal
是一个 plain struct,因此可以使用它的内部结构来处理守卫的底片。这将适用于 Elixir ≥ 1.11 和 OTP ≥ 23 的现代版本。
defguard is_decimal_positive(value)
when is_map(value) and
value.__struct__ == Decimal and
value.sign == 1
有关详细信息,请参阅 Kernel.defguard/1
。并将其用作
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> handle_cast(balance, operation)
end
defp handle_cast({:ok, value}, balance, _)
when is_decimal_positive(value),
do: Decimal.add(balance, value)
defp handle_cast(_error, _balance, _),
do: {:error, "Number must be positive decimal"}
他也在做 ELixir NLW!
我是这样做的并且有效:
存储库:https://github.com/librity/nlw_elixir
defmodule Rocketpay.Accounts.Operation do
alias Ecto.Multi
alias Rocketpay.Account
def call(%{"id" => id, "value" => value}, operation) do
fetch_account_operation_name = build_fetch_account_operation_name(operation)
Multi.new()
|> Multi.run(
fetch_account_operation_name,
fn repo, _previous -> fetch_account(repo, id) end
)
|> Multi.run(operation, fn repo, fetch_result ->
account = Map.get(fetch_result, fetch_account_operation_name)
update_balance(repo, account, value, operation)
end)
end
defp fetch_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
|> calculate_new_balance(value, operation)
|> update_account(account, repo)
end
defp calculate_new_balance(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> validate_greater_than_zero()
|> handle_cast(balance, operation)
end
defp validate_greater_than_zero({:ok, casted_value}) do
case Decimal.compare(casted_value, 0) do
:lt -> :error
:eq -> :error
:gt -> {:ok, casted_value}
end
end
defp validate_greater_than_zero(:error), do: :error
defp handle_cast({:ok, valid_value}, balance, :deposit), do: Decimal.add(balance, valid_value)
defp handle_cast({:ok, valid_value}, balance, :withdraw), do: Decimal.sub(balance, valid_value)
defp handle_cast(:error, _balance, :deposit), do: {:error, "Invalid deposit value."}
defp handle_cast(:error, _balance, :withdraw), do: {:error, "Invalid withdraw value."}
defp update_account({:error, _reason} = error, _account, _repo), do: error
defp update_account(new_balance, account, repo) do
account
|> Account.changeset(%{balance: new_balance})
|> repo.update()
end
defp build_fetch_account_operation_name(operation),
do: "fetch_#{operation}_account" |> String.to_atom()
end
你好,我收到一个值,这个值不能为负,只能为正,我想 return 如果它是负的则自定义错误,如果它是负的则继续处理管道。
我现在有这个:
def call(%{"id" => id, "value" => value}, operation) do
Multi.new()
|> Multi.run(:account, fn repo, _changes -> get_account(repo, id) end)
|> Multi.run(:update_balance, fn repo, %{account: account} ->
update_balance(repo, account, value, operation)
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!"}
正如我在上一条评论中提到的,Decimal
是一种不同的“类型”,它大于整数。
幸运的是,Decimal
提供了 gt?
、lt?
和 compare
函数,您可以使用这些函数将 Decimal
与整数进行比较。一个稍微干净的解决方案,尽可能少地改变你的代码,是在 handle_cast
之前添加一个小的额外函数和 handle_cast
的另一个定义。像这样:
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> maybe_positive?()
|> handle_cast(balance, operation)
end
defp maybe_positive?({:ok, value}) do
case Decimal.compare?(value, 0) do
:lt -> {:error, :negative_number}
:gt -> {:ok, value}
end
end
defp maybe_positive?(error), do: error
defp handle_cast({:error, :negative_number}, _balance, _operation) do
{:error, "Number must be positive"}
end
defp handle_cast({:ok, value}, balance, :deposit), do: Decimal.add(balance, value)
...
这样,如果它是负数,它将通过 handle_cast
的 {:error, :negative_number}
案例,如果它是正数或 cast
由于某种原因失败,它将通过您已经拥有的其他 handle_cast
个验证
虽然@sbacarob 的回答完全正确,但为了方便起见,我会post这样做。
Decimal
是一个 plain struct,因此可以使用它的内部结构来处理守卫的底片。这将适用于 Elixir ≥ 1.11 和 OTP ≥ 23 的现代版本。
defguard is_decimal_positive(value)
when is_map(value) and
value.__struct__ == Decimal and
value.sign == 1
有关详细信息,请参阅 Kernel.defguard/1
。并将其用作
defp operation(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> handle_cast(balance, operation)
end
defp handle_cast({:ok, value}, balance, _)
when is_decimal_positive(value),
do: Decimal.add(balance, value)
defp handle_cast(_error, _balance, _),
do: {:error, "Number must be positive decimal"}
他也在做 ELixir NLW! 我是这样做的并且有效:
存储库:https://github.com/librity/nlw_elixir
defmodule Rocketpay.Accounts.Operation do
alias Ecto.Multi
alias Rocketpay.Account
def call(%{"id" => id, "value" => value}, operation) do
fetch_account_operation_name = build_fetch_account_operation_name(operation)
Multi.new()
|> Multi.run(
fetch_account_operation_name,
fn repo, _previous -> fetch_account(repo, id) end
)
|> Multi.run(operation, fn repo, fetch_result ->
account = Map.get(fetch_result, fetch_account_operation_name)
update_balance(repo, account, value, operation)
end)
end
defp fetch_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
|> calculate_new_balance(value, operation)
|> update_account(account, repo)
end
defp calculate_new_balance(%Account{balance: balance}, value, operation) do
value
|> Decimal.cast()
|> validate_greater_than_zero()
|> handle_cast(balance, operation)
end
defp validate_greater_than_zero({:ok, casted_value}) do
case Decimal.compare(casted_value, 0) do
:lt -> :error
:eq -> :error
:gt -> {:ok, casted_value}
end
end
defp validate_greater_than_zero(:error), do: :error
defp handle_cast({:ok, valid_value}, balance, :deposit), do: Decimal.add(balance, valid_value)
defp handle_cast({:ok, valid_value}, balance, :withdraw), do: Decimal.sub(balance, valid_value)
defp handle_cast(:error, _balance, :deposit), do: {:error, "Invalid deposit value."}
defp handle_cast(:error, _balance, :withdraw), do: {:error, "Invalid withdraw value."}
defp update_account({:error, _reason} = error, _account, _repo), do: error
defp update_account(new_balance, account, repo) do
account
|> Account.changeset(%{balance: new_balance})
|> repo.update()
end
defp build_fetch_account_operation_name(operation),
do: "fetch_#{operation}_account" |> String.to_atom()
end