Handling particular constraint errors in Phoenix

时间:2016-08-30 04:26:35

标签: elixir phoenix-framework

I have a changeset with a unique constraint on one of the fields:

defmodule Oauth.Shop do
  use Ecto.Model
  import Ecto.Changeset
  alias Ecto.Changeset

  schema "shops" do
    field :shop, :string
    field :access_token, :string
    field :scope, :string
    field :active, :boolean
    timestamps
  end

  def changeset(shop, params \\ %{}) do
    shop
    |> cast(params, [:shop, :access_token, :scope, :active])
    |> Changeset.validate_required([:shop, :access_token, :scope, :active])
    |> unique_constraint(:shop)
  end
end

In one of the controllers, I insert a new shop. However, if a duplicate shop is being created, an exception is raised:

** (exit) an exception was raised:
    ** (Ecto.ConstraintError) constraint error when attempting to insert model:

    * unique: shops_shop_index

Here is the code where I save the record:

  def save_shop({:ok, access_params}, shop) do
    Repo.insert(%Shop{shop: shop, access_token: access_params.access_token, scope: access_params.scope})
    hook_uninstall(shop, access_params.access_token)
    {:ok}
  end

Note that %Shop is a struct, while the variable shop is just a value from a query string parameter.

Although I could create a plug for Ecto.ConstraintError, I feel this won't give the the granular control I need for detailed user feedback.

What is a good way to catch the exception and notify the user that the shop has already been registered?

1 个答案:

答案 0 :(得分:3)

如果您希望该函数中定义的验证和unique_constraint具有任何结构,则需要将Ecto.Changeset返回的Shop.changeset/2直接传递给Repo.insert而不是Shop结构影响。您还必须使用模式匹配来处理错误情况。

def save_shop({:ok, access_params}, shop) do
  case Repo.insert(Shop.changeset(%Shop{}, %{shop: shop, access_token: access_params.access_token, scope: access_params.scope})) do
    {:ok, _} ->
      hook_uninstall(shop, access_params.access_token)
      {:ok}
    {:error, changeset} ->
      # Put whatever value you want to return in case of an error here.
      # You can get more details about the error using `changeset.errors`.
      {:error}
  end
end

(我假设你只想在插入成功的情况下运行hook_uninstall。如果不是这种情况,你可以将该行复制到另一个分支或将其移到case之外。)

在可能的情况下使用模式匹配而不是使用.field访问地图的字段时,也被认为更惯用:

def save_shop({:ok, %{access_token: access_token, scope: scope}}, shop) do
  case Repo.insert(Shop.changeset(%Shop{}, %{shop: shop, access_token: access_token, scope: scope})) do
    {:ok, _} ->
      hook_uninstall(shop, access_token)
      {:ok}
    {:error, changeset} ->
      # Put whatever value you want to return in case of an error here.
      # You can get more details about the error using `changeset.errors`.
      {:error}
  end
end