class GraphQL::Language::Nodes::AbstractNode

{AbstractNode} is the base class for all nodes in a GraphQL AST.

It provides some APIs for working with ASTs:

Constants

NO_CHILDREN

Attributes

col[R]
filename[R]
line[R]

Public Class Methods

new(options = {}) click to toggle source

Initialize a node by extracting its position, then calling the class’s ‘initialize_node` method. @param options [Hash] Initial attributes for this node

# File lib/graphql/language/nodes.rb, line 28
def initialize(options = {})
  if options.key?(:position_source)
    position_source = options.delete(:position_source)
    @line = position_source.line
    @col = position_source.col
  end

  @filename = options.delete(:filename)

  initialize_node(**options)
end

Protected Class Methods

inherited(child_class) click to toggle source

Add a default ‘#visit_method` and `#children_method_name` using the class name

Calls superclass method
# File lib/graphql/language/nodes.rb, line 137
          def inherited(child_class)
            super
            name_underscored = child_class.name
              .split("::").last
              .gsub(/([a-z])([A-Z])/,'\1_\2') # insert underscores
              .downcase # remove caps

            child_class.module_eval <<-RUBY
              def visit_method
                :on_#{name_underscored}
              end

              class << self
                attr_accessor :children_method_name
              end
              self.children_method_name = :#{name_underscored}s
            RUBY
          end

Private Class Methods

children_methods(children_of_type) click to toggle source

Name accessors which return lists of nodes, along with the kind of node they return, if possible.

  • Add a reader for these children

  • Add a persistent update method to add a child

  • Generate a ‘#children` method

