Module: Phronomy::WorkflowContext

Defined in:
lib/phronomy/workflow_context.rb

Overview

Module for defining workflow context (the data that travels through a workflow). Include in a class and use the field DSL to declare context fields.

In StateChart terminology this is the "extended state" or "context" — data associated with the current execution that does not affect transitions directly, as opposed to the current phase (which is the machine's state).

Field update policies: :replace (default) -- overwrites with the new value :append -- appends to an Array :merge -- shallow-merges into a Hash (top-level keys are merged; nested objects are replaced)

Examples:

class ScanContext
  include Phronomy::WorkflowContext
  field :messages, type: :append, default: -> { [] }
  field :query,    type: :replace
  field :metadata, type: :merge,   default: -> { {} }
end

Defined Under Namespace

Modules: ClassMethods

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#thread_idObject (readonly)

Internal workflow metadata accessors (not user-defined fields). These are preserved through merge but excluded from to_h.



64
65
66
# File 'lib/phronomy/workflow_context.rb', line 64

def thread_id
  @thread_id
end

Class Method Details

.included(base) ⇒ Object



24
25
26
27
# File 'lib/phronomy/workflow_context.rb', line 24

def self.included(base)
  base.extend(ClassMethods)
  base.instance_variable_set(:@fields, {})
end

Instance Method Details

#halted?Boolean

Returns true if the workflow is paused mid-execution (not yet completed). mutant:disable - phase != :end vs !phase.eql?(:end) vs !phase.equal?(:end) are genuine equivalents for Symbol (Symbols are interned so == / eql? / equal? all behave identically)

Returns:

  • (Boolean)


82
83
84
# File 'lib/phronomy/workflow_context.rb', line 82

def halted?
  phase != :__end__
end

#initialize(**attrs) ⇒ Object

mutant:disable - multiple genuine equivalent mutations: is_a?(Proc) vs instance_of?(Proc) (Proc has no subclasses in practice), config[]/fetch() for always-present :default key, @thread_id=nil removal (unset ivar is already nil), @phase=:end → nil or removal (phase method returns :end via @phase||:end fallback), raise message #inspect vs #{} (spec checks exception class not message text)

Raises:

  • (ArgumentError)


98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/phronomy/workflow_context.rb', line 98

def initialize(**attrs)
  unknown = attrs.keys - self.class.fields.keys
  raise ArgumentError, "Unknown WorkflowContext field(s): #{unknown.inspect}" unless unknown.empty?

  self.class.fields.each do |name, config|
    default = config[:default].is_a?(Proc) ? config[:default].call : config[:default]
    # Bypass the write guard in initialize — ownership enforcement begins
    # after construction is complete.
    instance_variable_set(:"@#{name}", attrs.fetch(name, default))
  end
  @thread_id = nil
  @phase = :__end__
end

#merge(updates) ⇒ self.class

Returns a new context instance with the specified field updates applied. Updated fields follow the field's declared +:type+ semantics (:replace, :append, or :merge). Unchanged fields are deep-copied on a best-effort basis — objects that do not support +#dup+ (e.g. integers, frozen objects) are carried over by reference. Internal workflow metadata (thread_id, phase) is preserved. mutant:disable - multiple genuine equivalent mutations: send/public_send/send are identical (all field accessors are public), fields[]/fetch() and field_config[]/fetch() for always-present keys, updates[]/fetch() when updates.key?(name) is already true, Array() wrapping for append fields that always hold Arrays, (send||{})/send equivalence for merge fields that always hold Hashes, deep_dup_value(send) vs send are equivalent under killfork (coverage selection does not trace the deep_dup_value call site across the fork boundary), raise message inspect vs to_s (spec checks exception class only)

Parameters:

  • updates (Hash)

    { field_name => new_value }

Returns:

  • (self.class)

    new context instance

Raises:

  • (ArgumentError)

    if updates contains keys that are not declared fields



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
# File 'lib/phronomy/workflow_context.rb', line 122

def merge(updates)
  unknown = updates.keys - self.class.fields.keys
  raise ArgumentError, "Unknown WorkflowContext field(s): #{unknown.inspect}" unless unknown.empty?

  new_attrs = {}
  self.class.fields.each_key do |name|
    field_config = self.class.fields[name]
    new_attrs[name] = if updates.key?(name)
      case field_config[:type]
      when :append
        Array(send(name)) + Array(updates[name])
      when :merge
        (send(name) || {}).merge(updates[name])
      else
        updates[name]
      end
    else
      deep_dup_value(send(name))
    end
  end
  new_context = self.class.new(**new_attrs)
  new_context.(
    thread_id: @thread_id,
    phase: @phase
  )
  new_context
end

#phaseSymbol

Returns the current execution phase of the workflow. Encoding: :end — workflow completed (or not yet started) :awaiting_ — halted at a wait_state(:awaiting_) declaration : — resuming at (workflow paused before its execution) mutant:disable - @phase is always non-nil (set to :end in initialize, only changed by set_graph_metadata which never sets nil), so the || :end fallback branch is never reached — all mutations of the right-hand side are genuine equivalents

Returns:

  • (Symbol)


74
75
76
# File 'lib/phronomy/workflow_context.rb', line 74

def phase
  @phase || :__end__
end

#set_graph_metadata(thread_id: nil, phase: nil) ⇒ Object

Sets internal workflow metadata. Returns self. mutant:disable - mutations replacing return value self with nil or removing the last line are genuine equivalents: callers chain on the return value only in merge which immediately discards it

Parameters:

  • thread_id (String, nil) (defaults to: nil)
  • phase (Symbol, nil) (defaults to: nil)


91
92
93
94
95
# File 'lib/phronomy/workflow_context.rb', line 91

def (thread_id: nil, phase: nil)
  @thread_id = thread_id unless thread_id.nil?
  @phase = phase unless phase.nil?
  self
end

#to_hHash

Converts user-defined fields to a Hash (excludes internal workflow metadata). mutant:disable - send/public_send/send are genuine equivalents (all field accessors are public methods)

Returns:

  • (Hash)


154
155
156
157
158
# File 'lib/phronomy/workflow_context.rb', line 154

def to_h
  self.class.fields.keys.each_with_object({}) do |name, h|
    h[name] = send(name)
  end
end