Skip to content

node and nodes scalar type configuration #5381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

crpahl
Copy link

@crpahl crpahl commented Jun 9, 2025

PR Description

This PR makes it possible to configure the node and nodes scalar type through the Configuration class.

For example, you could do this in a Rails initializer:

GraphQL::Configuration.relay_node_id_type = "SomeScalar"

And the arguments to node and nodes would be id: SomeScalar! and ids: [SomeScalar!]! respectively. The returned id field would be id: SomeScalar!.

Why is this needed?

We don't use the GraphQL Ruby ID scalar and instead use our own scalar for ID fields. Maybe we should consider going back to the ID scalar, but that's a different discussion/concern for if we ever want to go back in that direction 😄 This PR makes it possible to use our own scalar for the root node and nodes field.

GraphQL Ruby doesn't support this type of configuration?

I ultimately need to be able to set the scalar type at Ruby load time. Here for example:

child_module.field(:id, Configuration.relay_node_id_type, null: false, description: "ID of the object.", resolver_method: :default_global_id)

And setting this in a configuration class was the easiest way for me to do this through a simple Rails initializer. I realize this isn't a pattern that GraphQL Ruby takes for configuration (unless I missed it!) and I'm open to any other approaches!

Other approaches we considered

I realize I could also completely override the modules, but it felt much better to be able to configure/inject the type and save us from awkward monkey patching.

@crpahl
Copy link
Author

crpahl commented Jun 9, 2025

@rmosolgo I wanted to throw this proof of concept together quickly to get your thoughts 🙏 I'm open to any changes you suggest, or whatever you feel makes sense!

@crpahl
Copy link
Author

crpahl commented Jun 9, 2025

Hmm after looking closer at this, maybe the configuration of the id field and argument type should go somewhere in our base connection class with a new id_type class method?

@rmosolgo
Copy link
Owner

I'm open to accepting a customization here, but I don't want a global configuration object. Lots of applications run multiple schemas and I want to make sure that they can be configured separately. Here are a few other APIs I could imagine:

  1. Mixin plus configuration method:

    module HasCustomNodeField 
      extend HasNodeField 
      id_type(Types::CustomID) # your scalar here
    end
    # Something similar for HasCustomNodesField 
  2. Generated module:

    # `HasNodeField.[]` returns a generated module:
    include HasNodeField[id_type: Types::CustomID]
    include HasNodesField[id_type: Types::CustomID]
  3. Inject via method params:

    # Instead of `include ...`:
    HasNodeField.add_field(self, id_type: Types::CustomID)

I'm definitely game to support something like this -- is there one of of those alternatives (or another non-global one) that you'd like to explore?

@crpahl
Copy link
Author

crpahl commented Jun 11, 2025

I like Approach 2! It's new to me and I took a quick stab at it. Is this what you had in mind? I implemented it in a way that both:

  • include GraphQL::Types::Relay::HasNodeField
  • include GraphQL::Types::Relay::HasNodeField[id_type: <some_type>]

will work so it won't be a breaking change. I'd still need to inject it here into the Node interface in some way:

child_module.field(:id, ID, null: false, description: "ID of the object.", resolver_method: :default_global_id)

Do you have suggestions for that too? 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants