Class: CML::LogicTree::Graph

Inherits:
Object
  • Object
show all
Defined in:
lib/cml/logic_tree/graph.rb

Overview

Generates a DAG, directed acyclic graph of the logic tree.

Defined Under Namespace

Classes: Error, UnresolvableDependency

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(logic_tree) ⇒ Graph

Returns a new instance of Graph.

Raises:

  • (ArgumentError)


14
15
16
17
18
# File 'lib/cml/logic_tree/graph.rb', line 14

def initialize(logic_tree)
  raise(ArgumentError, "Requires a CML::LogicTree, instead got #{logic_tree.inspect}") unless CML::LogicTree===logic_tree
  @logic_tree = logic_tree
  create_structure
end

Instance Attribute Details

#logic_treeObject

Returns the value of attribute logic_tree.



12
13
14
# File 'lib/cml/logic_tree/graph.rb', line 12

def logic_tree
  @logic_tree
end

#structureObject

Returns the value of attribute structure.



12
13
14
# File 'lib/cml/logic_tree/graph.rb', line 12

def structure
  @structure
end

Instance Method Details

#create_structureObject

Generate a hash of field names with hashes of properties, e.g.:

{
  "omg"=>{
    :outbound_count=>1, :outbound_names=>["w_t_f"] }, 
  "w_t_f"=>{
    :inbound_count=>1, :inbound_names=>["omg"], :inbound_matches=>{"||" => [{"omg"=>[{:match_key => nil, :is_not=>true}]}]} }
}

For inbound matches, the match key tests the value of the remote vertex. Boolean combinators are represented here.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/cml/logic_tree/graph.rb', line 30

def create_structure
  # First, inbound links.
  @structure = map_dependencies do |field, cml_tag, dependency_names, dependencies|
    if dependencies
      dependency_names.uniq!
      { :inbound_count => dependency_names.size, :inbound_names => dependency_names, :inbound_matches => dependencies }
    else
      {}
    end
  end
  # Map outbound links by iterating over each node's inbound.
  @structure.each do |vertex, props|
    next if props.nil? || props[:inbound_names].nil?
    props[:inbound_names].each do |inbound_name|
      x = @structure[inbound_name]
      c, n = x[:outbound_count], x[:outbound_names]
      x[:outbound_names] = (n ? n<<vertex : [vertex]).sort.uniq
      x[:outbound_count] = x[:outbound_names].size
    end
  end
  @structure
end

#map_dependenciesObject

Returns a hash of the logic tree’s nodes.

When a block is given, each node’s entry is set to the return value of the block.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/cml/logic_tree/graph.rb', line 57

def map_dependencies
  structure = {}
  deferred_vertex_procs = []
  deferred_vertex_counts = Hash.new(0)
  created_vertex_for_fields = []
  @logic_tree.nodes.select {|k,v| v.tag.in_logic_graph? }.each do |field, cml_tag|
  
    # This proc is used to generate each vertex.
    map_vertex = lambda do |cml_tag, dependency_names, dependencies|
      if block_given?
        structure[field] = yield(field, cml_tag, dependency_names, dependencies)
      else
        structure[field] = { :cml_tag => cml_tag, :dependency_names => dependency_names, :dependencies => dependencies }
      end
      created_vertex_for_fields << field
    end
    
    # Expand dependencies & also collect a simple list of dependency names.
    dependency_names = []
    dependencies = cml_tag.dependencies_on {|name, descs| dependency_names << name }
    
    # Defer when a CML tag has unsatisfied dependencies, or just generate its vertex.
    if dependency_names.empty?
      map_vertex.call(cml_tag, nil, nil)
    else
      deferred_vertex_procs << lambda do |this_proc|
        if dependency_names.all? {|name| created_vertex_for_fields.include? name }
          map_vertex.call(cml_tag, dependency_names, dependencies)
        else
          # Push onto bottom of stack to try again last.
          raise(UnresolvableDependency, "CML element '#{field}' contains invalid only-if logic.") if 
            deferred_vertex_counts[field] > DEPTH_LIMIT
          deferred_vertex_counts[field] += 1
          deferred_vertex_procs.unshift(this_proc)
        end
      end
    end
  end
  
  # Iterate through deferred dependencies, filling out the structure as dependencies are satisfied.
  while !deferred_vertex_procs.empty? do
    p = deferred_vertex_procs.pop
    p.call(p) # pass the Proc itself, in case it needs to defer again
  end
  
  structure
end