Skip to content

Commit 192ca71

Browse files
committed
add some basic error handling, add update menu item mutation
1 parent ada8cd1 commit 192ca71

File tree

6 files changed

+132
-25
lines changed

6 files changed

+132
-25
lines changed

lib/plate_slate/menu/item.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ defmodule PlateSlate.Menu.Item do
2222
|> cast(attrs, [:name, :description, :price, :added_on])
2323
|> validate_required([:name, :price])
2424
|> foreign_key_constraint(:category)
25+
|> unique_constraint(:name)
2526
end
2627
end

lib/plate_slate_web/resolvers/menu.ex

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1+
# ---
2+
# Excerpted from "Craft GraphQL APIs in Elixir with Absinthe",
3+
# published by The Pragmatic Bookshelf.
4+
# Copyrights apply to this code. It may not be used to create training material,
5+
# courses, books, articles, and the like. Contact us if you are in doubt.
6+
# We make no guarantees that this code is fit for any purpose.
7+
# Visit http://www.pragmaticprogrammer.com/titles/wwgraphql for more book information.
8+
# ---
19
defmodule PlateSlateWeb.Resolvers.Menu do
210
alias PlateSlate.Menu
311

4-
def create_item(_, %{input: params}, _) do
5-
case Menu.create_item(params) do
6-
{:error, _} ->
7-
{:error, "Could not create menu item"}
8-
9-
{:ok, _} = success ->
10-
success
11-
end
12-
end
13-
1412
def menu_items(_, args, _) do
1513
{:ok, Menu.list_items(args)}
1614
end
@@ -23,4 +21,42 @@ defmodule PlateSlateWeb.Resolvers.Menu do
2321
query = Ecto.assoc(category, :items)
2422
{:ok, PlateSlate.Repo.all(query)}
2523
end
24+
25+
def create_item(_, %{input: params}, _) do
26+
case Menu.create_item(params) do
27+
{:error, changeset} ->
28+
{:ok, %{errors: transform_errors(changeset)}}
29+
30+
{:ok, menu_item} ->
31+
{:ok, %{menu_item: menu_item}}
32+
end
33+
end
34+
35+
def update_item(_, %{id: id, input: input}, _) do
36+
item = Menu.get_item!(id)
37+
38+
case Menu.update_item(item, input) do
39+
{:error, changeset} ->
40+
{:ok, %{errors: transform_errors(changeset)}}
41+
42+
{:ok, menu_item} ->
43+
{:ok, %{menu_item: menu_item}}
44+
end
45+
end
46+
47+
defp transform_errors(changeset) do
48+
changeset
49+
|> Ecto.Changeset.traverse_errors(&format_error/1)
50+
|> Enum.map(fn
51+
{key, value} ->
52+
%{key: key, message: value}
53+
end)
54+
end
55+
56+
@spec format_error(Ecto.Changeset.error()) :: String.t()
57+
defp format_error({msg, opts}) do
58+
Enum.reduce(opts, msg, fn {key, value}, acc ->
59+
String.replace(acc, "%{#{key}}", to_string(value))
60+
end)
61+
end
2662
end

lib/plate_slate_web/schema.ex

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
defmodule PlateSlateWeb.Schema do
22
use Absinthe.Schema
33

4-
alias PlateSlateWeb.Resolvers
5-
64
import_types(__MODULE__.MenuTypes)
75

86
query do
97
import_fields(:menu_queries)
10-
11-
field :search, list_of(:search_result) do
12-
arg(:matching, non_null(:string))
13-
resolve(&Resolvers.Menu.search/3)
14-
end
158
end
169

1710
mutation do
@@ -45,6 +38,12 @@ defmodule PlateSlateWeb.Schema do
4538
end)
4639
end
4740

41+
@desc "An error encountered trying to persist input"
42+
object :input_error do
43+
field :key, non_null(:string)
44+
field :message, non_null(:string)
45+
end
46+
4847
enum :sort_order do
4948
value(:asc)
5049
value(:desc)

lib/plate_slate_web/schema/menu_types.ex

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,35 @@ defmodule PlateSlateWeb.Schema.MenuTypes do
33

44
alias PlateSlateWeb.Resolvers
55

