Craft composable GraphQL queries using a Ruby DSL.
Parse GraphQL documents into a simple Ruby AST.
Manipulate, inspect and transform GraphQL documents as data, not opaque strings.
in your Gemfile
gem "graphlyte"
Queries can be constructed using the selection-builder DSL. This involves
defining blocks that receive a SelectionBuilder
object and using that to
build a selection. The builder supports a method-missing based API that allows
fields to be named directly for convenience.
# Basic query
query = Graphlyte.query do |q|
q.all_todos do |todo|
# specify fields for your query, using method-missing
todo.id
todo.status
todo.title
# or pass field names as values
todo.select!(:open)
end
end
puts query
produces:
query {
allTodos { id status title open }
}
Send it across the wire
require "rest-client" #or whatever api client you wish
RestClient.post("http://localhost", query.request_body, { "Content-Type" => "application/json"})
extra_fields = Graphlyte.fragment('extraFields', on: "Todo") do |todo|
todo.id
todo.status
end
# pass a block parameter if you want to merge/spread fieldsets or fragments
todo = Graphlyte.fragment('todoFields', on: "Todo") do |f|
f.title
f << extra_fields
end
query = Graphlyte.query do |q|
q.all_todos todo
end
puts query
produces:
query {
allTodos {
...todoFields
}
}
fragment todoFields on Todo {
title
...extraFields
}
fragment extraFields on Todo { id status }
Arguments may be passed as plain Ruby values, and aliases specified
with alias
:
query = Graphlyte.query do |q|
q.User(id: 123).alias("sean") do
_1.id
end
q.User(id: 456).alias("bob") do
_1.id
end
end
puts query
Produces:
query {
sean: User(id: 123) { id }
bob: User(id: 456) { id }
}
Symbols as arguments indicate a named, un-typed variable. Variable type is determined when you pass an argument value to it:
query = Graphlyte.query do |q|
q.all_todos(per_page: :per_page, page: :pages) do
q.status
q.title
end
end
puts query.request_body(per_page: 1, pages: 1)
Produces:
{
"query": "query($perPage: Int!, $pages: Int!) {\n status\n title\n allTodos(perPage: $perPage , page: $pages)\n}",
"variables":{"perPage":1,"pages":1}
}
If the argument value is not a Ruby primitive, or it does not map to the expected type, variables can be defined explicitly:
sean_id = Graphlyte.var('ID', :sean_id)
sean = Graphlyte.fragment("userFields", on: "Query") do |q|
q.User(id: sean_id, &:name)
end
query = Graphlyte.query do |q|
q.all_todos(filter: Graphlyte.var('TodoFilter', :filter)) do |t|
t.status
t.title
end
q << sean
end
puts query.request_body(filter: { ids: [1]}, sean_id: 123)
Produces:
{
"query":"query($filter: TodoFilter, $seanId: ID) {\n allTodos(filter: $filter) { status title}\n ...userFields\n}\n\nfragment userFields on Query {\n User(id: $seanId) { name }\n}",
"variables":{"filter":{"ids":[1]},"seanId":123}
}
The selector class provides path-based modification of queries:
query = Graphlyte.parse(<<~GQL)
query name($projectPath: ID!, $commitSha: String) {
project(fullPath: $projectPath, sha: $commitSha) {
createdAt
pipelines(sha: $commitSha) {
nodes {
status
}
}
}
}
GQL
selector = Graphlyte::Selector.new.
at('project.pipelines.nodes.status', &:remove).
at('project.pipelines.nodes') do |node| node.append do |n|
n.downstream { |ds| ds.nodes(&:active) }
end
end
puts selector.edit(query)
Produces:
query name($projectPath: ID!, $commitSha: String) {
project(fullPath: $projectPath, sha: $commitSha) {
createdAt
pipelines(sha: $commitSha) {
nodes {
downstream {
nodes { active }
}
}
}
}
}
Call Document#variable_references
to list all the used variables. This is useful when you don't know all of the variables that a query expects.
Example:
doc = Graphlyte.parse(<<~GQL)
query Pipelines($projectPath: ID!, $commitSha: String) {
project(fullPath: $projectPath) {
createdAt
pipelines(sha: $commitSha) {
nodes { ...pipelineFields }
}
}
}
mutation StopPipeline($id: ID!) {
stopPipeline(id: $id) {
errors
pipeline { ...pipelineFields }
}
}
fragment pipelineFields on Pipeline {
id
status
}
GQL
puts doc.variable_references['Pipelines'].map(&:variable).inspect
puts doc.variable_references['StopPipeline'].map(&:variable).inspect
Produces:
["projectPath", "commitSha"]
["id"]
There is full parsing support for the GraphQL specification. Call
Graphlyte.parse
to parse GraphQL text documents. See
Graphlyte::Syntax
for definitions of the AST.
This library aims to be a client agnostic implementation for parsing, building, inspecting and manipulating GraphQL documents.
- more documentation
- support schema validation
To run tests, you will need to install a node module to spin up a GraphQL server.
cd fixture && npm i
After installing you should be able to run the tests
rspec