✨ GraphQL DSL ✨
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
}
}
}
🧩 Supported GraphQL features
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)
⚙️ Installation
Add this line to your application's Gemfile:
gem 'graphql-dsl', '~> 1.0.0'
And then execute bundle install
.
⚡️ Getting Started
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 } }
👀 Documentation
💡 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.
Operations
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.
Anonymous operations
Call correspond GraphQL::DSL
method without any arguments to create anonymous operation:
puts GraphQL::DSL.query {
rockets {
name
}
}.to_gql
{
rockets
{
name
}
}
Named operations
Use string or symbol to specify operation name:
puts GraphQL::DSL.query(:rockets) {
rockets {
name
}
}.to_gql
query rockets
{
rockets
{
name
}
}
Parameterized operations
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.
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
}
}
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
}
}
__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.
Operation's directives
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 Sets
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
}
}
Fields
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 Documents
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
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
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
}
}
}
Directives
⚠️ Non-official SpaceX GraphQL API doesn't support any directives therefore examples below will be fail with error.
Choose appropriate notation to define directive:
Symbol
or String
# (:<name> | "name"), ...
puts GraphQL::DSL.query(:rockets, {}, [ :lowPriority ]) {
rockets {
name
}
}.to_gql
query rockets @lowPriority
{
rockets
{
name
}
}
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
Types for operation variables and fragments may be declared in several ways in GraphQL DSL.
Named Types
Named Type can be declared like a symbol or string, for instance: :Int
, 'Int'
List Types
List Type can be declared like a string only, for instance: '[Int]'
Not Null Types
Not Null Type can be declared like a string or symbol, for instance: :Int!
, 'Int!'
, '[Int!]!'
🚲 Example App
graphql-dsl-example shows how to use GraphQL DSL in Ruby applications.
🛣 Roadmap
-
[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
💻 Development
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.
👍 Contributing
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
📜 License
Distributed under the MIT License. See LICENSE
for more information.
🥰 Code of Conduct
Everyone interacting in the GraphQL DSL project's codebases and issue trackers is expected to follow the code of conduct.
📚 Resources
- Introduction to GraphQL
- GraphQL Specification
- Inspired by gqli.rb gem