6+
object :menu_item_result do
7+
field :menu_item, :menu_item
8+
field :errors, list_of(:input_error)
9+
end
10+
611
object :menu_queries do
712
field :menu_items, list_of(:menu_item) do
813
arg(:filter, :menu_item_filter)
914
arg(:order, type: :sort_order, default_value: :asc)
1015
resolve(&Resolvers.Menu.menu_items/3)
1116
end
17+
18+
field :search, list_of(:search_result) do
19+
arg(:matching, non_null(:string))
20+
resolve(&Resolvers.Menu.search/3)
21+
end
1222
end
1323

1424
object :menu_mutations do
15-
field :create_menu_item, :menu_item do
25+
field :create_menu_item, :menu_item_result do
1626
arg(:input, non_null(:menu_item_input))
1727
resolve(&Resolvers.Menu.create_item/3)
1828
end
29+
30+
field :update_menu_item, :menu_item_result do
31+
arg(:id, non_null(:id))
32+
arg(:input, non_null(:update_item_input))
33+
resolve(&Resolvers.Menu.update_item/3)
34+
end
1935
end
2036

2137
input_object :menu_item_input do
@@ -25,6 +41,13 @@ defmodule PlateSlateWeb.Schema.MenuTypes do
2541
field :category_id, non_null(:id)
2642
end
2743

44+
input_object :update_item_input do
45+
field :name, :string
46+
field :description, :string
47+
field :price, :decimal
48+
field :category_id, :id
49+
end
50+
2851
object :category do
2952
field(:name, :string)
3053
field(:description, :string)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
defmodule PlateSlate.Repo.Migrations.AddIndexForMenuItemNames do
2+
use Ecto.Migration
3+
4+
def change do
5+
create unique_index(:items, [:name])
6+
end
7+
end

test/plate_slate_web/schema/mutation/create_menu_item_test.exs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# ---
2+
# Excerpted from "Craft GraphQL APIs in Elixir with Absinthe",
3+
# published by The Pragmatic Bookshelf.
4+
# Copyrights apply to this code. It may not be used to create training material,
5+
# courses, books, articles, and the like. Contact us if you are in doubt.
6+
# We make no guarantees that this code is fit for any purpose.
7+
# Visit http://www.pragmaticprogrammer.com/titles/wwgraphql for more book information.
8+
# ---
19
defmodule PlateSlateWeb.Schema.Mutation.CreateMenuTest do
210
use PlateSlateWeb.ConnCase, async: true
311

@@ -19,13 +27,15 @@ defmodule PlateSlateWeb.Schema.Mutation.CreateMenuTest do
1927
@query """
2028
mutation ($menuItem: MenuItemInput!) {
2129
createMenuItem(input: $menuItem) {
22-
name
23-
description
24-
price
30+
errors { key message }
31+
menuItem {
32+
name
33+
description
34+
price
35+
}
2536
}
2637
}
2738
"""
28-
2939
test "createMenuItem field creates an item", %{category_id: category_id} do
3040
menu_item = %{
3141
"name" => "French Dip",
@@ -44,9 +54,40 @@ defmodule PlateSlateWeb.Schema.Mutation.CreateMenuTest do
4454
assert json_response(conn, 200) == %{
4555
"data" => %{
4656
"createMenuItem" => %{
47-
"name" => menu_item["name"],
48-
"description" => menu_item["description"],
49-
"price" => menu_item["price"]
57+
"errors" => nil,
58+
"menuItem" => %{
59+
"name" => menu_item["name"],
60+
"description" => menu_item["description"],
61+
"price" => menu_item["price"]
62+
}
63+
}
64+
}
65+
}
66+
end
67+
68+
test "creating a menu item with an existing name fails",
69+
%{category_id: category_id} do
70+
menu_item = %{
71+
"name" => "Reuben",
72+
"description" => "Roast beef, caramelized onions, horseradish, ...",
73+
"price" => "5.75",
74+
"categoryId" => category_id
75+
}
76+
77+
conn = build_conn()
78+
79+
conn =
80+
post conn, "/api",
81+
query: @query,
82+
variables: %{"menuItem" => menu_item}
83+
84+
assert json_response(conn, 200) == %{
85+
"data" => %{
86+
"createMenuItem" => %{
87+
"errors" => [
88+
%{"key" => "name", "message" => "has already been taken"}
89+
],
90+
"menuItem" => nil
5091
}
5192
}
5293
}

0 commit comments

Comments
 (0)