Class: Newflow::Workflow

Inherits:
Object
  • Object
show all
Defined in:
lib/newflow/workflow.rb

Instance Method Summary collapse

Constructor Details

#initialize(extendee, definition) ⇒ Workflow

Returns a new instance of Workflow.



3
4
5
6
# File 'lib/newflow/workflow.rb', line 3

def initialize(extendee, definition)
  @extendee = extendee
  construct_workflow!(definition)
end

Instance Method Details

#construct_workflow!(definition) ⇒ Object



24
25
26
27
28
29
30
31
# File 'lib/newflow/workflow.rb', line 24

def construct_workflow!(definition)
  instance_eval &definition
  start_state = states.values.detect { |s| s.start? }
  @extendee.workflow_state ||= start_state.name.to_s if start_state
  validate_workflow!
  define_state_query_methods
  raise InvalidWorkflowStateError.new(current_state) unless states[current_state]
end

#current_stateObject



74
75
76
# File 'lib/newflow/workflow.rb', line 74

def current_state
  @extendee.workflow_state.to_sym
end

#current_state=(state) ⇒ Object



78
79
80
# File 'lib/newflow/workflow.rb', line 78

def current_state=(state)
  @extendee.workflow_state = state.to_s
end

#define_state_query_methodsObject



33
34
35
36
37
38
39
# File 'lib/newflow/workflow.rb', line 33

def define_state_query_methods
  states.keys.each do |key|
    instance_eval "      def \#{key}?; current_state == :\#{key}; end\n    EOS\n  end\nend\n"

#state(name, opts = {}, &block) ⇒ Object



19
20
21
22
# File 'lib/newflow/workflow.rb', line 19

def state(name, opts={}, &block)
  # TODO: Assert we're not overriding a state
  states[name] = State.new(name, opts, &block)
end

#statesObject



15
16
17
# File 'lib/newflow/workflow.rb', line 15

def states
  @states ||= {}
end

#to_dottyObject



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/newflow/workflow.rb', line 82

def to_dotty
  dot = ""
  dot << "digraph {\n"
  states.keys.each { |state_name|
    state = states[state_name]
    # it'd be nice to have the current state somehow shown visually
    shape = "circle"
    if state_name == current_state
      puts "setting current shape to doublecircle #{state_name} vs #{current_state}"
      shape = "doublecircle"
    end
    dot << %Q[  "#{state_name}" [ shape = #{shape} ]; \n]
    state.transitions.each { |transition|
      dot << "  \"#{state_name}\" -> \"#{transition.target_state}\" [ label = \"#{transition.predicate_name}\" ];\n"
    }
  }
  dot << "}\n"
  return dot
end

#transition!(do_trigger = Newflow::WITH_SIDE_EFFECTS) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/newflow/workflow.rb', line 52

def transition!(do_trigger=Newflow::WITH_SIDE_EFFECTS)
  # TODO: watch out for max # of transits
  previous_state = current_state
  previous_states = {}
  num_transitions = 0
  begin
    if previous_states[current_state]
      raise "Error: possible [infinite] loop in workflow, started in: #{previous_state}, currently in #{current_state}, been through all of (#{previous_states.keys.map(&:to_s).sort.join(", ")})" # TODO: TEST
    end
    previous_states[current_state] = true
    the_state = current_state
    transition_once!(do_trigger)
  end while the_state != current_state && states[current_state]
  previous_state == current_state ? nil : current_state
ensure
  @extendee.workflow_state = previous_state unless do_trigger
end

#transition_once!(do_trigger = Newflow::WITH_SIDE_EFFECTS) ⇒ Object



41
42
43
44
45
46
47
48
49
50
# File 'lib/newflow/workflow.rb', line 41

def transition_once!(do_trigger=Newflow::WITH_SIDE_EFFECTS)
  state = states[current_state]
  raise InvalidWorkflowStateError.new(current_state) unless state # TODO: TEST
  target_state = states[state.run(@extendee, do_trigger)]
  if state != target_state
    @extendee.workflow_state = target_state.to_s
    target_state.run_on_entry(@extendee, do_trigger)
  end
  target_state
end

#validate_workflow!Object



8
9
10
11
12
13
# File 'lib/newflow/workflow.rb', line 8

def validate_workflow!
  # TODO: Validate that all transitions reach a valid state
  # TODO: Validate that there is at least one stop state
  raise InvalidStateDefinitionError.new("#{@extendee.class} needs at least two states") if states.size < 2
  raise InvalidStateDefinitionError.new("#{@extendee.class} needs a start of the workflow") unless current_state
end

#would_transition_toObject



70
71
72
# File 'lib/newflow/workflow.rb', line 70

def would_transition_to
  transition!(Newflow::WITHOUT_SIDE_EFFECTS)
end