首页 > 解决方案 > Elixir - 将复杂 if 语句的结果分配给变量

问题描述

如何使error变量始终等于字符串?它几乎在所有情况下都是 nil,并且永远不应该是 nil。在我有一个字符串的所有场景中,我想使error等于该字符串:

error = if user_product do
      if user_product.voted_not_vegan && report do
        "Whoops! You have already reported this product is not vegan"
      end

      if !user_product.voted_not_vegan && report do
        changeset =
        UserProduct.changeset(
          user_product,
          %{:voted_not_vegan => true}
        )
        case Api.Repo.update(changeset) do
          {:ok, product} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end

        "Success! Reported not vegan"
        changeset =
        Product.changeset(
          product,
          %{:not_vegan_count => not_vegan_count + 1}
        )

        case Api.Repo.update(changeset) do
          {:ok, product} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end
      end

      IO.inspect(user_product, label: "userproduct")
      IO.inspect(confirm, label: "confirm")

      if user_product.voted_vegan && confirm do
        "Whoops! You have already confirmed this product is vegan"
      end

      if !user_product.voted_vegan && confirm do
        changeset =
        Product.changeset(
          product,
          %{:vegan_count => vegan_count + 1}
        )


        case Api.Repo.update(changeset) do
          {:ok, product_shop} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end


        "Success! Confirmed is vegan"

        changeset =
        UserProduct.changeset(
          user_product,
          %{:voted_vegan => true}
        )

        case Api.Repo.update(changeset) do
          {:ok, product} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end
      end
    else
      IO.puts("insert user product")
      UserProduct.insert_user_product(conn, %{
        p_id: String.to_integer(pid),
        u_id: uid,
        voted_not_vegan: report,
        voted_vegan: confirm
      })

      user_product =
      from(up in UserProduct,
        where: up.u_id == ^uid,
        where: up.p_id == ^pid
      )
      |> Api.Repo.one()

      if report do
        changeset =
        Product.changeset(
          product,
          %{:not_vegan_count => not_vegan_count + 1}
        )
        case Api.Repo.update(changeset) do
          {:ok, product} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end

        changeset =
        UserProduct.changeset(
          user_product,
          %{:voted_not_vegan => true}
        )

        case Api.Repo.update(changeset) do
          {:ok, product} -> IO.puts("error")
          {:error, changeset} -> IO.puts("error")
        end

        "Success! Reported not vegan"

      end

