Skip to content
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

Circular dependencies in Class Based API without rails autoloading #1929

Closed
wSnarski opened this issue Oct 29, 2018 · 4 comments
Closed

Circular dependencies in Class Based API without rails autoloading #1929

wSnarski opened this issue Oct 29, 2018 · 4 comments

Comments

@wSnarski
Copy link

I am either missing something or it does not appear to be possible to set up circularly referencing objects with the class based API if you're not using the rails autoloader. The example in the introductory blog post

http://rmosolgo.github.io/blog/2018/03/25/why-a-new-schema-definition-api/

# Let's assume that `Post` is loaded first.
# app/graphql/types/post.rb
module Types                                  # 1, evaluation starts here
  class Post < BaseObject                     # 2, and naturally flows here, constant `Types::Post` is initialized as a class extending BaseObject
    field :author, Types::User, null: false   # 3, but when evaluating `Types::User`, jumps down below
  end                                         # 9, execution resumes here after loading `Types::User`
end                                           # 10
# app/graphql/types/user.rb
module Types                                  # 4, Rails opens this file looking for `Types::User`
  class User < BaseObject                     # 5, constant `Types::User` is initialized
    field :posts, [Types::Post], null: false  # 6, this line finishes without jumping, because `Types::Post` is _already_ initialized (see `# 2` above)
  end                                         # 7
end                                           # 8

only works because the rails autoloader kicks in on Module#const_missing at step 3. If we were to manually require in the files, we would have the following execution order.

# Let's assume that `Post` is loaded first.
# app/graphql/types/post.rb
require 'graphql/types/user'                  #1, evaluation starts here

module Types                                  
  class Post < BaseObject
   field :author, Types::User, null: false
  end                    
end         
# app/graphql/types/user.rb
require 'graphql/types/post'                   #2, evaluation jumps to the user file, the require for types/post returns false as we are in the process of loading it

module Types                                   #3 the Types constant is created                          
  class User < BaseObject                      #4 the User constant is created      
    field :posts, [Types::Post], null: false   #5 we blow up, the Types::Post constant is not found 
  end                                    
end                        

This can be seen in the stack trace you get running the above code

NameError: uninitialized constant Types::Post
....types/user.rb:5:in `<class:User>'
.../types/user.rb:4:in `<module:Types>'
.../types/user.rb:3:in `<top (required)>'
.../types/post.rb:1:in `<top (required)>'
.../types/query_type.rb:6:in `<top (required)>'

(Ruby 2.4.3, grapqhl 1.8.11)

Is the rails autoloader a hard dependency of the new schema definition API or is there a different way to solve this problem?

@rmosolgo
Copy link
Owner

Hi, good question!

It's not well documented but you can also use strings in cases like this, for example:

field :author, "Types::User", null: false

Here's a little runable example:

require "graphql"

class Query < GraphQL::Schema::Object
  field :a, "TypeA", null: false
end

class TypeA < GraphQL::Schema::Object
  field :b, "TypeB", null: false
end

class TypeB < GraphQL::Schema::Object
  field :a, "TypeA", null: false
end

class Schema < GraphQL::Schema
  query(Query)
end

root = OpenStruct.new(a: OpenStruct.new(b: OpenStruct.new(a: 1)))
puts Schema.execute("{ a { b { a { __typename } } } }", root_value: root ).to_h
{"data"=>{"a"=>{"b"=>{"a"=>{"__typename"=>"TypeA"}}}}}

Would using strings work in your case?

@wSnarski
Copy link
Author

Hey, that works perfectly!

Thanks

@vcc-LG
Copy link

vcc-LG commented Apr 25, 2022

I'm encountering this issue with circular dependencies, but my field returns an array instead of a single object:

class TypeA < GraphQL::Schema::Object
  field :b, [TypeB], null: false
end

Surrounding the type class in quotes does not resolve the issue, e.g.:

class TypeA < GraphQL::Schema::Object
  field :b, ["TypeB"], null: false
end

or

class TypeA < GraphQL::Schema::Object
  field :b, "[TypeB]", null: false
end

Are there any recommendations on how to overcome this issue in this particular case?

@rmosolgo
Copy link
Owner

I would expect wrapping the whole thing in a string ("[TypeB]") to work, for example:

require "graphql"

class Query < GraphQL::Schema::Object
  field :a, "[TypeA]", null: false
end

class TypeA < GraphQL::Schema::Object
  field :b, "[TypeB]", null: false
end

class TypeB < GraphQL::Schema::Object
  field :a, "[TypeA]", null: false
end

class Schema < GraphQL::Schema
  query(Query)
end

root = OpenStruct.new(a: [OpenStruct.new(b: [OpenStruct.new(a: [1])])])
puts Schema.execute("{ a { b { a { __typename } } } }", root_value: root ).to_h
# {"data"=>{"a"=>[{"b"=>[{"a"=>[{"__typename"=>"TypeA"}]}]}]}}

If that's not working for you, please open a new issue with more details about your situation and the error you encountered.

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

No branches or pull requests

3 participants