graphql-dsl lets you easy create GraphQL queries by code:
extend GraphQL::DSL # Include DSL methods like `query`, `mutation`, etc. 
using GraphQL::DSL  # Include refine methods like `variable` and `directive` if required.
# Query alive characters from Rick and Morty unofficial GraphQL API:
#   https://rickandmortyapi.com/graphql
puts query(:aliveCharacters, species: variable(:String!, 'Human')) {
  characters(filter: { status: 'Alive', species: :$species }) {
    results {
      name
      image
    }
  }
}.to_gqlquery aliveCharacters($species: String! = "Human")
{
  characters(filter: {status: "Alive", species: $species})
  {
    results
    {
      name
      image
    }
  }
}The GraphQL DSL base on draft version of GraphQL specification (updated at Wed, Sep 15, 2021) and support these features:
- Executable Documents (exclude type system definition documents)
-  All operations (query,mutation,subscription) and their features (variable, directives, selection sets, etc.)
- All field features (aliases, arguments, directives, etc.)
- Fragments (include inline fragments)
Add this line to your application's Gemfile:
gem 'graphql-dsl', '~> 1.0.0'And then execute bundle install.
Choose an appropriate way to use GraphQL DSL:
- 
Call methods of GraphQL::DSLmodule directlyrockets_query = GraphQL::DSL.query { rockets { name } }.to_gql puts rockets_query STDOUT{ rockets { name } }
- 
Extend class or module use GraphQL::DSLmodulemodule SpaceXQueries extend GraphQL::DSL # Create constant with GraphQL query ROCKETS = query { rockets { name } }.to_gql end puts SpaceXQueries::ROCKETS STDOUT{ rockets { name } }module SpaceXQueries extend GraphQL::DSL # `extend self` or `module_function` required to # call of `SpaceXQueries.rockets` extend self # use memorization or lazy initialization # to avoid generation of query on each method call def rockets query { rockets { name } }.to_gql end end puts SpaceXQueries.rockets STDOUT{ rockets { name } }
- 
Include GraphQL::DSLmodule to classclass SpaceXQueries include GraphQL::DSL # use memorization or lazy initialization # to avoid generation of query on each method call def rockets query { rockets { name } }.to_gql end end queries = SpaceXQueries.new puts queries.rockets STDOUT{ rockets { name } }
π‘ Non-official SpaceX GraphQL and Rick and Morty APIs are using for most of examples. So, you can test generated GraphQL queries here and here.
The GraphQL support three types of operations:
- query- for fetch data.
- mutation- for update data.
- subscription- for fetch stream of data during a log time.
To create these operations use correspond GraphQL DSL methods:
- GraphQL::DSL#query
- GraphQL::DSL#mutation
- GraphQL::DSL#subscription
π‘ All of them have the same signatures therefore all examples below will use query operation.
Call correspond GraphQL::DSL method without any arguments to create anonymous operation:
puts GraphQL::DSL.query {
  rockets {
    name
  }
}.to_gqlSTDOUT
{
  rockets
  {
    name
  }
}Use string or symbol to specify operation name:
puts GraphQL::DSL.query(:rockets) {
  rockets {
    name
  }
}.to_gqlSTDOUT
query rockets
{
  rockets
  {
    name
  }
}Pass variable definitions to second argument of correspond GraphQL::DSL method:
using GraphQL::DSL # Include refined `variable` method
puts GraphQL::DSL.query(:capsules, type: :String, status: variable(:String!, 'active')) {
  capsules(find: { type: :$type, status: :$status }) {
    type
    status
    landings
  }
}.to_gqlSTDOUT
query capsules($type: String, $status: String! = "active")
{
  capsules(find: {type: $type, status: $status})
  {
    type
    status
    landings
  }
}Choose appropriate notation to define variable type, default value and directives:
π‘ See more about types definition here.
Use Symbol or String
# <variable name>: <type>, ...
puts GraphQL::DSL.query(:capsules, status: :String!) {
  capsules(find: { status: :$status }) {
    type
    status
    landings
  }
}.to_gqlquery capsules($status: String!)
{
  capsules(find: {status: $status})
  {
    type
    status
    landings
  }
}Use variable refined method
# <variable name>: variable(<type>, [<default value>], [<directives>]), ...
using GraphQL::DSL # Required to refine `variable` method
puts GraphQL::DSL.query(:capsules, status: variable(:String!, 'active')) {
  capsules(find: { status: :$status }) {
    type
    status
    landings
  }
}.to_gqlquery capsules($status: String! = "active")
{
  capsules(find: {status: $status})
  {
    type
    status
    landings
  }
}Use __var method
# __var <variable name>, <type>, [default: <default value>], [directives: <directives>]
puts GraphQL::DSL.query(:capsules) {
   __var :status, :String!, default: "active"
   
  capsules(find: { status: :$status }) {
    type
    status
    landings
  }
}.to_gqlquery capsules($status: String! = "active")
{
  capsules(find: {status: $status})
  {
    type
    status
    landings
  }
}π‘ More information about directives you can find here.
Pass operation's directives to third argument of correspond GraphQL::DSL method:
using GraphQL::DSL # Include refined `variable` and `directive` methods
puts GraphQL::DSL.query(:capsules, { status: variable(:String!, 'active') }, [ directive(:priority, level: :LOW) ]) {
  capsules(find: { status: :$status }) {
    type
    status
    landings
  }
}.to_gqlSTDOUT
query capsules($status: String! = "active") @priority(level: LOW)
{
  capsules(find: {status: $status})
  {
    type
    status
    landings
  }
}π‘ More information about directives you can find here.
Selection Set is a block that contains fields, spread or
internal fragments. Operations (query, mutation, subscription), fragment operations, spread and internal fragments
must have Selection Set for select or update (in case of mutation) data. Even a field can contains Selection Set.
puts GraphQL::DSL.query {    # this is `Selection Set` of query
  company {                  # this is `Selection Set` of `company` field
    name
    ceo
    cto
  }
}.to_gqlSTDOUT
{
  company
  {
    name
    ceo
    cto
  }
}Selection Set should contains one or more fields to select or update (in case of mutation) data.
To create field just declare it name inside of Selection Set block:
puts GraphQL::DSL.query {    
  company {                  # this is `company` field
    name                     # this is `name` fields declared in `Selection Set` of `company` field 
  }
}.to_gqlSTDOUT
{
  company
  {
    name
  }
}As you can see above some fields can have Selection Set and allow to declare sub-fields.
In rare cases will be impossible to declare field in such way because its name can conflict with Ruby's keywords and
methods. In this case you can declare field use __field method:
# __field <name>, [__alias: <alias name>], [__directives: <directives>], [<arguments>]
puts GraphQL::DSL.query {
  __field(:class) {          # `class` is Ruby's keyword
     __field(:object_id)     # `object_id` is `Object` method
  }
}.to_gqlSTDOUT
{
  class
  {
    object_id
  }
}To rename field in GraphQL response specify alias in __alias argument:
puts GraphQL::DSL.query {
  company {
     name __alias: :businessName
  }
}.to_gqlSTDOUT
{
  company
  {
    businessName: name
  }
}Some field can accept arguments and change their data base on them:
puts GraphQL::DSL.query {
  company {
    revenue currency: :RUB   # convert revenue value to Russian Rubles
  }
}.to_gqlSTDOUT
{
  company
  {
    revenue(currency: RUB)
  }
}Any field can have directives. Pass them though __directives argument:
using GraphQL::DSL # Required to refine `directive` method
puts GraphQL::DSL.query(:company, additionalInfo: :Boolean) {
  company {
    name 
    revenue __directives: [ directive(:include, if: :$additionalInfo) ]
  }
}.to_gqlSTDOUT
query company($additionalInfo: Boolean)
{
  company
  {
    name
    revenue @include(if: $additionalInfo)
  }
}Executable Document helps to union several operations or fragments to one request:
puts GraphQL::DSL.executable_document {
  query(:companies) {
    company {
      name
    }
  }
  
  query(:rockets) {
    rockets {
      name
    }
  }
}.to_gqlSTDOUT
query companies
{
  company
  {
    name
  }
}
query rockets
{
  rockets
  {
    name
  }
}Fragments may contains common repeated selections of fields and can be reused in different operations. Each fragment must have a name, type and optional directives.
π‘ See more about type definitions here.
# fragment(<fragment name>, <type>, [<directives>])
fragment(:ship, :Ship) {
  id
  name
}Fragment spread is using to insert fragment to other operations or fragments. Use __frgment command to create fragment
spread and insert fragment by its name.
# __fragment(<fragment name>, [__directives: <directives>])
puts GraphQL::DSL.executable_document {
  query(:cargo_ships) {
    ships(find: { type: "Cargo" }) {
      __fragment :ship
    }
  }
  query(:barges) {
    ships(find: { type: "Barge" }) {
      __fragment :ship
    }
  }
  
  fragment(:ship, :Ship) {
    id
    name
  }
}.to_gqlSTDOUT
query cargo_ships
{
  ships(find: {type: "Cargo"})
  {
    ...ship
  }
}
query barges
{
  ships(find: {type: "Barge"})
  {
    ...ship
  }
}
fragment ship on Ship
{
  id
  name
}Inline fragments helps to define fields from heterogeneous collections
(collections which can contains different types of objects). Use __inline_fragment to insert inline fragment
to operation or fragment.
π‘ See more about type definitions here.
# __inline_fragment([<type>]) { Selections Set }
puts GraphQL::DSL.query {
  messages {
    __inline_fragment(:AdSection) {
      title
      image
    }
    
    __inline_fragment(:MessageSection) {
      title
      message
      author
    }
  }
}.to_gqlSTDOUT
{
  messages
  {
    ... on AdSection
    {
      title
      image
    }
    ... on MessageSection
    {
      title
      message
      author
    }
  }
}Inline fragments may also be used to apply a directive to a group of fields:
using GraphQL::DSL # Required to refine `directive` method
# __inline_fragment([<type>]) { Selections Set }
puts GraphQL::DSL.query(:company, additionalInfo: :Boolean) {
  company {
    name
    
    __inline_fragment(nil, __directives: [ directive(:include, if: :$additionalInfo) ]) {
      revenue
      valuation
    }
  }
}.to_gqlSTDOUT
query company($additionalInfo: Boolean)
{
  company
  {
    name
    ... @include(if: $additionalInfo)
    {
      revenue
      valuation
    }
  }
}Choose appropriate notation to define directive:
Use Symbol or String
# (:<name> | "name"), ...
 
