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:
-
`children` returns all AST nodes attached to this one. Used for tree traversal.
-
`scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes.
-
`to_query_string` turns an AST node into a
GraphQL
string
Constants
- NO_CHILDREN
Attributes
Public Class Methods
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
Add a default `#visit_method` and `#children_method_name` using the class name
# File lib/graphql/language/nodes.rb, line 139 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
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 165 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
# File lib/graphql/language/nodes.rb, line 253 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
These methods return a plain Ruby value, not another node
-
Add reader methods
-
Add a `#scalars` method
# File lib/graphql/language/nodes.rb, line 231 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
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
@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
# File lib/graphql/language/nodes.rb, line 68 def children_method_name self.class.children_method_name end
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
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
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
# File lib/graphql/language/nodes.rb, line 72 def position [line, col] end
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
@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
# 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
# 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