Load order for interfaces implementing interfaces
jacquesn opened this issue · 6 comments
Describe the bug
If Obect A implements Interface A, which implements interface B, which has a field that returns Object A, Object A will have the field but not the interface.
Versions
graphql
version: 2.3.14
rails
(or other framework): 7.1.3.4
GraphQL schema
module Types
class ObjectA < Types::BaseObject
implements InterfaceA
end
end
module Types
module InterfaceA
include Types::BaseInterface
implements InterfaceB
end
end
module Types
module InterfaceB
include Types::BaseInterface
field :my_field, ObjectA, null: false
end
end
Steps to reproduce
Make a new Rails app with the graphql gem and add the above types.
Expected behavior
Types::ObjectA.interfaces # => [Types::InterfaceB, Types::InterfaceA]
Actual behavior
Types::ObjectA.interfaces # => [Types::InterfaceA]
Additional context
Interestingly, even though ObjectA
doesn't have Types::InterfaceB
in its interfaces, it does have myField
in its fields:
Types::ObjectA.fields.pluck(0) # => ["myField"]
Since the field is still there, it's perhaps not a huge problem, but it does break things when used with graphiql, resulting in an error that looks like this:
Type ObjectA must implement InterfaceB because it is implemented by InterfaceA.
so that graphiql does not work.
This error only happens because my_field
returns ObjectA
. If you change ObjectA
to String
, it works as expected.
Hey, thanks for the detailed write up ... I wonder how this can be made to work nicely with Rails.
For work-arounds, we have a couple of options:
-
Use a string for
ObjectA
in the field definition:field :my_field, "Types::ObjectA", null: false
-
Move
type(ObjectA)
in to ado ... end
block forfield :my_field
:field :my_field, null: false do type(ObjectA) end
I think one or both of those will let GraphQL-Ruby defer its reference to ObjectA
, allowing Rails's autoloader to do its job better. Could you give those a try and let me know what you find?
Thanks for the quick response!
Oh cool—I didn't know either of those were options.
The string option works.
The block option seems to work at first, setting the interfaces correctly, but
Types::ObjectA.fields
# => undefined method `field' for an instance of Types::BaseField
I'll be going with the string option. That works nicely for me, and I'm glad to have a quick solution that didn't even need a change to the gem!
I'm glad that first one works for you. I'd really like to fix that error you encountered when moving the call to the block. Would you mind sharing the full backtrace for it?
Sure:
["/app/graphql/types/interface_b.rb:15:in `block in <module:InterfaceB>'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/field.rb:366:in `instance_eval'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/field.rb:366:in `ensure_loaded'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/member/has_fields.rb:164:in `block (2 levels) in fields'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/member/has_fields.rb:160:in `each'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/member/has_fields.rb:160:in `block in fields'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/member/has_fields.rb:158:in `each'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/graphql-2.3.14/lib/graphql/schema/member/has_fields.rb:158:in `fields'",
"(irb):1:in `<main>'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb/workspace.rb:121:in `eval'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb/workspace.rb:121:in `evaluate'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14
.0/lib/irb/context.rb:633:in `evaluate_expression'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb/context.rb:601:in `evaluate'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1049:in `block (2 levels) in eval_input'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1388:in `signal_status'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1041:in `block in eval_input'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1120:in `block in each_top_level_statement'",
"<internal:kernel>:187:in `loop'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1117:in `each_top_level_statement'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1040:in `eval_input'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1021:in `block in run'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1020:in `catch'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:1020:in `run'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/irb-1.14.0/lib/irb.rb:904:in `start'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/commands/console/console_command.rb:78:in `start'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/commands/console/console_command.rb:16:in `start'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/commands/console/console_command.rb:106:in `perform'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/thor-1.3.1/lib/thor/command.rb:28:in `run'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/thor-1.3.1/lib/thor/invocation.rb:127:in `invoke_command'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/command/base.rb:178:in `invoke_command'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/thor-1.3.1/lib/thor.rb:527:in `dispatch'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/command/base.rb:73:in `perform'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/command.rb:71:in `block in invoke'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/command.rb:149:in `with_argv'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/command.rb:69:in `invoke'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/railties-7.1.3.4/lib/rails/commands.rb:18:in `<main>'",
"/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `require'",
"/.rbenv/versions/3.3.1/lib/ruby/3.3.0/bundled_gems.rb:74:in `block (2 levels) in replace_require'",
"/.rbenv/versions/3.3.1/lib/ruby/gems/3.3.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'",
"bin/rails:4:in `<main>'"]
Derp, my bad. It's a typo in my original suggestion. It should be type(...)
inside the block, not field(...)
. I'll update the comment above accordingly:
- field(ObjectA)
+ type(ObjectA)
Thanks for sharing that, and please let me know if you run into any other trouble!
Oh—haha. Yes, that works for me now.