puts GraphQL::DSL.query(:rockets, {}, [ :lowPriority ]) {
   rockets {
      name
   }
}.to_gqlquery rockets @lowPriority
{
  rockets
  {
    name
  }
}Use refined directive method
# directive(<directive name>, [<arguments>]), ...
using GraphQL::DSL # Include refined `directive` method
puts GraphQL::DSL.query(:rockets, {}, [ directive(:lowPriority) ]) {
   rockets {
      name
   }
}.to_gqlquery rockets @lowPriority
{
  rockets
  {
    name
  }
}Types for operation variables and fragments may be declared in several ways in GraphQL DSL.
Named Type can be declared like a symbol or string, for instance: :Int, 'Int'
List Type can be declared like a string only, for instance: '[Int]'
Not Null Type can be declared like a string or symbol, for instance: :Int!, 'Int!', '[Int!]!'
graphql-dsl-example shows how to use GraphQL DSL in Ruby applications.
-  [Fearure]ImplementExecutableDocument#includeto include external operations
-  [Fearure]Strict validation of any argument
-  [Fearure]Compact format of GraphQL queries
-  [Improvement]Overload__inline_fragmentfor signature without type
After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rspec to run the tests.
You can also run bin/console for an interactive prompt that will allow you to experiment.
To release a new version:
- update the version number in lib/graphql/dsl/version.rbfile
- run bundle exec rake readme:updateto updateREADME.mdfile
- run bundle exec rake releaseto create a git tag for the version, push git commits and the created tag, and push the.gemfile to rubygems.org.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
- Fork the Project
- Create your Feature Branch (git checkout -b feature/NewFeature)
- Commit your Changes (git commit -m 'Add some NewFeature')
- Push to the Branch (git push origin feature/NewFeature)
- Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.
Everyone interacting in the GraphQL DSL project's codebases and issue trackers is expected to follow the code of conduct.
- Introduction to GraphQL
- GraphQL Specification
- Inspired by gqli.rb gem