# File lib/graphql/language/nodes.rb, line 163
          def children_methods(children_of_type)
            if defined?(@children_methods)
              raise "Can't re-call .children_methods for #{self} (already have: #{@children_methods})"
            else
              @children_methods = children_of_type
            end

            if children_of_type == false
              @children_methods = {}
              # skip
            else

              children_of_type.each do |method_name, node_type|
                module_eval <<-RUBY, __FILE__, __LINE__
                  # A reader for these children
                  attr_reader :#{method_name}
                RUBY

                if node_type
                  # Only generate a method if we know what kind of node to make
                  module_eval <<-RUBY, __FILE__, __LINE__
                    # Singular method: create a node with these options
                    # and return a new `self` which includes that node in this list.
                    def merge_#{method_name.to_s.sub(/s$/, "")}(node_opts)
                      merge(#{method_name}: #{method_name} + [#{node_type.name}.new(node_opts)])
                    end
                  RUBY
                end
              end

              if children_of_type.size == 1
                module_eval <<-RUBY, __FILE__, __LINE__
                  alias :children #{children_of_type.keys.first}
                RUBY
              else
                module_eval <<-RUBY, __FILE__, __LINE__
                  def children
                    @children ||= begin
                      if #{children_of_type.keys.map { |k| "@#{k}.any?" }.join(" || ")}
                        new_children = []
                        #{children_of_type.keys.map { |k| "new_children.concat(@#{k})" }.join("; ")}
                        new_children.freeze
                        new_children
                      else
                        NO_CHILDREN
                      end
                    end
                  end
                RUBY
              end
            end

            if defined?(@scalar_methods)
              if !method_defined?(:initialize_node)
                generate_initialize_node
              else
                # This method was defined manually
              end
            else
              raise "Can't generate_initialize_node because scalar_methods wasn't called; call it before children_methods"
            end
          end
generate_initialize_node() click to toggle source
# File lib/graphql/language/nodes.rb, line 251
          def generate_initialize_node
            scalar_method_names = @scalar_methods
            # TODO: These probably should be scalar methods, but `types` returns an array
            [:types, :description].each do |extra_method|
              if method_defined?(extra_method)
                scalar_method_names += [extra_method]
              end
            end

            all_method_names = scalar_method_names + @children_methods.keys
            if all_method_names.include?(:alias)
              # Rather than complicating this special case,
              # let it be overridden (in field)
              return
            else
              arguments = scalar_method_names.map { |m| "#{m}: nil"} +
                @children_methods.keys.map { |m| "#{m}: NO_CHILDREN" }

              assignments = scalar_method_names.map { |m| "@#{m} = #{m}"} +
                @children_methods.keys.map { |m| "@#{m} = #{m}.freeze" }

              module_eval <<-RUBY, __FILE__, __LINE__
                def initialize_node #{arguments.join(", ")}
                  #{assignments.join("\n")}
                end
              RUBY
            end
          end
scalar_methods(*method_names) click to toggle source

These methods return a plain Ruby value, not another node

  • Add reader methods

  • Add a ‘#scalars` method

# File lib/graphql/language/nodes.rb, line 229
          def scalar_methods(*method_names)
            if defined?(@scalar_methods)
              raise "Can't re-call .scalar_methods for #{self} (already have: #{@scalar_methods})"
            else
              @scalar_methods = method_names
            end

            if method_names == [false]
              @scalar_methods = []
              # skip it
            else
              module_eval <<-RUBY, __FILE__, __LINE__
                # add readers for each scalar
                attr_reader #{method_names.map { |m| ":#{m}"}.join(", ")}

                def scalars
                  @scalars ||= [#{method_names.map { |k| "@#{k}" }.join(", ")}].freeze
                end
              RUBY
            end
          end

Public Instance Methods

==(other) click to toggle source

Value equality @return [Boolean] True if ‘self` is equivalent to `other`

# File lib/graphql/language/nodes.rb, line 42
def ==(other)
  return true if equal?(other)
  other.kind_of?(self.class) &&
    other.scalars == self.scalars &&
    other.children == self.children
end
children() click to toggle source

@return [Array<GraphQL::Language::Nodes::AbstractNode>] all nodes in the tree below this one

# File lib/graphql/language/nodes.rb, line 52
def children
  NO_CHILDREN
end
children_method_name() click to toggle source
# File lib/graphql/language/nodes.rb, line 68
def children_method_name
  self.class.children_method_name
end
delete_child(previous_child) click to toggle source

TODO DRY with ‘replace_child`

# File lib/graphql/language/nodes.rb, line 114
def delete_child(previous_child)
  # Figure out which list `previous_child` may be found in
  method_name = previous_child.children_method_name
  # Copy that list, and delete previous_child
  new_children = public_send(method_name).dup
  new_children.delete(previous_child)
  # Copy this node, but with the new list of children:
  copy_of_self = merge(method_name => new_children)
  # Return the copy:
  copy_of_self
end
initialize_copy(other) click to toggle source

This might be unnecessary, but its easiest to add it here.

# File lib/graphql/language/nodes.rb, line 62
def initialize_copy(other)
  @children = nil
  @scalars = nil
  @query_string = nil
end
merge(new_options) click to toggle source

This creates a copy of ‘self`, with `new_options` applied. @param new_options [Hash] @return [AbstractNode] a shallow copy of `self`

# File lib/graphql/language/nodes.rb, line 87
def merge(new_options)
  dup.merge!(new_options)
end
position() click to toggle source
# File lib/graphql/language/nodes.rb, line 72
def position
  [line, col]
end
replace_child(previous_child, new_child) click to toggle source

Copy ‘self`, but modify the copy so that `previous_child` is replaced by `new_child`

# File lib/graphql/language/nodes.rb, line 92
def replace_child(previous_child, new_child)
  # Figure out which list `previous_child` may be found in
  method_name = previous_child.children_method_name
  # Get the value from this (original) node
  prev_children = public_send(method_name)
  if prev_children.is_a?(Array)
    # Copy that list, and replace `previous_child` with `new_child`
    # in the list.
    new_children = prev_children.dup
    prev_idx = new_children.index(previous_child)
    new_children[prev_idx] = new_child
  else
    # Use the new value for the given attribute
    new_children = new_child
  end
  # Copy this node, but with the new child value
  copy_of_self = merge(method_name => new_children)
  # Return the copy:
  copy_of_self
end
scalars() click to toggle source

@return [Array<Integer, Float, String, Boolean, Array>] Scalar values attached to this node

# File lib/graphql/language/nodes.rb, line 57
def scalars
  NO_CHILDREN
end
to_query_string(printer: GraphQL::Language::Printer.new) click to toggle source
# File lib/graphql/language/nodes.rb, line 76
def to_query_string(printer: GraphQL::Language::Printer.new)
  if printer.is_a?(GraphQL::Language::Printer)
    @query_string ||= printer.print(self)
  else
    printer.print(self)
  end
end

Protected Instance Methods

merge!(new_options) click to toggle source
# File lib/graphql/language/nodes.rb, line 128
def merge!(new_options)
  new_options.each do |key, value|
    instance_variable_set(:"@#{key}", value)
  end
  self
end