rmosolgo / graphql-ruby

Ruby implementation of GraphQL
http://graphql-ruby.org
MIT License
5.38k stars 1.39k forks source link

Shorthand notation #666

Closed aldofunes closed 7 years ago

aldofunes commented 7 years ago

This is more of a feature request than an issue. I have extensively used GraphQL in Node, specifically, Apollo server, which comes with shorthand notation support. I have just found this gem (pun intended), and am starting to miss shorthand notation.

Is there a particular reason why it is not supported?

Is it in the roadmap?

Have I missed something from the docs?

rmosolgo commented 7 years ago

What do you mean by "shorthand notation" ?

aldofunes commented 7 years ago

Oh, it's a way (at least using Apollo server) to generate a schema using JS string templates in one place and Resolvers in a different file. It kinda makes everything more convenient since declaring types and fields is very straightforward and it is possible to put resolvers in another place, modularisation and maintenance becomes very easy with large projects.

The makeExecutableSchema function in the npm package graphql-tools allows you to merge these files and build a GraphQL schema.

Shorthand cheatsheet

Let's say we have a lib/schema.js file ... it would look like this. Note the backticks

export default `
type Author {
  id: Int! # the ! means that every author object _must_ have an id
  firstName: String
  lastName: String
  posts: [Post] # the list of Posts by this author
}

type Post {
  id: Int!
  title: String
  votes: Int
  author: Author
}

# the schema allows the following query:
type Query {
  posts: [Post]
  author(id: Int!): Author # author query must receive an id as argument
}

# this schema allows the following mutation:
type Mutation {
  upvotePost (
    postId: Int!
  ): Post
}

# we need to tell the server which types represent the root query
# and root mutation types. We call them RootQuery and RootMutation by convention.
schema {
  query: Query
  mutation: Mutation
}
`;

Or we can put parts of it in different areas and export arrays to separate bits and pieces

This was taken from the Apollo server docs

rmosolgo commented 7 years ago

Ohh I see, thank you for clarifying! This is supported for simple use cases: http://rmosolgo.github.io/blog/2017/03/17/prototyping-a-graphql-schema-from-definition-with-ruby/

But a lot features depend on DSL methods, so it's limited! If you give a try, I'd love to hear how it plays out for you, or if anything in particular is missing.

aldofunes commented 7 years ago

Thank you, it worked wonderfully. Mine is definitely not a simple use case. It involves around 200 types with ≈10 mutations each. That is why code separation is a must. For the sake of modularisation, we put all stuff of each type inside a single file and then merge everything. It is best shown with a simple example.

Let's say we have a type User in graphql/types/user.rb

USER_SCHEMA = '
type User {
  id: ID!
  email: String!
  firstName: String
  lastName: String
  createdAt: String
  roles: [Role]
}
'

USER_QUERIES = '
myUser: User
'

USER_RESOLVERS = {
  'Query' => {
    'myUser' => -> (root, args, context) { context[:current_user] },
  },
  'User' => {
    'id' => -> (root, args, context) { root[:id] },
    'email' => -> (root, args, context) { root[:email] },
    'firstName' => -> (root, args, context) { root[:first_name] },
    'lastName' => -> (root, args, context) { root[:last_name] },
    'createdAt' => -> (root, args, context) { root[:created_at] },
    'roles' => -> (root, args, context) { root.roles },
  },
}

Then, in the _schema.rb file, we import this one and other types and merge them in a single schema, like such:

require 'types/company'
require 'types/user'
require 'types/admin/role'
require 'types/finance/entity'

definition = "
schema {
  query: Query
}

type Query {
  #{COMPANY_QUERIES}
  #{USER_QUERIES}
  #{ROLE_QUERIES}
  #{ENTITY_QUERIES}
}

#{COMPANY_SCHEMA}
#{USER_SCHEMA}
#{ROLE_SCHEMA}
#{ENTITY_SCHEMA}
"

# Use deep_merge to combine all resolvers into one large hash
# We do it this way to keep resolvers manageable due to quentity
default_resolve = [
  COMPANY_RESOLVERS,
  USER_RESOLVERS,
  ROLE_RESOLVERS,
  ENTITY_RESOLVERS,
].inject(:deep_merge)

AppSchema = GraphQL::Schema.from_definition(definition, default_resolve: default_resolve)

I purposefully omitted any mutations for the sake of brevity, but you get the idea. I hope this helps other developers.

ryannealmes commented 7 years ago

I was interested in this functionality too and I thought I would give my 2 cents.

Not having this functionality is almost a deal breaker for me. Having tested out the javascript reference implementation (which is very similar to graphql-ruby) and moving over to something more streamlined like apollo's implementation, the ability to use the shorthand notation makes things more readable, manageable and quicker dev time. I could never go back.

Since our team uses rails I thought I would look at this repo to see if I could bring the learning curve down so they didn't need to get into the javascript world. It would be great if I could just copy all my shorthand graphql schema files across and just fill in the resolvers somewhere else.

I feel like the shorthand notation should be the default for graphql-ruby. It will definitely increase uptake. I found people moved away from the reference implementation to apollo because it's so verbose and unnecessarily complicated.

Anyway, love that there is a ruby version of graphql and looking forward to seeing many more enhancements in the future.

rmosolgo commented 7 years ago

Thanks for speaking up! We're discussing some other ideas at #727 as well.

shorthand notation should be the default for graphql-ruby

I agree! But I want to keep backwards compatibility in mind so that current user can migrate in a manageable way. 🔜 :)