Skip to content
This repository was archived by the owner on Sep 24, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions app/models/graph.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ def find_by_id_field(type, model)
type type
argument :id, !types.ID
resolve ->(_, args, _) do
gid = GlobalID.parse(args[:id])
model_id = Graph.parse_id(args[:id], model)

return unless gid
return unless gid.model_name == type.name

Graph::FindLoader.for(model).load(gid.model_id.to_i)
Graph::FindLoader.for(model).load(model_id)
end
end
end

def parse_id(gid, model)
parsed_gid = GlobalID.parse(gid)

return unless parsed_gid
return unless parsed_gid.app == GlobalID.app
return unless parsed_gid.model_name != model.name.downcase

parsed_gid.model_id.to_i
end
end
end
Empty file removed app/models/graph/mutations/.keep
Empty file.
39 changes: 39 additions & 0 deletions app/models/graph/mutations/film_rate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module Graph
module Mutations
FilmRate = GraphQL::Relay::Mutation.define do
name "FilmRate"

input_field :filmId, !types.ID
input_field :rating, !types.Int

return_field :film, !Graph::Types::Film
return_field :rating, Graph::Types::Rating
return_field :errors, !types[!Graph::Types::MutationError]

resolve ->(_, input, ctx) do
raise GraphQL::ExecutionError.new('Authentication required to rate a film.') unless user = ctx[:user]

film_id = Graph.parse_id(input['filmId'], Film)
film = Film.find_by(id: film_id) if film_id
raise GraphQL::ExecutionError.new('Invalid filmId.') unless film
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuorig any objections? Feels like this is a dev error. 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm yeah, you're right. No real app would ask for an id as input i guess


rating = user.ratings.where(film: film).first_or_initialize
rating.rating = input['rating']

if rating.save
{
film: film,
rating: rating,
errors: []
}
else
{
film: film,
rating: nil,
errors: rating.errors.map { |field, message| MutationError.new(field, message) },
}
end
end
end
end
end
8 changes: 8 additions & 0 deletions app/models/graph/mutations/mutation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Graph
module Mutations
Mutation = GraphQL::ObjectType.define do
name "Mutation"
field :filmRate, field: Graph::Mutations::FilmRate.field
end
end
end
6 changes: 6 additions & 0 deletions app/models/graph/mutations/mutation_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Graph
module Mutations
class MutationError < Struct.new(:field, :message)
end
end
end
3 changes: 3 additions & 0 deletions app/models/graph/schema.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Graph
Schema = GraphQL::Schema.define do
query Graph::Types::Query
mutation Graph::Mutations::Mutation

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

object_from_id ->(id, query_ctx) do
gid = GlobalID.parse(id)
return unless gid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If id is garbage, gid will be nil and will 💥 later.


possible_types = query_ctx.warden.possible_types(GraphQL::Relay::Node.interface)

return unless possible_types.map(&:name).include?(gid.model_name)
Expand Down
10 changes: 10 additions & 0 deletions app/models/graph/types/mutation_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Graph
module Types
MutationError = GraphQL::ObjectType.define do
name "MutationError"

field :field, !types.String, "The name of the input field that caused the error."
field :message, !types.String, "The description of the error."
end
end
end
2 changes: 1 addition & 1 deletion app/models/rating.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ class Rating < ApplicationRecord
belongs_to :user
belongs_to :film

validates_inclusion_of :rating, in: 0..5
validates :rating, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 5 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better error message when you do this.

end
136 changes: 136 additions & 0 deletions test/models/graph/mutations/film_rate_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
require 'test_helper'

class Graph::Mutations::FilmRateTest < ActiveSupport::TestCase
def setup
@context = {
user: @user = User.first
}

@query_string = "
mutation ($input: FilmRateInput!) {
filmRate(input: $input) {
film {
title
}
rating {
rating
}
errors {
field
message
}
}
}
"

@film = Film.first

@variables = {
"input" => {
"filmId" => @film.to_global_id.to_s,
"rating" => 5,
}
}
end

test "raises an execution error when user is not logged in" do
expected = {
"data" => { "filmRate" => nil},
"errors" => [{
"message" => "Authentication required to rate a film.",
"locations" => [{ "line" => 3, "column" => 9}],
"path" => ["filmRate"]}
]
}

result = Graph::Schema.execute(@query_string, variables: @variables, context: {})
assert_equal expected, result
end

test "raises an execution error when filmId is invalid" do
@variables['input']['filmId'] = 'invalid'
expected = {
"data" => { "filmRate" => nil},
"errors" => [{
"message" => "Invalid filmId.",
"locations" => [{ "line" => 3, "column" => 9}],
"path" => ["filmRate"]}
]
}

result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
assert_equal expected, result
end

test "returns error when an invalid rating is inputted" do
@variables['input']['rating'] = 100
expected = {
"data" => {
"filmRate" => {
"film" => {
"title" => @film.title
},
"rating" => nil,
"errors" => [
{ "field" => "rating", "message" => "must be less than or equal to 5" }
]
}
}
}

result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
assert_equal expected, result
end

test "creates a new rating on success" do
expected = {
"data" => {
"filmRate" => {
"film" => {
"title" => @film.title
},
"rating" => {
"rating" => 5
},
"errors" => [],
}
}
}

assert_difference "Rating.count", 1 do
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
assert_equal expected, result
end

rating = Rating.last
assert_equal @film, rating.film
assert_equal @variables['input']['rating'], rating.rating
assert_equal @user, rating.user
end

test "updates existing rating if user already rated film" do
expected = {
"data" => {
"filmRate" => {
"film" => {
"title" => @film.title
},
"rating" => {
"rating" => 5
},
"errors" => [],
}
}
}

rating = @user.ratings.create(film: @film, rating: 1)

assert_difference "Rating.count", 0 do
result = Graph::Schema.execute(@query_string, variables: @variables, context: @context)
assert_equal expected, result
end

rating.reload
assert_equal @variables['input']['rating'], rating.rating
end
end