Rails + Graphql - Failed to implement Game.id - ruby-on-rails

I'm trying to implement a query type that can search by name of the record instead of id. Here's its definition in query_type.rb.
# Get game by name
field :game_by_name, Types::GameType, null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name) //find a game using the name attribute
end
But when I run:
query {
gameByName(name: "League of Legends") {
id
name
}
}
I get the following error.
Failed to implement Game.id, tried:\n\n
- `Types::GameType#id`, which did not exist\n
- `Game::ActiveRecord_Relation#id`, which did not exist\n
- Looking up hash key `:id` or `\"id\"` on `#<Game::ActiveRecord_Relation:0x00007f5644442888>`, but it wasn't a Hash\n\n
To implement this field, define one of the methods above (and check for typos)\n
This is odd because the following query type works perfectly.
# Get game by ID
field :game_by_id, Types::GameType, null: false do
argument :id, ID, required: true
end
def game_by_id(id:)
Game.find(id)
end
Here's game_type.rb:
module Types
class GameType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
end
end
How do I go about fixing this? Thank you!

I stumbled upon this, chasing a similar error. Not sure if you solved it or not, but I believe the issue is in the query type definition.
You're telling it to return a type of Types::GameType, but the Active Record query returns an Active Record Relation, which is a collection. So, graphql is expecting a single instance of Game, but is instead receiving a collection. Graphql is then trying to map the returned value from the query to the type definition, but is unable to. The best hint is from this line:
Looking up hash key `:id` or `\"id\"` on `#<Game::ActiveRecord_Relation:0x00007f5644442888>`, but it wasn't a Hash..
Graphql is trying to assign :id to the ActiveRecord_Relation and it can't do it.
Two paths forward, depending on how you want the API to behave. Do you want it to return 1 record or many?
Wrapping the Types::GameType within brackets will tell graphql it's a collection and to iterate over the records
# Get game by name
field :game_by_name, [Types::GameType], null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name) //find a game using the name attribute
end
or have Active Record return just 1 record, something like...
# Get game by name
field :game_by_name, Types::GameType, null: false do
argument :name, String, required: true
end
def game_by_name(name:)
Game.where(name: name).limit(1).first //find a game using the name attribute
end
I know this is months old, but just putting it out there for anyone else who stumbles upon this question, like I did!

Related

Graphql-Ruby: Creating interfaces that objects can inherit fields from