以下是我对@Aleksei Matiushkin 的回答的实现。它似乎正在工作,尽管可能偏离了答案。

  @spec check_user_product(
          usr_prduct :: %UserProduct{},
          {report :: boolean(), confirm :: boolean()},
          product :: %Product{}
        ) :: any()
  defp check_user_product(user_product, report_confirm, product)

  @doc """
    User confirms product is vegan, when they have already done so.
  """
  defp check_user_product(
         %UserProduct{voted_vegan: true} = _usr_prduct,
         {_, true} = _report_confirm,
         _product
       ) do
    "Whoops! You have already confirmed this product is vegan"
  end

  @doc """
    User confirms product is vegan, when they have not already done so.
  """
  defp check_user_product(
         %UserProduct{voted_vegan: false} = usr_prduct,
         {_, true} = _report_confirm,
         product
       ) do
    changeset =
      Product.changeset(
        product,
        %{:vegan_count => product.vegan_count + 1}
      )

    case Api.Repo.update(changeset) do
      {:ok, _} -> IO.puts("filler")
      {:error, _} -> IO.puts("filler")
    end

    changeset =
      UserProduct.changeset(
        usr_prduct,
        %{:voted_vegan => true}
      )

    case Api.Repo.update(changeset) do
      {:ok, _} -> IO.puts("filler")
      {:error, _} -> IO.puts("filler")
    end

    "Success! Confirmed is vegan"
  end

  @doc """
    User reports product is not vegan, when they have already done so.
  """
  defp check_user_product(
         %UserProduct{voted_not_vegan: true} = _usr_prduct,
         {true, _} = _report_confirm,
         _product
       ),
       do: "Whoops! You have already reported this product is not vegan"

  @doc """
    User reports product is not vegan, when they haven't already done so.
  """
  defp check_user_product(
         %UserProduct{voted_not_vegan: false} = usr_prduct,
         {true, _} = _report_confirm,
         product
       ) do
    changeset =
      UserProduct.changeset(
        usr_prduct,
        %{:voted_not_vegan => true}
      )

    case Api.Repo.update(changeset) do
      {:ok, _} -> IO.puts("filler")
      {:error, _} -> IO.puts("filler")
    end

    changeset =
      Product.changeset(
        product,
        %{:not_vegan_count => product.not_vegan_count + 1}
      )

    case Api.Repo.update(changeset) do
      {:ok, _} -> IO.puts("filler")
      {:error, _} -> IO.puts("filler")
    end

    "Success! Reported not vegan"
  end

  def put_product_is_vegan(conn) do
    product = Api.Product |> Api.Repo.get(conn.query_params["p_id"])
    confirm = parse_elem(conn.body_params["confirm"])
    report = parse_elem(conn.body_params["report"])
    uid = conn.query_params["u_id"]
    pid = conn.query_params["p_id"]

    user_product =
      from(usr_prduct in Api.UserProduct,
        where: usr_prduct.u_id == ^uid,
        where: usr_prduct.p_id == ^pid
      )
      |> Api.Repo.one()

    user_product =
      if !user_product do
        UserProduct.insert_user_product(conn, %{
          p_id: String.to_integer(pid),
          u_id: uid,
          voted_not_vegan: false,
          voted_vegan: false
        })

        user_product =
          from(usr_prduct in UserProduct,
            where: usr_prduct.u_id == ^uid,
            where: usr_prduct.p_id == ^pid
          )
          |> Api.Repo.one()

        user_product
      else
        user_product
      end

    error = check_user_product(user_product, {report, confirm}, product)

    product = Api.Repo.get_by(Product, id: pid)

    IO.inspect(error, label: "errors")

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(
      200,
      Poison.encode!(%{
        successs: "success",
        product: product,
        errors: error
      })
    )
  end

标签: elixir

解决方案


经验法则是您尽量避免在if中使用嵌套条件语句。一般来说,这是非常反惯用的和代码气味。

有很多更好的方法来处理每个问题:函数子句、模式匹配with/1等。

这里最适用的解决问题的方法是坚持将条件拆分为一组具有模式匹配的函数子句。在下面的几行旁边会起作用。

error = check_user_product(user_product, {report, config})

@spec check_user_product(
        up :: %UserProduct{},
        {report :: boolean(), config :: boolean()},
        stage :: :checking_not | :checking_yes
      ) :: any()
defp check_user_product(up, rc, stage \\ :checking_not)

defp check_user_product(
      %UserProduct{voted_not_vegan: true} = up,
      {false, _} = rc,
      :checking_not),
  do: check_user_product(up, rc, :checking_yes)

defp check_user_product(
      %UserProduct{voted_not_vegan: true} = up,
      {true, _},
      :checking_not),
  do: "Whoops! You have already reported this product is not vegan"

defp check_user_product(
      %UserProduct{} = up,
      {true, _},
      :checking_not),
  do: changeset = [...]

defp check_user_product(
      %UserProduct{voted_vegan: true} = up,
      {_, true},
      :checking_yes),
  do: "Whoops! You have already reported this product is vegan"

defp check_user_product(%UserProduct{} = up, _, :checking_yes),
  do: changeset = [...]

defp check_user_product(_, _, _),
  do: "Whoops! No UserProduct"

在这里,我们%UserProduct{}通过投票将模式匹配与模式匹配进行比较,然后立即返回或继续进行下一次检查。

如果不确定,想象一下输入遵循模式匹配路径。

嵌套if的条件是纯粹的邪恶。


voted_vegan通过直接与and进行模式匹配,您可能完全不需要第二个参数voted_not_vegan,但这需要额外的业务领域知识,所以我将把它留作练习。


推荐阅读