Skip to content
This repository was archived by the owner on Sep 24, 2019. It is now read-only.

Commit 4813580

Browse files
authored
Merge pull request #31 from GraphQLAcademy/film-mutation
Add FilmRate Mutation
2 parents a71bfb0 + 3196c8d commit 4813580

File tree

9 files changed

+215
-6
lines changed

9 files changed

+215
-6
lines changed

app/models/graph.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ def find_by_id_field(type, model)
55
type type
66
argument :id, !types.ID
77
resolve ->(_, args, _) do
8-
gid = GlobalID.parse(args[:id])
8+
model_id = Graph.parse_id(args[:id], model)
99

10-
return unless gid
11-
return unless gid.model_name == type.name
12-
13-
Graph::FindLoader.for(model).load(gid.model_id.to_i)
10+
Graph::FindLoader.for(model).load(model_id)
1411
end
1512
end
1613
end
14+
15+
def parse_id(gid, model)
16+
parsed_gid = GlobalID.parse(gid)
17+
18+
return unless parsed_gid
19+
return unless parsed_gid.app == GlobalID.app
20+
return unless parsed_gid.model_name != model.name.downcase
21+
22+
parsed_gid.model_id.to_i
23+
end
1724
end
1825
end

app/models/graph/mutations/.keep

Whitespace-only changes.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module Graph
2+
module Mutations
3+
FilmRate = GraphQL::Relay::Mutation.define do
4+
name "FilmRate"
5+
6+
input_field :filmId, !types.ID
7+
input_field :rating, !types.Int
8+
9+
return_field :film, !Graph::Types::Film
10+
return_field :rating, Graph::Types::Rating
11+
return_field :errors, !types[!Graph::Types::MutationError]
12+
13+
resolve ->(_, input, ctx) do
14+
raise GraphQL::ExecutionError.new('Authentication required to rate a film.') unless user = ctx[:user]
15+
16+
film_id = Graph.parse_id(input['filmId'], Film)
17+
film = Film.find_by(id: film_id) if film_id
18+
raise GraphQL::ExecutionError.new('Invalid filmId.') unless film
19+
20+
rating = user.ratings.where(film: film).first_or_initialize
21+
rating.rating = input['rating']
22+
23+
if rating.save
24+
{
25+
film: film,
26+
rating: rating,
27+
errors: []
28+
}
29+
else
30+
{
31+
film: film,
32+
rating: nil,
33+
errors: rating.errors.map { |field, message| MutationError.new(field, message) },
34+
}
35+
end
36+
end
37+
end
38+
end
39+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module Graph
2+
module Mutations
3+
Mutation = GraphQL::ObjectType.define do
4+
name "Mutation"
5+
field :filmRate, field: Graph::Mutations::FilmRate.field
6+
end
7+
end
8+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module Graph
2+
module Mutations
3+
class MutationError < Struct.new(:field, :message)
4+
end
5+
end
6+
end

app/models/graph/schema.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Graph
22
Schema = GraphQL::Schema.define do
33
query Graph::Types::Query
4+
mutation Graph::Mutations::Mutation
45

56
resolve_type ->(obj, ctx) do
67
Graph::Schema.types.values.find { |type| type.name == obj.class.name }
@@ -12,6 +13,8 @@ module Graph
1213

1314
object_from_id ->(id, query_ctx) do
1415
gid = GlobalID.parse(id)
16+
return unless gid
17+
1518
possible_types = query_ctx.warden.possible_types(GraphQL::Relay::Node.interface)
1619

1720
return unless possible_types.map(&:name).include?(gid.model_name)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module Graph
2+
module Types
3+
MutationError = GraphQL::ObjectType.define do
4+
name "MutationError"
5+
6+
field :field, !types.String, "The name of the input field that caused the error."
7+
field :message, !types.String, "The description of the error."
8+
end
9+
end
10+
end

app/models/rating.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ class Rating < ApplicationRecord
22
belongs_to :user
33
belongs_to :film
44

5-
validates_inclusion_of :rating, in: 0..5
5+
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }
66
end
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
require 'test_helper'
2+
3+
class Graph::Mutations::FilmRateTest < ActiveSupport::TestCase
4+
def setup
5+
@context = {
6+
user: @user = User.first
7+
}
8+
9+
@query_string = "
10+
mutation ($input: FilmRateInput!) {
11+
filmRate(input: $input) {
12+
film {
13+
title
14+
}
15+
rating {
16+
rating
17+
}
18+
errors {
19+
field
20+
message
21+
}
22+
}
23+
}
24+
"
25+
26+
@film = Film.first
27+
28+
@variables = {
29+
"input" => {
30+
"filmId" => @film.to_global_id.to_s,
31+
"rating" => 5,
32+
}
33+
}
34+
end
35+
36+
test "raises an execution error when user is not logged in" do
37+
expected = {
38+
"data" => { "filmRate" => nil},
39+
"errors" => [{
40+
"message" => "Authentication required to rate a film.",
41+
"locations" => [{ "line" => 3, "column" => 9}],
42+
"path" => ["filmRate"]}
43+
]
44+
}
45+
46+
result = Graph::Schema.execute(@query_string, variables: @variables, context: {})
47+
assert_equal expected, result
48+
end
49+
50+
test "raises an execution error when filmId is invalid" do
51+
@variables['input']['filmId'] = 'invalid'
52+
expected = {
53+
"data" => { "filmRate" => nil},
54+
"errors" => [{
55+
"message" => "Invalid filmId.",
56+
"locations" => [{ "line" => 3, "column" => 9}],
57+
"path" => ["filmRate"]}
58+
]
59+
}
60+
61+
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
62+
assert_equal expected, result
63+
end
64+
65+
test "returns error when an invalid rating is inputted" do
66+
@variables['input']['rating'] = 100
67+
expected = {
68+
"data" => {
69+
"filmRate" => {
70+
"film" => {
71+
"title" => @film.title
72+
},
73+
"rating" => nil,
74+
"errors" => [
75+
{ "field" => "rating", "message" => "must be less than or equal to 5" }
76+
]
77+
}
78+
}
79+
}
80+
81+
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
82+
assert_equal expected, result
83+
end
84+
85+
test "creates a new rating on success" do
86+
expected = {
87+
"data" => {
88+
"filmRate" => {
89+
"film" => {
90+
"title" => @film.title
91+
},
92+
"rating" => {
93+
"rating" => 5
94+
},
95+
"errors" => [],
96+
}
97+
}
98+
}
99+
100+
assert_difference "Rating.count", 1 do
101+
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
102+
assert_equal expected, result
103+
end
104+
105+
rating = Rating.last
106+
assert_equal @film, rating.film
107+
assert_equal @variables['input']['rating'], rating.rating
108+
assert_equal @user, rating.user
109+
end
110+
111+
test "updates existing rating if user already rated film" do
112+
expected = {
113+
"data" => {
114+
"filmRate" => {
115+
"film" => {
116+
"title" => @film.title
117+
},
118+
"rating" => {
119+
"rating" => 5
120+
},
121+
"errors" => [],
122+
}
123+
}
124+
}
125+
126+
rating = @user.ratings.create(film: @film, rating: 1)
127+
128+
assert_difference "Rating.count", 0 do
129+
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
130+
assert_equal expected, result
131+
end
132+
133+
rating.reload
134+
assert_equal @variables['input']['rating'], rating.rating
135+
end
136+
end

0 commit comments

Comments
 (0)