Class: Tap::Support::Audit
Overview
Audit provides a way to track the values passed among tasks or, more generally, any Executable. Audits collectively build a directed acyclic graph of task execution and have great utility in debugging and record keeping.
Audits record a key, a current value, and the previous audit(s) in the trail. Keys are arbitrary identifiers of where the value comes from. To illustrate, lets use symbols as keys.
# initialize a new audit
_a = Audit.new(:one, 1)
_a.key # => :one
_a.value # => 1
# build a short trail
_b = Audit.new(:two, 2, _a)
_c = Audit.new(:three, 3, _b)
_a.sources # => []
_b.sources # => [_a]
_c.sources # => [_b]
Audits allow you track back through the sources of each audit to build a trail describing how a particular value was produced.
_c.trail # => [_a,_b,_c]
_c.trail {|audit| audit.key } # => [:one, :two, :three]
_c.trail {|audit| audit.value } # => [1,2,3]
Any number of audits may share the same source, so forks are naturally supported.
_d = Audit.new(:four, 4, _b)
_d.trail # => [_a,_b,_d]
_e = Audit.new(:five, 5, _b)
_e.trail # => [_a,_b,_e]
Merges are supported by specifying more than one source. Merges have the effect of nesting audit trails within an array:
_f = Audit.new(:six, 6)
_g = Audit.new(:seven, 7, _f)
_h = Audit.new(:eight, 8, [_c,_d,_g])
_h.trail # => [[[_a,_b,_c], [_a,_b,_d], [_f,_g]], _h]
Nesting can get quite ugly after a couple merges so Audit provides a scalable pretty-print dump that helps visualize the audit trail.
"\n" + _h.dump
# => %q{
# o-[one] 1
# o-[two] 2
# |
# |-o-[three] 3
# | |
# `---o-[four] 4
# | |
# | | o-[six] 6
# | | o-[seven] 7
# | | |
# `-`-`-o-[eight] 8
# }
In practice, tasks are recorded as keys. Thus audit trails can be used to access task configurations and other information that may be useful when creating reports or making workflow decisions. Note that by convention audits and non-audit methods that return audits are prefixed with an underscore.
– Note Audit could easily be expanded to track sinks as well as sources. In initialize:
@sinks = []
sources.each do |source|
source.sinks << self
end
The downside is that this may not circumvent cleanly if you want light or no auditing. It also adds additonal references which will prevent garbage collection. On the plus side, sinks will make it easier to truly use Audits as a DAG
Instance Attribute Summary collapse
-
#key ⇒ Object
readonly
A key for self (typically the task producing value, or nil if the value has an unknown origin).
-
#value ⇒ Object
readonly
The current value.
Class Method Summary collapse
-
.dump(audits, target = $stdout) ⇒ Object
Produces a pretty-print dump of the specified audits to target.
Instance Method Summary collapse
-
#dump(&block) ⇒ Object
A kind of pretty-print for Audits.
-
#initialize(key = nil, value = nil, sources = nil) ⇒ Audit
constructor
Initializes a new Audit.
-
#sources ⇒ Object
An array of source audits for self.
-
#splat ⇒ Object
Produces a fork of self for each item in value, using the index of the item as a key.
-
#trail(trail = [], &block) ⇒ Object
Recursively collects an audit trail leading to self.
Constructor Details
#initialize(key = nil, value = nil, sources = nil) ⇒ Audit
227 228 229 230 231 |
# File 'lib/tap/support/audit.rb', line 227 def initialize(key=nil, value=nil, sources=nil) @key = key @value = value @source = singularize(sources) end |
Instance Attribute Details
#key ⇒ Object (readonly)
A key for self (typically the task producing value, or nil if the value has an unknown origin)
210 211 212 |
# File 'lib/tap/support/audit.rb', line 210 def key @key end |
#value ⇒ Object (readonly)
The current value
213 214 215 |
# File 'lib/tap/support/audit.rb', line 213 def value @value end |
Class Method Details
.dump(audits, target = $stdout) ⇒ Object
Produces a pretty-print dump of the specified audits to target. A block may be provided to format the trailer of each line.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/tap/support/audit.rb', line 92 def dump(audits, target=$stdout) # :yields: audit return dump(audits, target) do |audit| "o-[#{audit.key}] #{audit.value.inspect}" end unless block_given? # arrayify audits audits = [audits].flatten # the order of audits order = [] # (audit, sinks) hash preventing double iteration over # audits, and identifying sinks for a particular audit sinks = {} # iterate over all audits, collecting in order audits.each do |audit| traverse(audit, order, sinks) end # visit each audit, collecting audits into indent groups groups = [] group = nil order.each do |audit| sources = audit.sources unless sources.length == 1 && sinks[sources[0]].length <= 1 group = [] groups << group end group << audit end # identify nodes at which a fork occurs... these are audits # that have more than one sink, and they cause a fork-style # leader to be printed forks = {} sinks.each_pair do |audit, sinks| n = sinks.length forks[audit] = [0, n] if n > 1 end # setup print index = 0 leader = "" # print each group groups.each do |group| sources = group[0].sources complete = audits.include?(group[-1]) case when sources.length > 1 # print a merge # `-`-`-o-[merge] leader =~ /^(.*)((\| *){#{sources.length}})$/ leader = "#{$1}#{' ' * $2.length} " target << "#{$1}#{$2.gsub('|', '`').gsub(' ', '-')}-#{yield(group.shift)}\n" when fork = forks[sources[0]] # print a fork # |-o-[a] # | # `---o-[b] n = fork[0] += 1 base = leader[0, leader.length - (2 * n - 1)] target << "#{base}#{fork[0] == fork[1] ? '`-' : '|-'}#{'--' * (n-1)}#{yield(group.shift)}\n" leader = "#{base}#{fork[0] == fork[1] ? ' ' : '| '}#{'| ' * (n-1)}" when index > 0 # simply get ready to print the next series of audits # o-[a] # o-[b] leader = "#{leader} " leader = "" if leader.strip.empty? end # print the next series of audits group.each do |audit| target << "#{leader}#{yield(audit)}\n" end # add a continuation line, if necessary unless group == groups.last if complete leader = "#{leader} " else leader = "#{leader}|" end target << "#{leader}\n" end index += 1 end target end |
Instance Method Details
#dump(&block) ⇒ Object
A kind of pretty-print for Audits.
306 307 308 |
# File 'lib/tap/support/audit.rb', line 306 def dump(&block) Audit.dump(self, "", &block) end |
#sources ⇒ Object
An array of source audits for self. Sources may be empty.
234 235 236 |
# File 'lib/tap/support/audit.rb', line 234 def sources arrayify(@source) end |
#splat ⇒ Object
Produces a fork of self for each item in value, using the index of the item as a key. Splat is useful for developing each item of an array value along different paths.
_a = Audit.new(nil, [:x, :y, :z])
_b,_c,_d = _a.splat
_b.key # => 0
_b.value # => :x
_c.key # => 1
_c.value # => :y
_d.key # => 2
_d.value # => :z
_d.trail # => [_a,_d]
If value does not respond to ‘each’, an array with self as the only member will be returned. This ensures that the result of splat is an array of audits ready for further development.
_a = Audit.new(nil, :value)
_a.splat # => [_a]
262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/tap/support/audit.rb', line 262 def splat return [self] unless value.respond_to?(:each) collection = [] index = 0 value.each do |obj| collection << Audit.new(index, obj, self) index += 1 end collection end |
#trail(trail = [], &block) ⇒ Object
Recursively collects an audit trail leading to self. Single sources are collected into the trail directly, while multiple sources are collected into arrays.
_a = Audit.new(:one, 1)
_b = Audit.new(:two, 2, _a)
_b.trail # => [_a,_b]
_a = Audit.new(:one, 1)
_b = Audit.new(:two, 2)
_c = Audit.new(:three, 3, [_a, _b])
_c.trail # => [[[_a],[_b]],_c]
A block may be provided to collect a specific audit attribute instead of the audit itself.
_c.trail {|audit| audit.value } # => [[[1],[2]],3]
292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/tap/support/audit.rb', line 292 def trail(trail=[], &block) trail.unshift(block_given? ? block.call(self) : self) case @source when Audit @source.trail(trail, &block) when Array trail.unshift @source.collect {|audit| audit.trail(&block) } end trail end |