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_gql
query 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::DSL
module directlyrockets_query = GraphQL::DSL.query { rockets { name } }.to_gql puts rockets_query
STDOUT
{ rockets { name } }
-
Extend class or module use
GraphQL::DSL
modulemodule 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::DSL
module 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_gql
STDOUT
{
rockets
{
name
}
}
Use string or symbol to specify operation name:
puts GraphQL::DSL.query(:rockets) {
rockets {
name
}
}.to_gql
STDOUT
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_gql
STDOUT
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_gql
query 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_gql
query 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_gql
query 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_gql
STDOUT
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_gql
STDOUT
{
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_gql
STDOUT
{
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_gql
STDOUT
{
class
{
object_id
}
}
To rename field in GraphQL response specify alias in __alias
argument:
puts GraphQL::DSL.query {
company {
name __alias: :businessName
}
}.to_gql
STDOUT
{
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_gql
STDOUT
{
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_gql
STDOUT
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_gql
STDOUT
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_gql
STDOUT
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_gql
STDOUT
{
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_gql
STDOUT
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_gql
query 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_gql
query 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#include
to include external operations -
[Fearure]
Strict validation of any argument -
[Fearure]
Compact format of GraphQL queries -
[Improvement]
Overload__inline_fragment
for 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.rb
file - run
bundle exec rake readme:update
to updateREADME.md
file - run
bundle exec rake release
to create a git tag for the version, push git commits and the created tag, and push the.gem
file 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