Class: GraphQL::Execution::Lookahead

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/execution/lookahead.rb

Overview

Lookahead creates a uniform interface to inspect the forthcoming selections.

It assumes that the AST it’s working with is valid. (So, it’s safe to use during execution, but if you’re using it directly, be sure to validate first.)

A field may get access to its lookahead by adding extras: [:lookahead] to its configuration.

NOTE: Lookahead for typed fragments (eg node { ... on Thing { ... } }) hasn’t been implemented yet. It’s possible, I just didn’t need it yet. Feel free to open a PR or an issue if you want to add it.

Examples:

looking ahead in a field

field :articles, [Types::Article], null: false,
  extras: [:lookahead]

# For example, imagine a faster database call
# may be issued when only some fields are requested.
#
# Imagine that _full_ fetch must be made to satisfy `fullContent`,
# we can look ahead to see if we need that field. If we do,
# we make the expensive database call instead of the cheap one.
def articles(lookahead:)
  if lookahead.selects?(:full_content)
    fetch_full_articles(object)
  else
    fetch_preview_articles(object)
  end
end

Direct Known Subclasses

NullLookahead

Defined Under Namespace

Modules: ArgumentHelpers, FieldHelpers Classes: NullLookahead

Constant Summary

NULL_LOOKAHEAD =

A singleton, so that misses don’t come with overhead.

NullLookahead.new

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(query:, ast_nodes:, field: nil, root_type: nil) ⇒ Lookahead

Returns a new instance of Lookahead

Parameters:



38
39
40
41
42
43
44
# File 'lib/graphql/execution/lookahead.rb', line 38

def initialize(query:, ast_nodes:, field: nil, root_type: nil)
  @ast_nodes = ast_nodes.freeze
  @field = field
  @root_type = root_type
  @query = query
  @selected_type = @field ? @field.type.unwrap : root_type
end

Instance Attribute Details

#ast_nodesArray<GraphQL::Language::Nodes::Field> (readonly)



47
48
49
# File 'lib/graphql/execution/lookahead.rb', line 47

def ast_nodes
  @ast_nodes
end

#fieldGraphQL::Schema::Field (readonly)



50
51
52
# File 'lib/graphql/execution/lookahead.rb', line 50

def field
  @field
end

Instance Method Details

#inspectObject



137
138
139
# File 'lib/graphql/execution/lookahead.rb', line 137

def inspect
  "#<GraphQL::Execution::Lookahead #{@field ? "@field=#{@field.path.inspect}": "@root_type=#{@root_type}"} @ast_nodes.size=#{@ast_nodes.size}>"
end

#nameSymbol

The method name of the field. It returns the method_sym of the Lookahead’s field.

Examples:

getting the name of a selection

def articles(lookahead:)
  article.selection(:full_content).name # => :full_content
  # ...
end

Returns:

  • (Symbol)


133
134
135
# File 'lib/graphql/execution/lookahead.rb', line 133

def name
  @field && @field.original_name
end

#selected?Boolean

Returns True if this lookahead represents a field that was requested

Returns:

  • (Boolean)

    True if this lookahead represents a field that was requested



69
70
71
# File 'lib/graphql/execution/lookahead.rb', line 69

def selected?
  true
end

#selection(field_name, selected_type: @selected_type, arguments: nil) ⇒ GraphQL::Execution::Lookahead

Like #selects?, but can be used for chaining. It returns a null object (check with #selected?)



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/graphql/execution/lookahead.rb', line 76

def selection(field_name, selected_type: @selected_type, arguments: nil)
  next_field_name = normalize_name(field_name)

  next_field_defn = FieldHelpers.get_field(@query.schema, selected_type, next_field_name)
  if next_field_defn
    next_nodes = []
    @ast_nodes.each do |ast_node|
      ast_node.selections.each do |selection|
        find_selected_nodes(selection, next_field_name, next_field_defn, arguments: arguments, matches: next_nodes)
      end
    end

    if next_nodes.any?
      Lookahead.new(query: @query, ast_nodes: next_nodes, field: next_field_defn)
    else
      NULL_LOOKAHEAD
    end
  else
    NULL_LOOKAHEAD
  end
end

#selections(arguments: nil) ⇒ Array<GraphQL::Execution::Lookahead>

Like #selection, but for all nodes. It returns a list of Lookaheads for all Selections

If arguments: is provided, each provided key/value will be matched against the arguments in each selection. This method will filter the selections if any of the given arguments: do not match the given selection.

Examples:

getting the name of a selection

def articles(lookahead:)
  next_lookaheads = lookahead.selections # => [#<GraphQL::Execution::Lookahead ...>, ...]
  next_lookaheads.map(&:name) #=> [:full_content, :title]
end

Parameters:

  • arguments (Hash)

    Arguments which must match in the selection

Returns:



113
114
115
116
117
118
119
120
121
# File 'lib/graphql/execution/lookahead.rb', line 113

def selections(arguments: nil)
  subselections_by_name = {}
  @ast_nodes.each do |node|
    find_selections(subselections_by_name, @selected_type, node.selections, arguments)
  end

  # Items may be filtered out if `arguments` doesn't match
  subselections_by_name.values.select(&:selected?)
end

#selects?(field_name, arguments: nil) ⇒ Boolean

True if this node has a selection on field_name. If field_name is a String, it is treated as a GraphQL-style (camelized) field name and used verbatim. If field_name is a Symbol, it is treated as a Ruby-style (underscored) name and camelized before comparing.

If arguments: is provided, each provided key/value will be matched against the arguments in the next selection. This method will return false if any of the given arguments: are not present and matching in the next selection. (But, the next selection may contain more than the given arguments.)

Parameters:

  • field_name (String, Symbol)
  • arguments (Hash)

    Arguments which must match in the selection

Returns:

  • (Boolean)


64
65
66
# File 'lib/graphql/execution/lookahead.rb', line 64

def selects?(field_name, arguments: nil)
  selection(field_name, arguments: arguments).selected?
end