Graphql-ruby: Does plan to support Relay mutation/connection in new class-based API?

Created on 1 Dec 2017  路  19Comments  路  Source: rmosolgo/graphql-ruby

I didn't found it in the repo yet. I tried to mix old Relay mutation types and others, but it can't work.

All 19 comments

Could you share an example error from Relay mutations, perhaps with a snippet of source code?

As for connections, sorry it's not well documented, but connections are applied by convention:

  • If the field's return type name ends in Connection, the field is treated like a connection (this replaces the old connection ... method
  • This default behavior can be overridden by connection: kwarg

Determine if a field is a connection:

https://github.com/rmosolgo/graphql-ruby/blob/c39c352e7e2e68887b02e9e0619d7c7846153b33/lib/graphql/schema/field.rb#L53-L64

Apply the determined value:

https://github.com/rmosolgo/graphql-ruby/blob/c39c352e7e2e68887b02e9e0619d7c7846153b33/lib/graphql/schema/field.rb#L81

Let's keep this open until the docs are improved.

The following is a snippet:

Mutations::Shared::Create = GraphQL::Relay::Mutation.define do
  name "CreateShared"
  return_field :result, Types::SharedType
  return_field :errors, Types::ErrorsType

  input_field :title, !types.String
  input_field :description, types.String
  input_field :cover, !Types::FileType     # <------ ERROR LINE

  resolve ->(obj, args, ctx) {
    # ...
  }
end

Error:

GraphQL::Schema::InvalidTypeError (Argument input on Mutation.createShared is invalid: argument "cover" type must be a valid input type (Scalar or InputObject), not NilClass ()):

Types::FileType is class-based type. When I comment this line, it works fine. So I think maybe just let input_field supports null: false argument.

I'll test connection later.

Yay, connection works fine. 鉁岋笍

Another error (full log at the end):

LoadError (Unable to autoload constant Types::UserType, expected /Users/lijie/backend/app/graphql/types/user_type.rb to define it):

Content of app/graphql/types/user_type.rb:

class Types::UserType < GraphQL::Schema::Object
  graphql_name "User"
  field :id, ID, null: false
  field :email, String, null: false
  field :authentication_token, String, null: false
  field :inactive, String, null: true,
    resolve: -> (user, args, ctx) {
      user.confirmed? ? nil : user.inactive_message
    }

  field :user_info, Types::UserInfoType, null: false
end

UPDATED:
This error caused after I change the ERROR LINE above to:

input_field :cover, Types::FileType, required: true

The content of types/file_type.rb:

class Types::FileType < GraphQL::Schema::Scalar
  graphql_name "File"
end

After I removed required: true, it works.

BTW: This test is refreshing GraphiQL page.
END UPDATED

Full log:

