🚀 Ruby DSL for build GraphQL queries by code.
MIT License
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:
query
, mutation
, subscription
) and their features (variable, directives, selection sets, etc.)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 directly
rockets_query = GraphQL::DSL.query {
rockets {
name
}
}.to_gql
puts rockets_query
{
rockets
{
name
}
}
Extend class or module use GraphQL::DSL
module
module SpaceXQueries
extend GraphQL::DSL
# Create constant with GraphQL query
ROCKETS = query {
rockets {
name
}
}.to_gql
end
puts SpaceXQueries::ROCKETS
{
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
{
rockets
{
name
}
}
Include GraphQL::DSL
module to class
class 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
{
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
{
rockets
{
name
}
}
Use string or symbol to specify operation name:
puts GraphQL::DSL.query(:rockets) {
rockets {
name
}
}.to_gql
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
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.
# <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
}
}
# <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
}
}
# __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
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
{
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
{
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
{
class
{
object_id
}
}
To rename field in GraphQL response specify alias in __alias
argument:
puts GraphQL::DSL.query {
company {
name __alias: :businessName
}
}.to_gql
{
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
{
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
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
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
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
{
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
query company($additionalInfo: Boolean)
{
company
{
name
... @include(if: $additionalInfo)
{
revenue
valuation
}
}
}
⚠️ Non-official SpaceX GraphQL API doesn't support any directives therefore examples below will be fail with error.
Choose appropriate notation to define directive:
# (:<name> | "name"), ...
puts GraphQL::DSL.query(:rockets, {}, [ :lowPriority ]) {
rockets {
name
}
}.to_gql
query rockets @lowPriority
{
rockets
{
name
}
}
# 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]
Implement ExecutableDocument#include
to include external operations[Fearure]
Strict validation of any argument[Fearure]
Compact format of GraphQL queries[Improvement]
Overload __inline_fragment
for signature without typeAfter 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:
lib/graphql/dsl/version.rb
filebundle exec rake readme:update
to update README.md
filebundle exec rake release
to create a git tag for the version, push git commits and the created tag,.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.
git checkout -b feature/NewFeature
)git commit -m 'Add some NewFeature'
)git push origin feature/NewFeature
)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.