Graphql-ruby: Custom field class that uses `extras`, incompatible with `GraphQL::Relay::Node.field` as specified by `field:`?

Created on 23 Aug 2019  路  7Comments  路  Source: rmosolgo/graphql-ruby

Is there a way to customize all fields with custom extras for use with an extension? The recommended way in the guide conflicts with anyone using the field class option such as field :node, field: GraphQL::Relay::Node.field.

It also seems that even if someone is using add_field(NodeField) theres no way to set its extras.

class FieldWithExtensionAndExtras < GraphQL::Schema::Field
  def initialize(*args, **kwargs, &block)
    kwargs = kwargs.merge(extensions: [MyExtension])

    kwargs[:extras] ||= kwargs[:mutation]&.extras || []
    kwargs[:extras] << :ast_node # 馃挘

    super(*args, **kwargs, &block)
  end
end

class BaseObject < GraphQL::Schema::Object
  field_class FieldWithExtensionAndExtras
end

class Comment < BaseObject
  implements GraphQL::Relay::Node.interface
  description "A blog comment"

  field :id, ID, null: false
  field :body, String, null: false
end

class QueryRoot < BaseObject
  # NOTE: The following field definition, using `field:` errors out with:
  #   `ArgumentError: keyword `extras:` may only be used with method-based resolve and class-based field such as
  #   mutation class, please remove `field:`, `function:` or `resolve:``
  field :node, field: GraphQL::Relay::Node.field # 馃挜

  field :comments, [Comment], null: false
end

class Schema < GraphQL::Schema
  query QueryRoot
  use GraphQL::Analysis::AST
end

Most helpful comment

Oh, you're using it just for the Node field, I see. Somehow I got the impression that there was more to it than that.

add an #extras method to set extras after the field is instantiated

Yeah, that seems fine!

All 7 comments

Interesting! How about skipping the extras addition if kwargs[:field] is present? For example:

class FieldWithExtensionAndExtras < GraphQL::Schema::Field
  def initialize(*args, **kwargs, &block)
    kwargs = kwargs.merge(extensions: [MyExtension])
    if !kwargs[:field]
      kwargs[:extras] ||= kwargs[:mutation]&.extras || []
      kwargs[:extras] << :ast_node # 馃挘
    end 

    super(*args, **kwargs, &block)
  end
end

That would at least side-step it in this case! For a more fine-grained approach, you could add another initialization option to skip the extras, and pass the option when adding Node.field. What do you think?

Ideally we should be able to customize every field without skipping any like the above case. Skipping the legacy Node.field case seems fine, but I don't think there's any way to customize a field added by add_field either unless we're missing something.

We can't override add_field on object types and set extras because GraphQL::Schema::Field doesn't expose it like it does with extensions. So I think one solution is to forget about the Node.field case and add an #extras method to set extras after the field is instantiated.

Is this more than an edge case? Why use add_field?

Isn't that the recommended solution to add the Node field now? It's used throughout the codebase when the new interpreter is used.

Oh, you're using it just for the Node field, I see. Somehow I got the impression that there was more to it than that.

add an #extras method to set extras after the field is instantiated

Yeah, that seems fine!

I'd like to have a go at making this change :)

One small issue so far:

add an #extras method to set extras after the field is instantiated.

GraphQL::Schema::Field already has:

# @return [Array<Symbol>]
attr_reader :extras

Any opinions on whether we prefer to leave that attr_reader alone and sprout a new method to add extras to an instantiated field (add_extras?), or do we prefer to make extras behave similar to extensions?

def extras(new_extras = nil)
  if new_extras.nil?
    # Read the value
    @extras # returns [Array<Symbol>]
  else
    # ... @extras << ... etc.

similar to extensions

:+1:

Was this page helpful?
0 / 5 - 0 ratings