Class: DagMe::Graph

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

Overview

Class-level facade for graph-wide operations, reached via Model.dag (default graph) or Model.dag(:name) (named graph):

Task.dag.between(a, b)
Task.dag.edges_among(relation)
Relay.dag(:power).rebuild!
Task.dag.valid?
Task.dag.validate!

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, config = nil) ⇒ Graph

Returns a new instance of Graph.



15
16
17
18
# File 'lib/dag_me/graph.rb', line 15

def initialize(model, config = nil)
  @model = model
  @config = config || model.dag_config
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



13
14
15
# File 'lib/dag_me/graph.rb', line 13

def config
  @config
end

#modelObject (readonly)

Returns the value of attribute model.



13
14
15
# File 'lib/dag_me/graph.rb', line 13

def model
  @model
end

Instance Method Details

#adapterObject



20
21
22
# File 'lib/dag_me/graph.rb', line 20

def adapter
  config.adapter
end

#backfill_self_rows!Object

Restores missing self-rows for nodes inserted with triggers disabled - Rails fixture loading wraps inserts in disable_referential_integrity, which skips the node_insert trigger. Idempotent; meant for test setups.



62
63
64
65
66
67
68
69
# File 'lib/dag_me/graph.rb', line 62

def backfill_self_rows!
  return self unless config.closure?

  model.connection_pool.with_connection do |conn|
    conn.execute(DDL.new(config).backfill_self_rows_sql)
  end
  self
end

#between(ancestor, descendant) ⇒ Object

Every node lying on some path from ancestor to descendant, both endpoints included.



26
27
28
# File 'lib/dag_me/graph.rb', line 26

def between(ancestor, descendant)
  adapter.between(ancestor, descendant)
end

#edgesObject



30
31
32
# File 'lib/dag_me/graph.rb', line 30

def edges
  config.edge_class.all
end

#edges_among(relation) ⇒ Object

Edges whose both endpoints are inside the given node relation - the induced subgraph's edge set (for exports, visualization, Kahn walks in Ruby, ...).



37
38
39
40
41
42
43
44
45
46
# File 'lib/dag_me/graph.rb', line 37

def edges_among(relation)
  edges = config.edge_class
  sub = relation.select(*config.node_pk_columns).arel.ast
  endpoint_in = lambda do |columns|
    tuple = Arel::Nodes::Grouping.new(columns.map { |c| edges.arel_table[c] })
    Arel::Nodes::In.new(tuple, sub)
  end
  edges.where(endpoint_in.call(config.edge_parent_columns))
       .where(endpoint_in.call(config.edge_child_columns))
end

#rebuild!Object

Rebuilds the closure table from the edges table. No-op for maintain: :recursive_cte (there is nothing materialized).



50
51
52
53
54
55
56
57
# File 'lib/dag_me/graph.rb', line 50

def rebuild!
  return self unless config.closure?

  model.connection_pool.with_connection do |conn|
    conn.execute("SELECT #{conn.quote_table_name(config.function_ref('rebuild_paths'))}();")
  end
  self
end

#valid?Boolean

Returns:

  • (Boolean)


81
82
83
# File 'lib/dag_me/graph.rb', line 81

def valid?
  validate.empty?
end

#validateObject

Rows where the stored closure disagrees with the recursive-CTE truth. Empty means healthy.



73
74
75
76
77
78
79
# File 'lib/dag_me/graph.rb', line 73

def validate
  return [] unless config.closure?

  model.connection_pool.with_connection do |conn|
    conn.select_all("SELECT * FROM #{conn.quote_table_name(config.function_ref('validate_paths'))}();").to_a
  end
end

#validate!Object



85
86
87
88
89
90
91
92
93
94
# File 'lib/dag_me/graph.rb', line 85

def validate!
  rows = validate
  if rows.any?
    raise CorruptionError.new(
      "dag_me: closure for #{model.name} diverged from edge truth (#{rows.length} rows)", rows
    )
  end

  self
end