class ComputedModel::DepGraph

A dependency graph representation used within ComputedModel::Model. Usually you don't need to use this class directly.

@api private

@example

graph = ComputedModel::DepGraph.new
graph << ComputedModel::DepGraph::Node.new(:computed, :foo, { bar: [] })
graph << ComputedModel::DepGraph::Node.new(:loaded, :bar, {})
plan = graph.tsort.plan([:foo])

Public Class Methods

merge(graphs) click to toggle source

@param graphs [Array<ComputedModel::DepGraph>] @return [ComputedModel::DepGraph]

# File lib/computed_model/dep_graph.rb, line 104
def self.merge(graphs)
  new_graph = ComputedModel::DepGraph.new
  nodes_by_name = graphs.flat_map(&:nodes).group_by(&:name)
  nodes_by_name.each do |name, nodes|
    type = nodes.first.type
    raise ArgumentError, "Field #{name} has multiple different types" unless nodes.all? { |node| node.type == type }

    new_graph << ComputedModel::DepGraph::Node.new(type, name, nodes.map { |n| n.edges.transform_values { |e| e.spec } })
  end
  new_graph
end
new() click to toggle source
# File lib/computed_model/dep_graph.rb, line 17
def initialize
  @nodes = {}
end

Public Instance Methods

<<(node) click to toggle source

Adds the new node.

@param node [Node] @return [void] @raise [ArgumentError] when the node already exists

@example

graph = ComputedModel::DepGraph.new
graph << ComputedModel::DepGraph::Node.new(:computed, :foo, {})
# File lib/computed_model/dep_graph.rb, line 42
def <<(node)
  raise ArgumentError, "Field already declared: #{node.name}" if @nodes.key?(node.name)

  @nodes[node.name] = node
end
[](name) click to toggle source

Returns the node with the specified name.

@param name [Symbol] the name of the node @return [Node, nil]

@example

graph = ComputedModel::DepGraph.new
graph[:foo]
# File lib/computed_model/dep_graph.rb, line 29
def [](name)
  @nodes[name]
end
nodes() click to toggle source

@return [Array<ComputedModel::DepGraph::Node>]

# File lib/computed_model/dep_graph.rb, line 49
def nodes
  @nodes.values
end
tsort() click to toggle source

Preprocess the graph by topological sorting. This is a necessary step for loader planning.

@return [ComputedModel::DepGraph::Sorted]

@example

graph = ComputedModel::DepGraph.new
graph << ComputedModel::DepGraph::Node.new(:computed, :foo, { bar: [] })
graph << ComputedModel::DepGraph::Node.new(:loaded, :bar, {})
sorted = graph.tsort
# File lib/computed_model/dep_graph.rb, line 62
def tsort
  load_order = []
  visiting = Set[]
  visited = Set[]

  @nodes.each_value do |node|
    next unless node.type == :primary

    load_order << node.name
    visiting.add node.name
    visited.add node.name
  end

  raise ArgumentError, 'No primary loader defined' if load_order.empty?
  raise "Multiple primary fields: #{load_order.inspect}" if load_order.size > 1

  @nodes.each_value do |node|
    tsort_dfs(node.name, load_order, visiting, visited)
  end

  nodes_in_order = load_order.reverse.map { |name| @nodes[name] }
  ComputedModel::DepGraph::Sorted.new(self, nodes_in_order)
end

Private Instance Methods

tsort_dfs(name, load_order, visiting, visited) click to toggle source
# File lib/computed_model/dep_graph.rb, line 86
        def tsort_dfs(name, load_order, visiting, visited)
  return if visited.include?(name)
  raise ComputedModel::CyclicDependency, "Cyclic dependency for ##{name}" if visiting.include?(name)
  raise "No dependency info for ##{name}" unless @nodes.key?(name)

  visiting.add(name)

  @nodes[name].edges.each_value do |edge|
    tsort_dfs(edge.name, load_order, visiting, visited)
  end

  load_order << name
  visiting.delete(name)
  visited.add(name)
end