Elixir - 将复杂 if 语句的结果分配给变量

Elixir - Assigning the result of a complex if statement to a variable

如何使 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

每个可能的控制流都必须return一个字符串:

error =
      if true do
        if !false and true do
          "!false and true"
        else
          "2nd else case"
        end
      else
        "1st else case"
      end
"!false and true"

经验法则是尽量避免在 中使用嵌套的 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{} 进行模式匹配,通过投票进行模式匹配,然后立即 return 或继续进行下一次检查。

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

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


您可以通过直接与 voted_veganvoted_not_vegan 进行模式匹配来完全避免第二个参数的需要,但这需要业务领域的额外知识,所以我会离开这是一个练习。