lijie$ bundle exec rails s -p 3876
/Users/lijie/.rvm/gems/ruby-2.4.1/gems/word_salad-2.0.0/lib/word_salad/core_ext.rb:1: warning: constant ::Fixnum is deprecated
DEPRECATION WARNING: The factory_girl gem is deprecated. Please upgrade to factory_bot. See https://github.com/thoughtbot/factory_bot/blob/v4.9.0/UPGRADE_FROM_FACTORY_GIRL.md for further instructions. (called from <top (required)> at /Users/lijie/backend/config/application.rb:7)
=> Booting Puma
=> Rails 5.1.4 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.11.0 (ruby 2.4.1-p111), codename: Love Song
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3876
Use Ctrl-C to stop
Started GET "/graphiql" for ::1 at 2017-12-01 10:38:31 +0800
   (0.5ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by GraphiQL::Rails::EditorsController#show as HTML
  Parameters: {"graphql_path"=>"/graphql"}
  Rendering /Users/lijie/.rvm/gems/ruby-2.4.1/gems/graphiql-rails-1.4.8/app/views/graphiql/rails/editors/show.html.erb
  Rendered /Users/lijie/.rvm/gems/ruby-2.4.1/gems/graphiql-rails-1.4.8/app/views/graphiql/rails/editors/show.html.erb (602.3ms)
Completed 200 OK in 652ms (Views: 635.4ms | ActiveRecord: 0.0ms)


Started POST "/graphql" for ::1 at 2017-12-01 10:38:32 +0800
Processing by GraphqlController#execute as */*
  Parameters: {"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      subscriptionType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n", "graphql"=>{"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      subscriptionType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n"}}
  User Load (0.7ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Completed 500 Internal Server Error in 153ms (ActiveRecord: 2.1ms)



LoadError (Unable to autoload constant Types::UserType, expected /Users/lijie/backend/app/graphql/types/user_type.rb to define it):

app/controllers/graphql_controller.rb:20:in `execute'
Started POST "/graphql" for ::1 at 2017-12-01 10:38:33 +0800
Processing by GraphqlController#execute as */*
  Parameters: {"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n", "graphql"=>{"query"=>"\n  query IntrospectionQuery {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n"}}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Completed 500 Internal Server Error in 8ms (ActiveRecord: 0.3ms)



LoadError (Unable to autoload constant Types::UserType, expected /Users/lijie/backend/app/graphql/types/user_type.rb to define it):

app/controllers/graphql_controller.rb:20:in `execute'

A problem not relatived to Relay.

For those tests, I must restart again and again when some errors cause. Your patch https://github.com/rmosolgo/graphql-ruby/issues/651#issuecomment-292718619 works not good for new API. It can't reload the module after fixed some error, but if remove it, the rails server will hang.

Oh, I see. ! doesn't create non-null types anymore, but I didn't document it 馃槚

I removed the ! API, you can backport it with:

Thanks. Using GraphQL::DeprecatedDSL now.

But I think it deprecated, what is the new way?

Another error:

using GraphQL::DeprecatedDSL

Mutations::Gist::Update = GraphQL::Relay::Mutation.define do
  name "UpdateGist"

  return_field :result, Types::GistType
  return_field :errors, Types::ErrorsType

  input_field :id, !types.ID
  input_field :title, types.String
  input_field :description, types.String
  input_field :public, types.Boolean
  input_field :text_files, types[!Types::TextFileInputType]       #<-- CAUSE ERROR

  resolve ->(_, args, ctx) {
    # ...
  }
end
class Types::TextFileInputType < GraphQL::Schema::InputObject
  graphql_name "TextFileInput"
  argument :path, String, required: false
  argument :data, String, required: false
end

When I comment out that line, no errors again.

And the error is:

LoadError (Unable to autoload constant Types::UserType, expected /Users/lijie/backend/app/graphql/types/user_type.rb to define it):

app/controllers/graphql_controller.rb:20:in `execute'

But Types::UserType hasn't errors.

Woah! That's a weird one. Is there any line in the backtrace that references UserType?

I added docs for GraphQL::DeprecatedDSL in bae8e422

Another alternative is #to_non_null_type, for example:

input_field :cover, !Types::FileType
# could be 
input_field :cover, Types::FileType.to_non_null_type 

Also, added an example connection field in f76062ee33a1ecfff3f62841802369120dab9334 !

Thanks. I met an error when I used Types::FileType.to_non_null_type.to_list_type to instead of types[!Types::FileType], and it worked using Types::FileType.to_list_type, but its GraphQL type is [FileType], I need it [FileType!]. How to distinguish Array of Nullable FileType and Array of Non-nullable FileType? I think this case is common.

UPDATE:
I need [FileType!]! type too, it can deny someone pass null to an array type. Oh, I think we can use required: or null: for this case.

The methods are additive, so maybe changing the order will help, for example:

Types::FileType.to_non_null_type # => FileType!
Types::FileType.to_non_null_type.to_list_type # [FileType!]

Types::FileType.to_list_type # => [FileType]
Types::FileType.to_list_type.to_non_null_type # => [FileType]!

# All together now :P 
Types::FileType.to_non_null_type.to_list_type.to_non_null_type # => [FileType!]!

I used your example but can't works still.

Some new errors:

  • ArgumentError (A copy of Types has been removed from the module tree but is still active!):
  • NoMethodError (undefined method `unwrap' for nil:NilClass):

Both of them cause after server restarted and refresh GraphiQL. I think I should create a repo to reproduce.

_IGNORE ABOVE: (Those errors causes when I used to_non_null_type in class-based definition.)_

Currently the result of some cases (in Relay mutation):

The following works fine:

return_field :files, Types::SourceFileType.to_non_null_type

return_field :files, Types::SourceFileType.to_list_type

input_field :text_files, Types::TextFileInputType.to_non_null_type

input_field :text_files, Types::TextFileInputType.to_list_type

The following causes errors:

LoadError (Unable to autoload constant Types::UserType, expected /Users/lijie/source/applean/applean-api/app/graphql/types/user_type.rb to define it):

_(Ignore UserType because if I comment out some fields about UserType, the error message change to another type)_

return_field :files, Types::SourceFileType.to_non_null_type.to_list_type

return_field :files, Types::SourceFileType.to_non_null_type.to_list_type.to_non_null_type

return_field :files, Types::SourceFileType.to_list_type.to_non_null_type

input_field :text_files, Types::TextFileInputType.to_non_null_type.to_list_type

input_field :text_files, Types::TextFileInputType.to_non_null_type.to_list_type.to_non_null_type

input_field :text_files, Types::TextFileInputType.to_list_type.to_non_null_type

Create a repo https://github.com/cpunion/reproduce-graphql-ruby-mutation-error, currently it always causes the following error and I can't reproduce other errors.

ArgumentError (A copy of Types has been removed from the module tree but is still active!):

app/graphql/reproduce_graphql_ruby_mutation_error_schema.rb:2:in `<class:ReproduceGraphqlRubyMutationErrorSchema>'
app/graphql/reproduce_graphql_ruby_mutation_error_schema.rb:1:in `<top (required)>'
app/controllers/graphql_controller.rb:10:in `execute'

To test it, just open /graphiql and refresh page. Doesn't need call some query.

When I see the "expected QueryType to define it" LoadError, I've had some success opening a Rails console and typing the name of my schema. This gives me a much more specific and helpful error message, for example:

[1] pry(main)> ApiSchema
NameError: uninitialized constant Types::CompanyType
from /Users/zubin/code/rails/app/graphql/resolvers/find_company.rb:6:in `<class:FindCompany>'

That particular error happened because I renamed the class to Types::Company but forgot to update the resolver, but I've found lots of other problems this way too.

I'm going to close this because I don't see any action items left to address, please feel free to open another issue if there's still something to do here!

@rmosolgo unsure if I should reopen or not (the docs are still on the same ; but I'm having hard times understanding how the GraphQL::Relay::Mutation should be used in a class based context - using a class based approach, input_field and return_field are unavailable

May I suggest you to provide a minimal example here ?

GraphQL::Relay::Mutation has been replaced by GraphQL::Schema::RelayClassicMutation, which is a subclass of GraphQL::Schema::Resolver. see

https://graphql-ruby.org/api-doc/1.9.14/GraphQL/Schema/RelayClassicMutation
https://graphql-ruby.org/mutations/mutation_classes
https://graphql-ruby.org/fields/resolvers.html

I've tried to indicate that in the docs:

image

Feel free to use the "Edit" button the website if you'd like to suggest a specific change!

@rmosolgo oh i see, though that "classic" was about a previous version; my bad. thx a lot for your reply;

Was this page helpful?
0 / 5 - 0 ratings