After upgrading to the latest version of graphql-ruby, I started seeing the subject error. For example:
GraphQL error: undefined methodobject' for #
I've confirmed that 1.8.3 is fine and that this occurs in 1.8.4 and 1.8.5 in the following code:
def resolve_field(obj, args, ctx)
ctx.schema.after_lazy(obj) do |after_obj|
# First, apply auth ...
query_ctx = ctx.query.context
inner_obj = after_obj && after_obj.object #<------- fails here
So far, I've been able to determine that ctx.schema.after_lazy typically returns some graphql type. But in the failure case it appears to return an instance of one of my models (Acl in this case). I haven't figured out why this is yet. I do see that this code was re-written between 1.8.3 and 1.8.4. I'm trying to isolate the query that causes this now.
Stack trace snippet:
Error: undefined method `object' for #<Acl:0x00007fa3cb076298>
Did you mean? object_id. Stack trace follows:
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activemodel-5.2.0/lib/active_model/attribute_methods.rb:430:in `method_missing'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema/field.rb:309:in `block in resolve_field'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema.rb:1004:in `after_lazy'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema/field.rb:306:in `resolve_field'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema/field.rb:314:in `call'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema/field.rb:314:in `block in resolve_field'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema.rb:1004:in `after_lazy'
/Users/blevine/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/graphql-1.8.4/lib/graphql/schema/field.rb:306:in `resolve_field'
So far, I've been able to determine that the following query results in this error:
query PageQuery {
page(id: "c1619480-7b9f-11e7-9198-005056be3680") {
acls {
id
}
}
}
and that the error only occurs when the 'id' field of acls is requested.
acls is defined in an interface inherited by PageType:
field :acls, [ACLType], null: false
ACLType is defined like so:
module Types
class ACLType < BaseObject
implements ::GraphQL::Relay::Node.interface
graphql_name 'ACL'
description 'An ACL'
field :created_at, DateTimeType, null: false
field :updated_at, DateTimeType, null: false
field :right, String, null: false
field :allow, Boolean, null: false
field :role_name, String, null: false
field :authorizable, AbNode, null: true
def authorizable(**args)
user = @context[:user] || User.current_user
n = Node.find_by_id(@object.authorizable_id)
n.viewable?(user) ? n : nil
end
end
end
So the fact that this query blows up only when the 'id' field of ACLType is requested and that the 'id' field comes from ::GraphQL::Relay::Node.interface seems suspicious.
This problem appears to have been introduced in b553d6b3055b2313d5a9a2847fcb52658bf16362. Still debugging.
Additional notes: In order to do role-based white/black-listing of fields in the schema, I have class called RoleBasedField. This is declared in my BaseObject class like so:
module Types
class BaseObject < ::GraphQL::Schema::Object
field_class Suede::GraphQL::Types::RolebasedField
I don't know whether this fact is significant or not.
In ACLType, if I don't implement GraphQL::Relay::Node.interface and just declare the 'id' field explicitly, everything works fine. So the use of GraphQL::Relay::Node.interface is significant here.
Hmmm thanks for all the details here! It seems like somehow, GraphQL-Ruby is expecting to find an instance of Types::ACLType, whose object returns the ACL instance, but instead, it's getting the ACL instance directly. I think this is a symptom of some error in GraphQL-Ruby, where that wrapper object is removed when it shouldn't be.
One way to figure out what's going wrong is to try to replicate the problem. Based on your description above, I tried to reproduce the part of the schema that demonstrates this bug:
require "graphql"
require "ostruct"
puts GraphQL::VERSION
class Schema < GraphQL::Schema
class BaseObject < GraphQL::Schema::Object
end
module BaseInterface
include GraphQL::Schema::Interface
end
class OtherItem < BaseObject
implements ::GraphQL::Relay::Node.interface
end
module HasOtherItems
include GraphQL::Schema::Interface
field :other_items, [OtherItem], null: false
end
class Item < BaseObject
implements HasOtherItems
end
class Query < BaseObject
field :item, Item, null: false
def item
OpenStruct.new(other_items: [OpenStruct.new(id: 1)])
end
end
query(Query)
end
p Schema.execute(<<-GRAPHQL).to_h
{
item {
otherItems {
id
}
}
}
GRAPHQL
But, it works fine:
$ ruby test.rb
1.8.5
{"data"=>{"item"=>{"otherItems"=>[{"id"=>"1"}]}}}
So, something must be missing 馃 Here are some thoughts:
I debugged a bit further and the problem appears to be related to the fact that GraphQL::Relay::Node.interface returns GraphQL::Types::Relay::Node.graphql_definition. So ACLType is actually implementing GraphQL::Types::Relay::Node.graphql_definition. If I make ACLType directly implement GraphQL::Types::Relay::Node.graphql_definition, I get the same error (not surprisingly). However, if I make ACLType directly implement GraphQL::Types::Relay::Node, everything works fine.
Could this have anything to do with your CachedGraphQLDefinition mechanism?
Also, GraphQL::Types::Relay::Node.graphql_definition returns a GraphQL::InterfaceType whereas GraphQL::Types::Relay::Node is a Module. The code for the implements method takes a different path depending on whether you're implementing a GraphQL::InterfaceType or a Module.
Ohhhh maybe it happened when I added Types::Relay::Node, that's a recent addition. But I'm not sure _why_ that would cause it 馃
I had the same problem while converting a codebase to the new API. Managed to sort it by implementing GraphQL::Types::Relay::Node, rather than implementing GraphQL::Relay::Node.interface. This new one could do with a mention in the docs - I didn't see it anywhere.
Thanks @jonleighton! :+1:
I think I found the cause of the problem. You must include the global_id_field either on an interface or directly on the type. I found that when I tried to implement GraphQL::Types::Relay::Node it didn't generate the node ID I was expecting. But when implementing GraphQL::Relay::Node.interface with global_id_field everything worked as expected.
I think this is similar to the issue I encountered in #1634. Switching to the newer node type helps but it's unfortunate that you can't define an base id method in your BaseType (instead you must call global_id_field in every class).
Same issue while upgrading a internal github app =)
I have the same problem, but we're not using Relay/Nodes at all. We do use graphql-batch, however, but not in the query I'm debugging currently.
graphql 1.8.17
graphql-batch 0.4.0
In my case, resolving any field other than __typename breaks. The type is wrapped in a list.
Same symptom: The value that is passed as obj to the resolver is my DB record instance.
Schema, mutation root, mutation, payload_type of the mutation are all new types. Do I have to convert every type in the entire schema over to make this work, maybe?
Small update: After a week of porting (was was already 6 days into it when I reported the problem in the previous comment), the entire API has been converted to the new style and the error went away.
It was really scary spending so much time porting the API when I didn't know if it would work in the end or not. Maybe my success story could encourage other people that have the same issue.
Thanks for sharing your workarounds here! Sorry about the hangups in migrating 馃槚
Most helpful comment
I think I found the cause of the problem. You must include the
global_id_fieldeither on an interface or directly on the type. I found that when I tried to implementGraphQL::Types::Relay::Nodeit didn't generate the node ID I was expecting. But when implementingGraphQL::Relay::Node.interfacewithglobal_id_fieldeverything worked as expected.