Graphql-ruby: Circular dependencies in Class Based API without rails autoloading

Created on 29 Oct 2018  路  2Comments  路  Source: rmosolgo/graphql-ruby

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?

Most helpful comment

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?

All 2 comments

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?

Hey, that works perfectly!

Thanks

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ecomuere picture ecomuere  路  3Comments

pareeohnos picture pareeohnos  路  3Comments

peterphan1996 picture peterphan1996  路  3Comments

amozoss picture amozoss  路  3Comments

rylanc picture rylanc  路  3Comments