module Types::ProgramType
include Types::BaseInterface
description "Objects which inherit from Program"
graphql_name "Program"
orphan_types Types::SomeProgramType, Types::AnotherProgramType, Types::ThirdProgramType
field :id, ID, null: false
field :type, Integer, null: false
field :name, String, null: false
definition_methods do
def self.resolve_type(object, _context)
case object.class
when SomeProgram then SomeProgramType
when AnotherProgram then AnotherProgramType
when ThirdProgram then ThirdProgramType
else
raise "Unknown program type"
end
end
end
end
module Types
class SomeProgramType < Types::BaseObject
implements Types:ProgramType
field :description, String, null: false
end
end
I also added queries for SomeProgram types in the query_type file. I was under the impression that adding "implement " to the object types, would allow them to inherit the fields from the interface (per this link: https://graphql-ruby.org/type_definitions/interfaces) and I would be able to query like this:
query {
someProgram(id: 1) {
name
type
description
}
}
But I am getting errors in graphiql, like "Field 'name' doesn't exist on type 'SomeProgram'". What am I missing?
Update:
My QueryType class:
class QueryType < Types::BaseObject
# Add `node(id: ID!) and `nodes(ids: [ID!]!)`
include GraphQL::Types::Relay::HasNodeField
include GraphQL::Types::Relay::HasNodesField
field :some_programs, [SomeProgramType], null: false,
description: "all some programs"
def some_programs
SomeProgram.all
end
field :some_program, SomeProgramType, null: false do
argument :id, ID, required: true
end
def some_program(id:)
SomeProgram.find(id)
end
end
Can you also share how you expose someProgram in your query type?
You can also debug this by looking into the schema if you are using GraphiQL app. You can see the return type of someProgram which should be type of ProgramType.
You can also update your query to include __typename so maybe start with
query {
someProgram(id: 1) {
__typename
}
}
First to see what is the return type and if it's being properly handled in the resolve_type
I figured out the issue. I had put implements Types:SomeProgram instead of implements Types::SomeProgram. I was missing a colon.

How to properly update model using graphql-ruby?

I'm working on a side project to learn implementation of GraphQL into a Rails 6 app. To do this, I'm using the graphql-ruby gem.
I've got a resolve method to update a Medium model that looks like this:
module Mutations
module Media
class UpdateMedia < GraphQL::Schema::Mutation
include ::GraphqlAuthenticationConcerns
include ::GraphqlActiveModelConcerns
description 'Update Media'
argument :id, Integer, required: true
argument :title, String, required: false
argument :preview_url, String, required: false
argument :preview_image, String, required: false
argument :watched, Boolean, required: false
field :success, Boolean, null: false
field :errors, [Types::ActiveModelError], null: false
field :media, Types::MediumType, null: false
def resolve(id:, title:, release_date:, preview_url:, preview_image:, watched:)
authenticate_user!
media = Medium.find(id)
media_params = {
title: title,
preview_url: preview_url,
preview_image: preview_image,
watched: watched,
}
if media.update(media_params)
success_response(media)
else
failed_response(media)
end
end
private
def success_response(media)
{
success: true,
errors: [],
media: media
}
end
def failed_response(media)
{
success: false,
errors: errors(media)
}
end
end
end
end
If I set up the arguments in this way and I want to only update the watched field, I receive a 500 error stating missing keywords: :title, :release_date, :preview_url, :preview_image.
I saw this issue in the graphql-ruby repo from someone with the same problem, however they were told to set default values to nil, and when I tried this it of course sets every column for that model to nil.
I want to be able to change just the fields that are actually being passed as arguments, without affecting others. How do I allow for both a required parameter (id), as well as optional arguments?
Finally figured it out. By defining the method like this:
def resolve(id:, **args)
authenticate_user!
media = Medium.find(id)
if media.update(args)
success_response(media)
else
failed_response(media)
end
end
this keeps the id argument as required, and allows other params to pass through without setting the entire record to nil.
Ended up being more of a general Ruby question rather than specific to graphql-ruby.

How to get the field arguments in connection type in graphql ruby

Currently I have the user's query type which returns a list of all the users. I have defined a custom connection and used it in the user type.
module Types
class QueryType < Types::BaseObject
description "Get all the users"
field :users, Types::UserConnection, null: false do
argument :search, String, required: false
argument :limit, Integer, required: true
argument :offset, Integer, required: true
end
def users(search:, limit:, offset:)
User.search(search).limit(limit).offset(offset)
end
end
end
My user connection looks something like this...
class Types::UserEdgeType < GraphQL::Types::Relay::BaseEdge
node_type(Types::UserType)
end
class Types::UserConnection < GraphQL::Types::Relay::BaseConnection
edge_type(Types::UserEdgeType)
field :items_currently_on_page, Integer, null: false
field :total_pages, Integer, null: false
field :items_per_page, Integer, null: false
def items_currently_on_page
object.items.size
end
def total_pages
(User.all.count.to_f / items_per_page).ceil
end
def items_per_page
3
end
end
Currently I have hard-coded the items_per_page which corresponds to the limit defined in the user's arguments. My question is how can I get the user's limit argument and replace with the hard coded value.
If you are sending the query using variables, then you can use context.query.provided_variables. That's a hash containing the variables you sent.
In the resolve function of a field you have access to the context variable.
If you have a graphql query like this:
{
users(offset: 10, limit:10) {
id
name
}
}
you should be able to access the arguments and fields like that:
# .query: Access GraphQL::Query instance
# .lookahead: Access Class: GraphQL::Execution::Lookahead instance
# Lookahead creates a uniform interface to inspect the forthcoming selections.
# .ast_nodes: Access to Array<GraphQL::Language::Nodes::Field> (.length always 1 for one query)
# .selections: Access to Array<Nodes::Field> (.length always 1 for one query)
# .name returns to name of the query defined in query_type.rb for example "users"
# .children: Access to Class: GraphQL::Language::Nodes::AbstractNode instance
# AbstractNode is the base class for all nodes in a GraphQL AST.
# Seems to be the root of the field selection of a query.
# Contains all queried connection fields like nodes, edges, pageInfo, totalCount
# Also contains the provided arguments like first,last,after,before,limit,offset.
# nodes.selections: Access to Array<Nodes::Field>
# Contains all requested nodes like id, slug, name, [...]
arguments = context.query.lookahead.ast_nodes[0].selections[0].arguments
fields = context.query.lookahead.ast_nodes[0].selections[0].children.find {|c| c.name == "nodes"}.selections
To get the value of the limit argument this should work:
def items_per_page
context.query.lookahead.ast_nodes[0].selections[0].arguments.find {|a| a.name == "limit"}.value
end
use this function for get the count of the items_per_page
def items_per_page
object.nodes.size
end

Reuse GraphQL arguments in Ruby

I'm building my first GraphQL api using rails and the graphql-ruby gem. So far it's been very simple and just awesome.
I'm kinda stuck now in regards to duplicate code. I've got a project management rails app which has spaces, todos (they belong to a space), users.
What I want to achieve is to be able to query spaces and its todos but also all the todos for the current user for example. For todos I want to be able to filter them using different arguments:
Done - Boolean,
Scope - String (today or thisWeek),
Assignee - Integer (Id of a user)
query {
space(id: 5) {
todos(done: false) {
name
done
dueAt
}
}
}
query {
me {
todos(done: false, scope: today) {
name
done
dueAt
}
}
}
That means everytime I got the field todos I want to be able to filter them whether they are done or due today etc.
I know how to use arguments and everything works fine but currently I got the same coder over and over again. How (and where) could I extract that code to make it reusable everytime I have the same todos field?
field :todos, [TodoType], null: true do
argument :done, Boolean, required: false
argument :scope, String, required: false
argument :limit, Integer, required: false
end
Okay, as #jklina also said I ended up using a custom resolver.
I changed:
field :todos, [TodoType], null: true do
argument :done, Boolean, required: false
argument :scope, String, required: false
argument :limit, Integer, required: false
end
to:
field :todos, resolver: Resolvers::Todos, description: "All todos that belong to a given space"
and added a Resolver:
module Resolvers
class Todos < Resolvers::Base
type [Types::TodoType], null: false
TodoScopes = GraphQL::EnumType.define do
name "TodoScopes"
description "Group of available scopes for todos"
value "TODAY", "List all todos that are due to today", value: :today
value "THISWEEK", "List all todos that are due to the end of the week", value: :this_week
end
argument :done, Boolean, required: false
argument :scope, TodoScopes, required: false
argument :limit, Integer, required: false
argument :assignee_id, Integer, required: false
def resolve(done: nil, scope:nil, limit:5000, assignee_id: nil)
todos = object.todos
# filtering the records...
return todos.limit(limit)
end
end
end
Quite easy!

How to write a graphQL mutation that allows for partial updates?

So I'm working on trying to learn GraphQL for ruby for a project.
I've almost got some parts of it up and running, but I'm having issues with other parts. There are plenty of tutorials out there that cover ultra-basics, but none of them seem to expand in the right directions.
I have a mutation to update my user. So far so good. I can look up the user by their ID, and update a single specific field. I can extend that to updating two fields.
What I cannot do, and this is looking insane, is generalize those fields -- at all. My user model will wind up with over 20 fields attached to it -- phone numbers, addresses, job title, etc etc.
When I create the mutation, I have to define the arguments that go into the resolve method. So far so good. I then define the fields the mutation can return. Again, so far so good.
Then I get to the actual resolve method.
The initial syntax isn't bad. def resolve(user_id:, name:, email:). Then you discover that despite setting required to false, you have to include all the values. You need to specify default values for the optional variables. So it becomes def resolve(user_id:, name: null, email: null) -- but that actually nulls out those values, you can't do partial updates. Worse yet, imagine having 20 fields you have to set this way. You can play games by trying to convert the arguments into a dictionary and rejecting null values -- but then you can't set properties to nil if they need to be nil again.
The solution: a double splat operator. Your syntax becomes def resolve(user_id:, **args). From what I can tell, it turns all remaining named arguments into a dictionary -- and I think unnamed arguments would become an array. Not sure how it would react with a mix of the two.
Full model becomes:
argument :user_id, ID, required: true#, loads: Types::UserType
argument :name, String, required: false
argument :email, String, required: false
field :user, Types::UserType, null: true
field :errors, Types::UserType, null: true
def resolve(user_id:, **args)
user = User.find(user_id)
if user.update(args)
{
user: user,
errors: []
}
else
{
user: nil,
errors: user.errors.full_messages
}
end
end
end

Resources