rmosolgo/graphql-ruby

Can't use fields with the name object/context, reserved terms?

ioev opened this issue · 3 comments

ioev commented

Describe the bug

I have a type that is using an "object" and "context" field and it ends up returning the graphql object/context instead. Seems related to #1320 but I can't figure out how to make any of the recommended fixes work for me.

Versions

graphql version: 2.3.5
rails : 7.0.8

GraphQL schema

module Types
  class Permission < SecureObject
    description "A permission"

    field :id, Integer
    field :description, String
    field :object, String, method: :permission_object
    field :action, String
    field :context, String, method: :permission_context
  end
end

Steps to reproduce

Query for this type along with the object/context fields.

Expected behavior

It should return the object/context properties, as defined in permission_object/permission_context.

Actual behavior

I get back a response that looks like:

"description":"Read all asset"
"object":"#<Permission:0x00007f2129c68238>"
"action":"read"
"context":"#<GraphQL::Query::Context:0x00007f2129d73650>"
"__typename":"Permission"
}

Additional context

Even though I have added method: :permission_object it doesn't matter if I define this method or not because it seems to be ignored. Are reserved keywords such as object/context completely off limits now? I'd prefer to not have to change my schema to work around this if possible.

Hey! Sorry for the trouble. Was this working in a previous GraphQL-Ruby version? I would expect it to have not worked for a long time ...

I wrote up a little replication script and it emitted this warning:

Thing's `field :context` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_context` and `def resolve_context`). Or use `method_conflict_warning: false` to suppress this warning.

and:

Thing's `field :object` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_object` and `def resolve_object`). Or use `method_conflict_warning: false` to suppress this warning.

Does your app emit those warnings when it boots? (If not, I may need to check the code and make sure it's working right...)

The solution mentioned in those warnings works for me (using resolver_method: ... to pick a new method name, then implementing the method to make the calls). Here's a script using that approach:

require "bundler/inline"

gemfile do
  gem "graphql", "2.3.5"
end

class MySchema < GraphQL::Schema
  class Thing < GraphQL::Schema::Object
    field :context, String, resolver_method: :resolve_context

    def resolve_context
      object.context
    end

    field :object, String, resolver_method: :resolve_object

    def resolve_object
      object.object
    end
  end

  class Query < GraphQL::Schema::Object
    field :thing, Thing

    def thing
      OpenStruct.new(context: "This is the context", object: "This is the object")
    end
  end

  query(Query)
end

query_str = "{ thing { object context } }"

pp MySchema.execute(query_str).to_h
# {"data"=>{"thing"=>{"object"=>"This is the object", "context"=>"This is the context"}}}

What do you think of that approach?

ioev commented

Thank you for the quick reply! This is new functionality that was added while using graphql 2.3.0, though I upgraded to 2.3.5 to make sure there wasn't a fix I was missing.

I took a closer look at the log files (this app is quite noisy) and did find the warning that you mentioned. It doesn't display on boot, but displays alongside one of 12 or so queries that are resolved, so I ended up not noticing it:

web         | Permission's `field :context` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_context` and `def resolve_context`). Or use `method_conflict_warning: false` to suppress this warning.

Using resolver_method: also corrects the issue, and I can use the field names that I want here:

module Types
  class Permission < SecureObject
    description "A permission"

    field :id, Integer
    field :description, String
    field :object, String, resolver_method: :permission_object
    field :action, String
    field :context, String, resolver_method: :permission_context

    def permission_object
      object.object
    end

    def permission_context
      object.context
    end
  end
end

I think my biggest sticking point was that I got caught up trying to use method: to override here (which we've used elsewhere in a similar way) rather than resolver_method:, which I didn't know existed at the time. I've since realized that this specific use case is even documented on https://graphql-ruby.org/fields/introduction.

I wonder if since these 2 methods are so similar in usage/name, could they be combined into a single method? I'm guessing there must have been a reason to not just allow something like:

field :context, String, method: :permission_context

In any case, thanks again for your help!

Glad it worked for you!

method and resolver_method are very similar, but they were intentionally separated back in #1961. I don't remember the details but the concerns there would have to be addressed in order to re-merge them in some way... And I don't plan to wade back in on that 😅