class GraphQL::Relay::Walker::QueryBuilder

Constants

BASE_QUERY
DEFAULT_ARGUMENTS

Attributes

ast[R]
connection_arguments[R]
except[R]
only[R]
schema[R]

Public Class Methods

new(schema, except: nil, only: nil, connection_arguments: DEFAULT_ARGUMENTS) click to toggle source

Initialize a new QueryBuilder.

schema - The GraphQL::Schema to build a query for. connection_arguments: - A Hash or arguments to use for connection fields

(optional).

Returns nothing.

# File lib/graphql/relay/walker/query_builder.rb, line 15
def initialize(schema, except: nil, only: nil, connection_arguments: DEFAULT_ARGUMENTS)
  @schema = schema
  @except = except
  @only   = only
  @connection_arguments = connection_arguments
  @ast = build_query
end

Public Instance Methods

query_string() click to toggle source

Get the built query.

Returns a String.

# File lib/graphql/relay/walker/query_builder.rb, line 26
def query_string
  ast.to_query_string
end

Private Instance Methods

build_query() click to toggle source

Build a query for our relay schema that selects an inline fragment for every node type. For every inline fragment, we select the ID of every node field and connection.

Returns a GraphQL::Language::Nodes::Document instance.

# File lib/graphql/relay/walker/query_builder.rb, line 37
def build_query
  GraphQL.parse(BASE_QUERY).tap do |d_ast|
    selections = d_ast.definitions.first.selections.first.selections

    node_types.each do |type|
      selections << inline_fragment_ast(type) if include?(type)
    end

    selections.compact!
  end
end
connection_field?(field) click to toggle source

Is this field for a relay connection?

field - A GraphQL::Field instance.

Returns true if this field's type has a `edges` field whose type has a `node` field that is a relay node. Returns false otherwise.

# File lib/graphql/relay/walker/query_builder.rb, line 217
def connection_field?(field)
  type = field.type.unwrap
  if type.kind.fields?
    if edges_field = type.get_field('edges')
      edges = edges_field.type.unwrap
      if node_field = edges.get_field('node')
        return node_field?(node_field)
      end
    end
  end

  false
end
connection_field_ast(field) click to toggle source

Make a field AST for a connection field.

field - The GraphQL::Field instance to make the fragment for.

Returns a GraphQL::Language::Nodes::Field instance or nil if the created AST was invalid for missing required arguments.

# File lib/graphql/relay/walker/query_builder.rb, line 181
def connection_field_ast(field)
  f_ast = field_ast(field, connection_arguments)
  return nil if f_ast.nil?
  edges_fields = [edges_field_ast(field.type.unwrap.get_field('edges'))]
  if f_ast.respond_to?(:merge) # GraphQL-Ruby 1.9+
    f_ast.merge(selections: f_ast.selections + edges_fields)
  else
    f_ast.selections.concat(edges_fields)
    f_ast
  end
end
edges_field_ast(field) click to toggle source

Make a field AST for an edges field.

field - The GraphQL::Field instance to make the fragment for.

Returns a GraphQL::Language::Nodes::Field instance.

# File lib/graphql/relay/walker/query_builder.rb, line 163
def edges_field_ast(field)
  f_ast = field_ast(field)
  return nil if f_ast.nil?
  node_fields = [node_field_ast(field.type.unwrap.get_field('node'))]
  if f_ast.respond_to?(:merge) # GraphQL-Ruby 1.9+
    f_ast.merge(selections: f_ast.selections + node_fields)
  else
    f_ast.selections.concat(node_fields)
    f_ast
  end
end
field_ast(field, arguments = {}, &blk) click to toggle source

Make a field AST.

field - The GraphQL::Field instance to make the fragment for. arguments - A Hash of arguments to include in the field. &blk - A block to call with the AST and field type before returning

the AST.

Returns a GraphQL::Language::Nodes::Field instance or nil if the created AST was invalid for having no selections or missing required arguments.

# File lib/graphql/relay/walker/query_builder.rb, line 109
def field_ast(field, arguments = {}, &blk)
  type = field.type.unwrap

  # Bail unless we have the required arguments.
  required_args_are_present = field.arguments.all? do |arg_name, arg|
    arguments.key?(arg_name) || valid_input?(arg.type, nil)
  end

  if !required_args_are_present
    nil
  else
    f_alias = field.graphql_name == 'id' ? nil : random_alias
    f_args = arguments.map do |name, value|
      GraphQL::Language::Nodes::Argument.new(name: name, value: value)
    end

    GraphQL::Language::Nodes::Field.new(name: field.graphql_name, alias: f_alias, arguments: f_args)
  end
end
include?(type) click to toggle source

Private: Depending on the `except` or `include` filters, should this item be included a AST of the given type.

