A Ruby implementation of GraphQL for building GraphQL schemas and executing queries.
Query parsing, execution engine, and context management for running GraphQL operations against schemas in GraphQL Ruby.
Parse GraphQL query strings into Abstract Syntax Trees and validate syntax.
# Main parsing interface
def GraphQL.parse(graphql_string, trace: GraphQL::Tracing::NullTrace, filename: nil, max_tokens: nil)
def GraphQL.parse_file(filename)
def GraphQL.scan(graphql_string)
# Language parsing classes
class GraphQL::Language::Parser
def self.parse(string, filename: nil, trace: GraphQL::Tracing::NullTrace)
end
class GraphQL::Language::Lexer
def self.tokenize(graphql_string)
end
# Parse result
class GraphQL::Language::Nodes::Document
def definitions
def to_query_string
endUsage Examples:
# Parse a query string
query_string = '
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
'
document = GraphQL.parse(query_string)
puts document.definitions.first.name # "GetUser"
# Parse from file
document = GraphQL.parse_file("queries/get_user.graphql")
# Tokenize for analysis
tokens = GraphQL.scan(query_string)
tokens.each { |token| puts "#{token[0]}: #{token[1]}" }Represents a parsed GraphQL operation ready for execution with variables and context.
class GraphQL::Query
def initialize(schema, query_string = nil, document: nil, variables: {}, context: {}, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
# Execution
def result
def result_name
# Validation and analysis
def valid?
def static_errors
def analyzers
# Query introspection
def operation_name
def selected_operation
def variables
def context
def root_value
def schema
def document
def query_string
# Utilities
def fingerprint
def sanitized_query_string(inline_variables: false)
endUsage Examples:
# Create and execute a query
schema = MySchema
query_string = 'query { users { id name } }'
query = GraphQL::Query.new(
schema,
query_string,
variables: {},
context: { current_user: current_user },
operation_name: nil
)
# Check if query is valid
unless query.valid?
puts "Query errors:"
query.static_errors.each { |error| puts error.message }
end
# Execute the query
result = query.result
puts result.to_json
# Query introspection
puts "Operation: #{query.operation_name}"
puts "Variables: #{query.variables}"
puts "Fingerprint: #{query.fingerprint}"The result of executing a GraphQL query, containing data and errors.
class GraphQL::Query::Result
# Data access
def [] (key)
def dig(*keys)
def to_h
def to_json(**options)
# Error handling
def context_errors
def execution_errors
# Metadata
def query
def extensions
endUsage Examples:
result = MySchema.execute('query { user(id: "1") { name email } }')
# Access data
user_data = result["data"]["user"]
user_name = result.dig("data", "user", "name")
# Check for errors
if result["errors"]&.any?
puts "GraphQL errors:"
result["errors"].each { |error| puts error["message"] }
end
# Convert to JSON for API response
json_response = result.to_json
# Access extensions (custom metadata)
if result["extensions"]
puts "Query complexity: #{result['extensions']['complexity']}"
endPass request-scoped data through the GraphQL execution pipeline.
class GraphQL::Query::Context
def initialize(query:, schema:, values: {})
# Data access
def [] (key)
def []= (key, value)
def key?(key)
def fetch(key, default = nil)
def delete(key)
def merge(new_values)
def merge!(new_values)
# Namespace support
def namespace(ns_name)
# Query access
def query
def schema
def warden
def interpreter?
# Execution state
def errors
def add_error(error)
def invalid_null?
def skip
def ast_node
def irep_node
def path
endUsage Examples:
# Setting up context in schema execution
result = MySchema.execute(
query_string,
context: {
current_user: current_user,
request: request,
abilities: current_user.abilities,
dataloader: MyDataLoader.new
}
)
# Accessing context in resolvers
class UserType < GraphQL::Schema::Object
field :posts, [PostType], null: true
def posts
current_user = context[:current_user]
abilities = context[:abilities]
# Filter posts based on user permissions
object.posts.accessible_by(abilities)
end
end
# Adding errors to context
def secure_field
unless context[:current_user]&.admin?
context.add_error(GraphQL::ExecutionError.new(
"Access denied",
path: context.path,
extensions: { code: "UNAUTHORIZED" }
))
return nil
end
# Return authorized data
object.secure_data
end
# Using namespaced context
def expensive_computation
cache = context.namespace(:cache)
cache[:computation_result] ||= perform_expensive_operation
endControl query execution behavior including multiplexing, timeouts, and analysis.
class GraphQL::Schema
# Single query execution
def self.execute(query_string, variables: {}, context: {}, operation_name: nil, root_value: nil, only: nil, except: nil, validate: true)
# Multiple query execution (multiplexing)
def self.multiplex(queries, context: {}, max_complexity: nil, validate: true)
# Analysis configuration
def self.query_analyzer(analyzer_class)
def self.analysis_engine(engine = nil)
# Execution hooks
def self.lazy_resolve(lazy_class, method_name)
def self.after_lazy(value, &block)
end
# Multiplex execution
class GraphQL::Execution::Multiplex
def initialize(schema:, queries:, context: {})
def self.run_all(schema, query_options, **kwargs)
endUsage Examples:
# Single query with options
result = MySchema.execute(
query_string,
variables: { id: "123" },
context: { current_user: user },
operation_name: "GetUser",
only: [:query], # Only allow queries, not mutations
validate: true # Validate before execution
)
# Multiplex execution (multiple queries in one request)
queries = [
{
query: "query GetUser($id: ID!) { user(id: $id) { name } }",
variables: { id: "1" },
operation_name: "GetUser"
},
{
query: "query GetPosts { posts(limit: 5) { title } }",
operation_name: "GetPosts"
}
]
results = MySchema.multiplex(queries, context: { current_user: user })
results.each_with_index do |result, index|
puts "Query #{index + 1}: #{result.to_json}"
end
# Custom lazy resolution
class MySchema < GraphQL::Schema
# Define how to resolve lazy values
lazy_resolve(Promise, :sync)
lazy_resolve(Concurrent::Future, :value!)
end
def lazy_field
# Return a lazy value that will be resolved later
Promise.new { expensive_database_call }
endHandle and customize GraphQL execution errors.
class GraphQL::ExecutionError < GraphQL::Error
def initialize(message, ast_node: nil, options: {})
# Error information
def message
def path
def locations
def ast_node
def extensions
# Extensions for additional error data
def extensions=(new_extensions)
end
# Schema error handling hooks
class GraphQL::Schema
def self.type_error(err, context)
def self.rescue_from(error_class, &block)
def self.unauthorized_object(error)
def self.unauthorized_field(error)
endUsage Examples:
# Raising execution errors in resolvers
def user
user = User.find_by(id: args[:id])
unless user
raise GraphQL::ExecutionError.new(
"User not found",
extensions: {
code: "NOT_FOUND",
timestamp: Time.current.iso8601
}
)
end
user
end
# Schema-level error handling
class MySchema < GraphQL::Schema
# Handle type resolution errors
def self.type_error(err, context)
GraphQL::ExecutionError.new(
"Failed to resolve type: #{err.message}",
extensions: { code: "TYPE_ERROR" }
)
end
# Handle specific exception types
rescue_from(ActiveRecord::RecordNotFound) do |err, obj, args, ctx, field|
GraphQL::ExecutionError.new(
"#{field.owner.name} not found",
extensions: { code: "NOT_FOUND" }
)
end
rescue_from(ActiveRecord::RecordInvalid) do |err, obj, args, ctx, field|
GraphQL::ExecutionError.new(
"Validation failed: #{err.record.errors.full_messages.join(', ')}",
extensions: {
code: "VALIDATION_ERROR",
details: err.record.errors.messages
}
)
end
# Handle authorization errors
def self.unauthorized_object(err)
GraphQL::ExecutionError.new(
"Access denied",
extensions: { code: "UNAUTHORIZED" }
)
end
end
# Collecting multiple errors
def resolve_with_multiple_errors
errors = []
results = []
args[:ids].each do |id|
begin
results << fetch_item(id)
rescue StandardError => e
errors << GraphQL::ExecutionError.new(
e.message,
path: context.path + [results.length],
extensions: { code: "FETCH_ERROR" }
)
results << nil
end
end
errors.each { |error| context.add_error(error) }
results
endtessl i tessl/gem-graphql@2.5.2