如何使用 Elixir 和 GraphQL 从数据库中删除照片
How to delete a photo from a database using Elixir and GraphQL
我一直在关注 EQuimper 制作的 Instagram 克隆教程。我一直在尝试通过后端进一步扩展应用程序,因为目前不关心前端。我正在尝试实现删除照片功能,但它似乎不起作用,我也不知道为什么。
mutation do
@doc """
deletes a photo in the database
"""
field :photo_delete, list_of(:photo) do
arg :id, non_null(:id)
resolve &Resolvers.Posts.photo_delete/3
end
end
我的 schema.ex 在突变部分下包含此代码。
def photos(_,_,_) do
{:ok, Instagram.Posts.list_photos}
end
def photo(_, %{id: id}, _) do
{:ok, Instagram.Posts.get_photo!(id)}
end
def photo_delete(_, %{id: id}, _) do
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
{:ok, Instagram.Posts.delete_photo(photo_from_db)}
end
这是 resolver.posts 文件中用于返回列表或单张照片的代码。
def delete_photo(%Photo{} = photo) do
Repo.delete(photo)
end
这是执行突变以从数据库中删除照片的代码,它接收照片结构并将其删除。
object :photo do
field :id, non_null(:id)
field :image_url, non_null(:string)
field :caption, :string
field :inserted_at, non_null(:string)
field :update_at, non_null(:string)
end
这是定义照片架构的代码。
schema "photos" do
field :caption, :string
field :image_url, :string
timestamps()
end
@doc 错误
def changeset(%Photo{} = photo, attrs) 做
照片
|> cast(attrs, [:image_url, :caption])
|> validate_required([:image_url])
结束
此代码位于处理架构的 photo.ex 文件中(我认为)
mutation {
photo_delete(id: 1){
id
}
}
这是我运行从数据库中删除查询的突变。它 returns 一个错误说
"no function clause matching in Instagram.Posts.delete_photo/1"
从终端返回。
我做错了什么?我对这个例子中的函数流程有什么不理解的。
Link 到视频系列:https://www.youtube.com/watch?v=AVQQF_J3Az0&list=PLzQWIQOqeUSOu74jGJMRH06gneM3wL82Z 以进一步说明。
我认为最有可能的罪魁祸首是 schema.ex 中函数调用正上方的行:
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
您可能想要的是:
{:ok, photo_from_db} = Instagram.Posts.get_photo!(id)
这样您将向函数传递它期望的照片结构,而不是 {:ok, %Photo{}}
。
找到答案
field :delete_user, :user do
arg :id, non_null(:id)
resolve &Graphical.UserResolver.delete/2
end
这会进入您的 schema.ex 文件
def delete(%{id: id}, _info) do
Accounts.get_user!(id)
|> Accounts.delete_user
end
然后从架构文件中调用它,它将找到该 ID 处的记录,然后将其通过管道传递到删除方法,从数据库中删除该记录
def photo_delete(_, %{id: id}, _) do
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
{:ok, Instagram.Posts.delete_photo(photo_from_db)}
end
应该是这样的
with {:ok, photo_from_db} <- Instagram.Posts.get_photo!(id) do
Instagram.Posts.delete_photo(photo_from_db)
else
{:error, error} ->
{:error, error}
end
或类似的东西。
此外,我现在将所有上下文函数写入 return :ok/:error
元组,因此一切都与 Absinthe 相得益彰。总的来说,这似乎也是一种很好的做法。
通过硬编码 :ok 条件,您没有正确处理失败的情况,只会抛出异常而不是 returning 有用的错误。
你可以使用这种中间件来处理错误:
defmodule ApiWeb.Middleware.ErrorMiddleware do
require Logger
alias Api.Error.AbsintheError
def add_error_handling(spec) do
fn res, config ->
spec
|> to_fun(res, config)
|> exec_safely(res)
end
end
defp to_fun({{module, function}, config}, res, _config) do
fn -> apply(module, function, [res, config]) end
end
defp to_fun({module, config}, res, _config) do
fn -> apply(module, :call, [res, config]) end
end
defp to_fun(module, res, config) when is_atom(module) do
fn -> apply(module, :call, [res, config]) end
end
defp to_fun(fun, res, config) when is_function(fun, 2) do
fn -> fun.(res, config) end
end
defp exec_safely(fun, res) do
fun.()
|> Map.update!(:errors, &Enum.map(&1, fn e -> AbsintheError.serialize(e) end))
rescue
err ->
# TODO: https://authkit.atlassian.net/projects/AUT/issues/AUT-9
Logger.error(Exception.format(:error, err, __STACKTRACE__))
Absinthe.Resolution.put_result(
res,
{:error, %{code: :internal_server_error, message: "An internal server error has occured"}}
)
end
end
然后像这样构建错误结构
defmodule Api.Error.NotFoundError do
@type error_source ::
:internal | :network
@type t :: %__MODULE__{
source: error_source,
code: :not_found,
message: String.t()
}
@enforce_keys [:source, :code, :message]
defstruct [:source, :code, :message]
@spec new(Keyword.t()) :: t
def new(fields) do
struct!(__MODULE__, fields)
end
end
并像这样实现
defprotocol Api.Error.AbsintheError do
def serialize(err)
end
defimpl Api.Error.AbsintheError, for: Api.Error.NotFoundError do
@doc """
`serialize` takes our standard `%NotFoundError{}` struct and converts it
into a regular map in order to make it play nice with `Absinthe`. We then
use `Absinthe` `middleware` to force the execution of serialize every time
it gets passed a `%NotFoundError{}`.
"""
def serialize(err) do
%{source: err.source, code: err.code, message: err.message}
end
end
我一直在关注 EQuimper 制作的 Instagram 克隆教程。我一直在尝试通过后端进一步扩展应用程序,因为目前不关心前端。我正在尝试实现删除照片功能,但它似乎不起作用,我也不知道为什么。
mutation do
@doc """
deletes a photo in the database
"""
field :photo_delete, list_of(:photo) do
arg :id, non_null(:id)
resolve &Resolvers.Posts.photo_delete/3
end
end
我的 schema.ex 在突变部分下包含此代码。
def photos(_,_,_) do
{:ok, Instagram.Posts.list_photos}
end
def photo(_, %{id: id}, _) do
{:ok, Instagram.Posts.get_photo!(id)}
end
def photo_delete(_, %{id: id}, _) do
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
{:ok, Instagram.Posts.delete_photo(photo_from_db)}
end
这是 resolver.posts 文件中用于返回列表或单张照片的代码。
def delete_photo(%Photo{} = photo) do
Repo.delete(photo)
end
这是执行突变以从数据库中删除照片的代码,它接收照片结构并将其删除。
object :photo do
field :id, non_null(:id)
field :image_url, non_null(:string)
field :caption, :string
field :inserted_at, non_null(:string)
field :update_at, non_null(:string)
end
这是定义照片架构的代码。
schema "photos" do
field :caption, :string
field :image_url, :string
timestamps()
end
@doc 错误 def changeset(%Photo{} = photo, attrs) 做 照片 |> cast(attrs, [:image_url, :caption]) |> validate_required([:image_url]) 结束
此代码位于处理架构的 photo.ex 文件中(我认为)
mutation {
photo_delete(id: 1){
id
}
}
这是我运行从数据库中删除查询的突变。它 returns 一个错误说
"no function clause matching in Instagram.Posts.delete_photo/1"
从终端返回。 我做错了什么?我对这个例子中的函数流程有什么不理解的。 Link 到视频系列:https://www.youtube.com/watch?v=AVQQF_J3Az0&list=PLzQWIQOqeUSOu74jGJMRH06gneM3wL82Z 以进一步说明。
我认为最有可能的罪魁祸首是 schema.ex 中函数调用正上方的行:
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
您可能想要的是:
{:ok, photo_from_db} = Instagram.Posts.get_photo!(id)
这样您将向函数传递它期望的照片结构,而不是 {:ok, %Photo{}}
。
找到答案
field :delete_user, :user do
arg :id, non_null(:id)
resolve &Graphical.UserResolver.delete/2
end
这会进入您的 schema.ex 文件
def delete(%{id: id}, _info) do
Accounts.get_user!(id)
|> Accounts.delete_user
end
然后从架构文件中调用它,它将找到该 ID 处的记录,然后将其通过管道传递到删除方法,从数据库中删除该记录
def photo_delete(_, %{id: id}, _) do
photo_from_db = {:ok, Instagram.Posts.get_photo!(id)}
{:ok, Instagram.Posts.delete_photo(photo_from_db)}
end
应该是这样的
with {:ok, photo_from_db} <- Instagram.Posts.get_photo!(id) do
Instagram.Posts.delete_photo(photo_from_db)
else
{:error, error} ->
{:error, error}
end
或类似的东西。
此外,我现在将所有上下文函数写入 return :ok/:error
元组,因此一切都与 Absinthe 相得益彰。总的来说,这似乎也是一种很好的做法。
通过硬编码 :ok 条件,您没有正确处理失败的情况,只会抛出异常而不是 returning 有用的错误。
你可以使用这种中间件来处理错误:
defmodule ApiWeb.Middleware.ErrorMiddleware do
require Logger
alias Api.Error.AbsintheError
def add_error_handling(spec) do
fn res, config ->
spec
|> to_fun(res, config)
|> exec_safely(res)
end
end
defp to_fun({{module, function}, config}, res, _config) do
fn -> apply(module, function, [res, config]) end
end
defp to_fun({module, config}, res, _config) do
fn -> apply(module, :call, [res, config]) end
end
defp to_fun(module, res, config) when is_atom(module) do
fn -> apply(module, :call, [res, config]) end
end
defp to_fun(fun, res, config) when is_function(fun, 2) do
fn -> fun.(res, config) end
end
defp exec_safely(fun, res) do
fun.()
|> Map.update!(:errors, &Enum.map(&1, fn e -> AbsintheError.serialize(e) end))
rescue
err ->
# TODO: https://authkit.atlassian.net/projects/AUT/issues/AUT-9
Logger.error(Exception.format(:error, err, __STACKTRACE__))
Absinthe.Resolution.put_result(
res,
{:error, %{code: :internal_server_error, message: "An internal server error has occured"}}
)
end
end
然后像这样构建错误结构
defmodule Api.Error.NotFoundError do
@type error_source ::
:internal | :network
@type t :: %__MODULE__{
source: error_source,
code: :not_found,
message: String.t()
}
@enforce_keys [:source, :code, :message]
defstruct [:source, :code, :message]
@spec new(Keyword.t()) :: t
def new(fields) do
struct!(__MODULE__, fields)
end
end
并像这样实现
defprotocol Api.Error.AbsintheError do
def serialize(err)
end
defimpl Api.Error.AbsintheError, for: Api.Error.NotFoundError do
@doc """
`serialize` takes our standard `%NotFoundError{}` struct and converts it
into a regular map in order to make it play nice with `Absinthe`. We then
use `Absinthe` `middleware` to force the execution of serialize every time
it gets passed a `%NotFoundError{}`.
"""
def serialize(err) do
%{source: err.source, code: err.code, message: err.message}
end
end