type - The GraphQL item to identify to make the fragment

Returns a Boolean.

# File lib/graphql/relay/walker/query_builder.rb, line 55
def include?(type)
  return !@except.call(type, {}) if @except
  return @only.call(type, {}) if @only
  true
end
inline_fragment_ast(type, with_children: true) click to toggle source

Make an inline fragment AST.

type - The GraphQL::ObjectType instance to make the fragment

for.

with_children: - Boolean. Whether to select all children of this inline

fragment, or just it's ID.

Returns a GraphQL::Language::Nodes::InlineFragment instance or nil if the created AST was invalid for having no selections.

# File lib/graphql/relay/walker/query_builder.rb, line 70
def inline_fragment_ast(type, with_children: true)
  selections = []
  if with_children
    # Class-based types return all fields in `.fields`
    all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
    all_fields = all_fields.sort_by(&:graphql_name)
    all_fields.each do |field|
      field_type = field.type.unwrap
      if node_field?(field) && include?(field_type)
        selections << node_field_ast(field)
      elsif connection_field?(field) && include?(field_type)
        selections << connection_field_ast(field)
      end
    end
  elsif id = type.get_field('id')
    selections << field_ast(id)
  end

  selections.compact!

  if selections.none?
    nil
  else
    GraphQL::Language::Nodes::InlineFragment.new(
      type: make_type_name_node(type.graphql_name),
      selections: selections,
    )
  end
end
make_type_name_node(type_name) click to toggle source
# File lib/graphql/relay/walker/query_builder.rb, line 278
def make_type_name_node(type_name)
  GraphQL::Language::Nodes::TypeName.new(name: type_name)
end
node_field?(field) click to toggle source

Is this field for a relay node?

field - A GraphQL::Field instance.

Returns true if the field's type includes the `Node` interface or is a union or interface with a possible type that includes the `Node` interface Returns false otherwise.

# File lib/graphql/relay/walker/query_builder.rb, line 200
def node_field?(field)
  type = field.type.unwrap
  kind = type.kind

  if kind.object?
    node_types.include?(type)
  elsif kind.interface? || kind.union?
    possible_node_types(type).any?
  end
end
node_field_ast(field) click to toggle source

Make a field AST for a node field.

field - The GraphQL::Field instance to make the fragment for.

Returns a GraphQL::Language::Nodes::Field instance.

# File lib/graphql/relay/walker/query_builder.rb, line 134
def node_field_ast(field)
  f_ast = field_ast(field)
  return nil if f_ast.nil?
  type = field.type.unwrap
  selections = f_ast.selections.dup

  if type.kind.object?
    selections << field_ast(type.get_field('id'))
  else
    possible_node_types(type).each do |if_type|
      selections << inline_fragment_ast(if_type, with_children: false)
    end
  end

  selections.compact!

  if f_ast.respond_to?(:merge) # GraphQL-Ruby 1.9+
    f_ast = f_ast.merge(selections: selections)
  else
    f_ast.selections = selections
  end
  f_ast
end
node_interface() click to toggle source

Get the `Node` interface.

Returns a GraphQL::InterfaceType instance.

# File lib/graphql/relay/walker/query_builder.rb, line 263
def node_interface
  schema.types['Node']
end
node_types() click to toggle source

Get the types that implement the `Node` interface.

Returns an Array of GraphQL::ObjectType instances.

# File lib/graphql/relay/walker/query_builder.rb, line 256
def node_types
  schema.possible_types(node_interface)
end
possible_node_types(type) click to toggle source

Get the possible types of a union or interface that are relay nodes.

type - A GraphQL::UnionType or GraphQL::InterfaceType instance.

Returns an Array of GraphQL::ObjectType instances.

# File lib/graphql/relay/walker/query_builder.rb, line 249
def possible_node_types(type)
  possible_types(type) & node_types
end
possible_types(type) click to toggle source

Get the possible types of a union or interface.

type - A GraphQL::UnionType or GraphQL::InterfaceType instance.

Returns an Array of GraphQL::ObjectType instances.

# File lib/graphql/relay/walker/query_builder.rb, line 236
def possible_types(type)
  if type.kind.interface?
    schema.possible_types(type)
  elsif type.kind.union?
    type.possible_types
  end
end
random_alias() click to toggle source

Make a random alias for a field.

Returns a twelve character random String.

# File lib/graphql/relay/walker/query_builder.rb, line 270
def random_alias
  12.times.map { (SecureRandom.random_number(26) + 97).chr }.join
end
valid_input?(type, input) click to toggle source
# File lib/graphql/relay/walker/query_builder.rb, line 274
def valid_input?(type, input)
  type.valid_input?(input, GraphQL::Query::NullContext)
end