WedX - журнал о программировании и компьютерных науках

Эликсир: Десятичное приведение и не допускаются отрицательные числа

Привет, я получаю значение, и это значение не может быть отрицательным, только положительным, я хотел бы вернуть пользовательскую ошибку, если оно отрицательное, и перейти к каналу, если оно отрицательное.

у меня это на данный момент:

 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!"}
26.02.2021

Ответы:


1

Как я упоминал в своем последнем комментарии, 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)
...

Таким образом, если это отрицательное число, оно проходит проверку {:error, :negative_number} из handle_cast, а если оно положительное или cast по какой-то причине не удалось, оно проходит другие проверки handle_cast, которые у вас уже есть.

26.02.2021
  • Я пробовал это таким образом, но отрицательные числа все еще принимаются, и мне также нужно привести к десятичному виду, прежде чем проверять, отрицательно ли оно. 26.02.2021
  • я редактирую вопрос с полным кодом. 26.02.2021
  • если я использую значение в виде строки, оно остается отрицательным, но если я использую число, оно работает. 26.02.2021
  • Да, если это строка, это не сработает, потому что строки всегда больше, чем числа. "2" < 3 не подведет, а вернет false. Я только что обновил ответ еще парой вариантов 26.02.2021
  • Думаю, я понимаю, если мне нужно проверить свой handle_cast, если число отрицательное. Если я это сделаю, мне не понадобятся эти две операции? Операция defp (_account, value, _operation) при значении ‹0 do {: ошибка, число должно быть положительным} end Операция defp (_account, - ‹› _value, _operation) do {: ошибка, число должно быть положительным} end 26.02.2021
  • Не могли бы вы показать, как будет выглядеть наилучшее окончательное решение? 26.02.2021
  • Да, точно. Если вы просто добавите один в handle_cast, вам не нужно будет изменять operation, так что это самое простое и наиболее правильное решение IMO. 26.02.2021
  • Я пробовал как с операциями, так и с функциями, но с этой ручкой handle_cast / 4 он по-прежнему допускает отрицательные числа. 26.02.2021
  • Я не знаю почему, но здесь я все еще принимаю отрицательные числа и строку с - : defp handle_cast({:ok, value}, _balance, _operation) когда значение ‹ 0 do {:error, число должно быть положительным} end 26.02.2021
  • Да, прости. Я обновляю ответ. Decimal — это другой тип, и, как и строки, он всегда будет больше, чем просто целое число. 26.02.2021
  • Взгляните сейчас. Это должно сделать это 26.02.2021
  • Большое спасибо, я начал сейчас с эликсира, и с вашим объяснением мне показалось понятнее, как решать проблемы таким образом, спасибо. 26.02.2021
  • @sbacarob, возможно, вы захотите проверить мой ответ, чтобы найти еще один удобный способ справиться с этим :) 26.02.2021

  • 2

    Хотя ответ @sbacarob совершенно правильный, я бы опубликовал его для удобства.

    Decimal — это простая структура, поэтому можно использовать его внутренности для обработки негативов с охраной. Это будет работать с современными версиями 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"}
    
    26.02.2021
  • Это довольно удивительно! Я не знал об этом, но определенно позволяет писать более чистый код без лишней суеты. 26.02.2021
  • Что ж, я предоставил PR для Decimal для изначальной поддержки охранников. Посмотрим. 26.02.2021
  • @Алексей Матюшкин, спасибо, отличное решение, братан. 26.02.2021

  • 3

    Ta fazendo или NLW de ELixir tambem! Eu fiz assim e funcionou:

    Репозиторий: 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
    
    27.02.2021
    Новые материалы

    Как создать диаграмму градиентной кисти с помощью D3.js
    Резюме: Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с..

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..


    Для любых предложений по сайту: [email protected]