Graphql-ruby: undefined method `object' for ... error in GraphQL::Schema::Field introduced in version 1.8.4 of gem

Created on 19 Jul 2018  路  14Comments  路  Source: rmosolgo/graphql-ruby

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'

Most helpful comment

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.

All 14 comments

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:

  • Are you using any plugins like GraphQL-Batch?
  • Are there any other customizations that could be added to that replication code?

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 馃槚

Was this page helpful?
0 / 5 - 0 ratings