workflow 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +7 -0
 - data/README.markdown +433 -0
 - data/Rakefile +26 -26
 - data/VERSION +1 -0
 - data/lib/workflow.rb +127 -31
 - data/test/couchtiny_example.rb +46 -0
 - data/test/main_test.rb +420 -0
 - data/test/readme_example.rb +37 -0
 - data/test/without_active_record_test.rb +54 -0
 - data/workflow.rb +1 -0
 - metadata +32 -14
 - data/README.rdoc +0 -452
 
    
        data/VERSION
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            0.4.1
         
     | 
    
        data/lib/workflow.rb
    CHANGED
    
    | 
         @@ -1,38 +1,46 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'rubygems'
         
     | 
| 
       2 
     | 
    
         
            -
            require 'active_support'
         
     | 
| 
       3 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            # See also README.markdown for documentation
         
     | 
| 
       4 
4 
     | 
    
         
             
            module Workflow
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       6 
6 
     | 
    
         
             
              class Specification
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
       8 
8 
     | 
    
         
             
                attr_accessor :states, :initial_state, :meta, :on_transition_proc
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
       10 
10 
     | 
    
         
             
                def initialize(meta = {}, &specification)
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @states = Hash.new
         
     | 
| 
       12 
12 
     | 
    
         
             
                  @meta = meta
         
     | 
| 
       13 
13 
     | 
    
         
             
                  instance_eval(&specification)
         
     | 
| 
       14 
14 
     | 
    
         
             
                end
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def state_names
         
     | 
| 
      
 17 
     | 
    
         
            +
                  states.keys
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
       16 
20 
     | 
    
         
             
                private
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
       18 
22 
     | 
    
         
             
                def state(name, meta = {:meta => {}}, &events_and_etc)
         
     | 
| 
       19 
23 
     | 
    
         
             
                  # meta[:meta] to keep the API consistent..., gah
         
     | 
| 
       20 
     | 
    
         
            -
                  new_state = State.new(name, meta[:meta])
         
     | 
| 
      
 24 
     | 
    
         
            +
                  new_state = Workflow::State.new(name, meta[:meta])
         
     | 
| 
       21 
25 
     | 
    
         
             
                  @initial_state = new_state if @states.empty?
         
     | 
| 
       22 
26 
     | 
    
         
             
                  @states[name.to_sym] = new_state
         
     | 
| 
       23 
27 
     | 
    
         
             
                  @scoped_state = new_state
         
     | 
| 
       24 
28 
     | 
    
         
             
                  instance_eval(&events_and_etc) if events_and_etc
         
     | 
| 
       25 
29 
     | 
    
         
             
                end
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
       27 
31 
     | 
    
         
             
                def event(name, args = {}, &action)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  target = args[:transitions_to] || args[:transition_to]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  raise WorkflowDefinitionError.new(
         
     | 
| 
      
 34 
     | 
    
         
            +
                    "missing ':transitions_to' in workflow event definition for '#{name}'") \
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if target.nil?
         
     | 
| 
       28 
36 
     | 
    
         
             
                  @scoped_state.events[name.to_sym] =
         
     | 
| 
       29 
     | 
    
         
            -
                    Event.new(name,  
     | 
| 
      
 37 
     | 
    
         
            +
                    Workflow::Event.new(name, target, (args[:meta] or {}), &action)
         
     | 
| 
       30 
38 
     | 
    
         
             
                end
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
       32 
40 
     | 
    
         
             
                def on_entry(&proc)
         
     | 
| 
       33 
41 
     | 
    
         
             
                  @scoped_state.on_entry = proc
         
     | 
| 
       34 
42 
     | 
    
         
             
                end
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
       36 
44 
     | 
    
         
             
                def on_exit(&proc)
         
     | 
| 
       37 
45 
     | 
    
         
             
                  @scoped_state.on_exit = proc
         
     | 
| 
       38 
46 
     | 
    
         
             
                end
         
     | 
| 
         @@ -41,7 +49,7 @@ module Workflow 
     | 
|
| 
       41 
49 
     | 
    
         
             
                  @on_transition_proc = proc
         
     | 
| 
       42 
50 
     | 
    
         
             
                end
         
     | 
| 
       43 
51 
     | 
    
         
             
              end
         
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
       45 
53 
     | 
    
         
             
              class TransitionHalted < Exception
         
     | 
| 
       46 
54 
     | 
    
         | 
| 
       47 
55 
     | 
    
         
             
                attr_reader :halted_because
         
     | 
| 
         @@ -57,14 +65,16 @@ module Workflow 
     | 
|
| 
       57 
65 
     | 
    
         | 
| 
       58 
66 
     | 
    
         
             
              class WorkflowError < Exception; end
         
     | 
| 
       59 
67 
     | 
    
         | 
| 
      
 68 
     | 
    
         
            +
              class WorkflowDefinitionError < Exception; end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
       60 
70 
     | 
    
         
             
              class State
         
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
       62 
72 
     | 
    
         
             
                attr_accessor :name, :events, :meta, :on_entry, :on_exit
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
       64 
74 
     | 
    
         
             
                def initialize(name, meta = {})
         
     | 
| 
       65 
75 
     | 
    
         
             
                  @name, @events, @meta = name, Hash.new, meta
         
     | 
| 
       66 
76 
     | 
    
         
             
                end
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
       68 
78 
     | 
    
         
             
                def to_s
         
     | 
| 
       69 
79 
     | 
    
         
             
                  "#{name}"
         
     | 
| 
       70 
80 
     | 
    
         
             
                end
         
     | 
| 
         @@ -73,20 +83,29 @@ module Workflow 
     | 
|
| 
       73 
83 
     | 
    
         
             
                  name.to_sym
         
     | 
| 
       74 
84 
     | 
    
         
             
                end
         
     | 
| 
       75 
85 
     | 
    
         
             
              end
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
       77 
87 
     | 
    
         
             
              class Event
         
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
       79 
89 
     | 
    
         
             
                attr_accessor :name, :transitions_to, :meta, :action
         
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
       81 
91 
     | 
    
         
             
                def initialize(name, transitions_to, meta = {}, &action)
         
     | 
| 
       82 
92 
     | 
    
         
             
                  @name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
         
     | 
| 
       83 
93 
     | 
    
         
             
                end
         
     | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
       85 
95 
     | 
    
         
             
              end
         
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
       87 
97 
     | 
    
         
             
              module WorkflowClassMethods
         
     | 
| 
       88 
98 
     | 
    
         
             
                attr_reader :workflow_spec
         
     | 
| 
       89 
99 
     | 
    
         | 
| 
      
 100 
     | 
    
         
            +
                def workflow_column(column_name=nil)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  if column_name
         
     | 
| 
      
 102 
     | 
    
         
            +
                    @workflow_state_column_name = column_name.to_sym
         
     | 
| 
      
 103 
     | 
    
         
            +
                  else
         
     | 
| 
      
 104 
     | 
    
         
            +
                    @workflow_state_column_name ||= :workflow_state
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                  @workflow_state_column_name
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
       90 
109 
     | 
    
         
             
                def workflow(&specification)
         
     | 
| 
       91 
110 
     | 
    
         
             
                  @workflow_spec = Specification.new(Hash.new, &specification)
         
     | 
| 
       92 
111 
     | 
    
         
             
                  @workflow_spec.states.values.each do |state|
         
     | 
| 
         @@ -110,7 +129,7 @@ module Workflow 
     | 
|
| 
       110 
129 
     | 
    
         
             
              end
         
     | 
| 
       111 
130 
     | 
    
         | 
| 
       112 
131 
     | 
    
         
             
              module WorkflowInstanceMethods
         
     | 
| 
       113 
     | 
    
         
            -
                def current_state 
     | 
| 
      
 132 
     | 
    
         
            +
                def current_state
         
     | 
| 
       114 
133 
     | 
    
         
             
                  loaded_state = load_workflow_state
         
     | 
| 
       115 
134 
     | 
    
         
             
                  res = spec.states[loaded_state.to_sym] if loaded_state
         
     | 
| 
       116 
135 
     | 
    
         
             
                  res || spec.initial_state
         
     | 
| 
         @@ -129,6 +148,8 @@ module Workflow 
     | 
|
| 
       129 
148 
     | 
    
         
             
                  raise NoTransitionAllowed.new(
         
     | 
| 
       130 
149 
     | 
    
         
             
                    "There is no event #{name.to_sym} defined for the #{current_state} state") \
         
     | 
| 
       131 
150 
     | 
    
         
             
                    if event.nil?
         
     | 
| 
      
 151 
     | 
    
         
            +
                  # This three member variables are a relict from the old workflow library
         
     | 
| 
      
 152 
     | 
    
         
            +
                  # TODO: refactor some day
         
     | 
| 
       132 
153 
     | 
    
         
             
                  @halted_because = nil
         
     | 
| 
       133 
154 
     | 
    
         
             
                  @halted = false
         
     | 
| 
       134 
155 
     | 
    
         
             
                  @raise_exception_on_halt = false
         
     | 
| 
         @@ -150,18 +171,18 @@ module Workflow 
     | 
|
| 
       150 
171 
     | 
    
         
             
                private
         
     | 
| 
       151 
172 
     | 
    
         | 
| 
       152 
173 
     | 
    
         
             
                def check_transition(event)
         
     | 
| 
       153 
     | 
    
         
            -
                  # Create a meaningful error message instead of 
     | 
| 
      
 174 
     | 
    
         
            +
                  # Create a meaningful error message instead of
         
     | 
| 
       154 
175 
     | 
    
         
             
                  # "undefined method `on_entry' for nil:NilClass"
         
     | 
| 
       155 
176 
     | 
    
         
             
                  # Reported by Kyle Burton
         
     | 
| 
       156 
177 
     | 
    
         
             
                  if !spec.states[event.transitions_to]
         
     | 
| 
       157 
     | 
    
         
            -
                    raise WorkflowError.new("Event[#{event.name}]'s " + 
     | 
| 
      
 178 
     | 
    
         
            +
                    raise WorkflowError.new("Event[#{event.name}]'s " +
         
     | 
| 
       158 
179 
     | 
    
         
             
                        "transitions_to[#{event.transitions_to}] is not a declared state.")
         
     | 
| 
       159 
180 
     | 
    
         
             
                  end
         
     | 
| 
       160 
181 
     | 
    
         
             
                end
         
     | 
| 
       161 
182 
     | 
    
         | 
| 
       162 
183 
     | 
    
         
             
                def spec
         
     | 
| 
       163 
184 
     | 
    
         
             
                  c = self.class
         
     | 
| 
       164 
     | 
    
         
            -
                  # using a simple loop instead of class_inheritable_accessor to avoid 
     | 
| 
      
 185 
     | 
    
         
            +
                  # using a simple loop instead of class_inheritable_accessor to avoid
         
     | 
| 
       165 
186 
     | 
    
         
             
                  # dependency on Rails' ActiveSupport
         
     | 
| 
       166 
187 
     | 
    
         
             
                  until c.workflow_spec || !(c.include? Workflow)
         
     | 
| 
       167 
188 
     | 
    
         
             
                    c = c.superclass
         
     | 
| 
         @@ -199,9 +220,9 @@ module Workflow 
     | 
|
| 
       199 
220 
     | 
    
         
             
                  self.send action_name.to_sym, *args if self.respond_to?(action_name.to_sym)
         
     | 
| 
       200 
221 
     | 
    
         
             
                end
         
     | 
| 
       201 
222 
     | 
    
         | 
| 
       202 
     | 
    
         
            -
                def run_on_entry(state, prior_state, triggering_event, *args) 
     | 
| 
      
 223 
     | 
    
         
            +
                def run_on_entry(state, prior_state, triggering_event, *args)
         
     | 
| 
       203 
224 
     | 
    
         
             
                  if state.on_entry
         
     | 
| 
       204 
     | 
    
         
            -
                    instance_exec(prior_state.name, triggering_event, *args, &state.on_entry) 
     | 
| 
      
 225 
     | 
    
         
            +
                    instance_exec(prior_state.name, triggering_event, *args, &state.on_entry)
         
     | 
| 
       205 
226 
     | 
    
         
             
                  else
         
     | 
| 
       206 
227 
     | 
    
         
             
                    hook_name = "on_#{state}_entry"
         
     | 
| 
       207 
228 
     | 
    
         
             
                    self.send hook_name, prior_state, triggering_event, *args if self.respond_to? hook_name
         
     | 
| 
         @@ -237,13 +258,13 @@ module Workflow 
     | 
|
| 
       237 
258 
     | 
    
         | 
| 
       238 
259 
     | 
    
         
             
              module ActiveRecordInstanceMethods
         
     | 
| 
       239 
260 
     | 
    
         
             
                def load_workflow_state
         
     | 
| 
       240 
     | 
    
         
            -
                  read_attribute( 
     | 
| 
      
 261 
     | 
    
         
            +
                  read_attribute(self.class.workflow_column)
         
     | 
| 
       241 
262 
     | 
    
         
             
                end
         
     | 
| 
       242 
263 
     | 
    
         | 
| 
       243 
264 
     | 
    
         
             
                # On transition the new workflow state is immediately saved in the
         
     | 
| 
       244 
265 
     | 
    
         
             
                # database.
         
     | 
| 
       245 
266 
     | 
    
         
             
                def persist_workflow_state(new_value)
         
     | 
| 
       246 
     | 
    
         
            -
                  update_attribute  
     | 
| 
      
 267 
     | 
    
         
            +
                  update_attribute self.class.workflow_column, new_value
         
     | 
| 
       247 
268 
     | 
    
         
             
                end
         
     | 
| 
       248 
269 
     | 
    
         | 
| 
       249 
270 
     | 
    
         
             
                private
         
     | 
| 
         @@ -254,7 +275,17 @@ module Workflow 
     | 
|
| 
       254 
275 
     | 
    
         
             
                # state. That's why it is important to save the string with the name of the
         
     | 
| 
       255 
276 
     | 
    
         
             
                # initial state in all the new records.
         
     | 
| 
       256 
277 
     | 
    
         
             
                def write_initial_state
         
     | 
| 
       257 
     | 
    
         
            -
                  write_attribute  
     | 
| 
      
 278 
     | 
    
         
            +
                  write_attribute self.class.workflow_column, current_state.to_s
         
     | 
| 
      
 279 
     | 
    
         
            +
                end
         
     | 
| 
      
 280 
     | 
    
         
            +
              end
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
              module RemodelInstanceMethods
         
     | 
| 
      
 283 
     | 
    
         
            +
                def load_workflow_state
         
     | 
| 
      
 284 
     | 
    
         
            +
                  send(self.class.workflow_column)
         
     | 
| 
      
 285 
     | 
    
         
            +
                end
         
     | 
| 
      
 286 
     | 
    
         
            +
             
     | 
| 
      
 287 
     | 
    
         
            +
                def persist_workflow_state(new_value)
         
     | 
| 
      
 288 
     | 
    
         
            +
                  update(self.class.workflow_column => new_value)
         
     | 
| 
       258 
289 
     | 
    
         
             
                end
         
     | 
| 
       259 
290 
     | 
    
         
             
              end
         
     | 
| 
       260 
291 
     | 
    
         | 
| 
         @@ -263,9 +294,74 @@ module Workflow 
     | 
|
| 
       263 
294 
     | 
    
         
             
                klass.extend WorkflowClassMethods
         
     | 
| 
       264 
295 
     | 
    
         
             
                if Object.const_defined?(:ActiveRecord)
         
     | 
| 
       265 
296 
     | 
    
         
             
                  if klass < ActiveRecord::Base
         
     | 
| 
       266 
     | 
    
         
            -
             
     | 
| 
       267 
     | 
    
         
            -
             
     | 
| 
      
 297 
     | 
    
         
            +
                    klass.send :include, ActiveRecordInstanceMethods
         
     | 
| 
      
 298 
     | 
    
         
            +
                    klass.before_validation :write_initial_state
         
     | 
| 
      
 299 
     | 
    
         
            +
                  end
         
     | 
| 
      
 300 
     | 
    
         
            +
                elsif Object.const_defined?(:Remodel)
         
     | 
| 
      
 301 
     | 
    
         
            +
                  if klass < Remodel::Entity
         
     | 
| 
      
 302 
     | 
    
         
            +
                    klass.send :include, RemodelInstanceMethods
         
     | 
| 
      
 303 
     | 
    
         
            +
                  end
         
     | 
| 
      
 304 
     | 
    
         
            +
                end
         
     | 
| 
      
 305 
     | 
    
         
            +
              end
         
     | 
| 
      
 306 
     | 
    
         
            +
             
     | 
| 
      
 307 
     | 
    
         
            +
              # Generates a `dot` graph of the workflow.
         
     | 
| 
      
 308 
     | 
    
         
            +
              # Prerequisite: the `dot` binary.
         
     | 
| 
      
 309 
     | 
    
         
            +
              # You can use it in your own Rakefile like this:
         
     | 
| 
      
 310 
     | 
    
         
            +
              #
         
     | 
| 
      
 311 
     | 
    
         
            +
              #     namespace :doc do
         
     | 
| 
      
 312 
     | 
    
         
            +
              #       desc "Generate a graph of the workflow."
         
     | 
| 
      
 313 
     | 
    
         
            +
              #       task :workflow do
         
     | 
| 
      
 314 
     | 
    
         
            +
              #         Workflow::create_workflow_diagram(Order.new)
         
     | 
| 
      
 315 
     | 
    
         
            +
              #       end
         
     | 
| 
      
 316 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 317 
     | 
    
         
            +
              #
         
     | 
| 
      
 318 
     | 
    
         
            +
              # You can influence the placement of nodes by specifying
         
     | 
| 
      
 319 
     | 
    
         
            +
              # additional meta information in your states and transition descriptions.
         
     | 
| 
      
 320 
     | 
    
         
            +
              # You can assign higher `doc_weight` value to the typical transitions
         
     | 
| 
      
 321 
     | 
    
         
            +
              # in your workflow. All other states and transitions will be arranged
         
     | 
| 
      
 322 
     | 
    
         
            +
              # around that main line. See also `weight` in the graphviz documentation.
         
     | 
| 
      
 323 
     | 
    
         
            +
              # Example:
         
     | 
| 
      
 324 
     | 
    
         
            +
              #
         
     | 
| 
      
 325 
     | 
    
         
            +
              #     state :new do
         
     | 
| 
      
 326 
     | 
    
         
            +
              #       event :approve, :transitions_to => :approved, :meta => {:doc_weight => 8}
         
     | 
| 
      
 327 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 328 
     | 
    
         
            +
              #
         
     | 
| 
      
 329 
     | 
    
         
            +
              #
         
     | 
| 
      
 330 
     | 
    
         
            +
              # @param klass A class with the Workflow mixin, for which you wish the graphical workflow representation
         
     | 
| 
      
 331 
     | 
    
         
            +
              # @param [String] target_dir Directory, where to save the dot and the pdf files
         
     | 
| 
      
 332 
     | 
    
         
            +
              # @param [String] graph_options You can change graph orientation, size etc. See graphviz documentation
         
     | 
| 
      
 333 
     | 
    
         
            +
              def self.create_workflow_diagram(klass, target_dir, graph_options='rankdir="LR", size="7,11.6", ratio="fill"')
         
     | 
| 
      
 334 
     | 
    
         
            +
                workflow_name = "#{klass.name.tableize}_workflow"
         
     | 
| 
      
 335 
     | 
    
         
            +
                fname = File.join(target_dir, "generated_#{workflow_name}")
         
     | 
| 
      
 336 
     | 
    
         
            +
                File.open("#{fname}.dot", 'w') do |file|
         
     | 
| 
      
 337 
     | 
    
         
            +
                  file.puts %Q|
         
     | 
| 
      
 338 
     | 
    
         
            +
            digraph #{workflow_name} {
         
     | 
| 
      
 339 
     | 
    
         
            +
              graph [#{graph_options}];
         
     | 
| 
      
 340 
     | 
    
         
            +
              node [shape=box];
         
     | 
| 
      
 341 
     | 
    
         
            +
              edge [len=1];
         
     | 
| 
      
 342 
     | 
    
         
            +
                  |
         
     | 
| 
      
 343 
     | 
    
         
            +
             
     | 
| 
      
 344 
     | 
    
         
            +
                  klass.workflow_spec.states.each do |state_name, state|
         
     | 
| 
      
 345 
     | 
    
         
            +
                    file.puts %Q{  #{state.name} [label="#{state.name}"];}
         
     | 
| 
      
 346 
     | 
    
         
            +
                    state.events.each do |event_name, event|
         
     | 
| 
      
 347 
     | 
    
         
            +
                      meta_info = event.meta
         
     | 
| 
      
 348 
     | 
    
         
            +
                      if meta_info[:doc_weight]
         
     | 
| 
      
 349 
     | 
    
         
            +
                        weight_prop = ", weight=#{meta_info[:doc_weight]}"
         
     | 
| 
      
 350 
     | 
    
         
            +
                      else
         
     | 
| 
      
 351 
     | 
    
         
            +
                        weight_prop = ''
         
     | 
| 
      
 352 
     | 
    
         
            +
                      end
         
     | 
| 
      
 353 
     | 
    
         
            +
                      file.puts %Q{  #{state.name} -> #{event.transitions_to} [label="#{event_name.to_s.humanize}" #{weight_prop}];}
         
     | 
| 
      
 354 
     | 
    
         
            +
                    end
         
     | 
| 
       268 
355 
     | 
    
         
             
                  end
         
     | 
| 
      
 356 
     | 
    
         
            +
                  file.puts "}"
         
     | 
| 
      
 357 
     | 
    
         
            +
                  file.puts
         
     | 
| 
       269 
358 
     | 
    
         
             
                end
         
     | 
| 
      
 359 
     | 
    
         
            +
                `dot -Tpdf -o#{fname}.pdf #{fname}.dot`
         
     | 
| 
      
 360 
     | 
    
         
            +
                puts "
         
     | 
| 
      
 361 
     | 
    
         
            +
            Please run the following to open the generated file:
         
     | 
| 
      
 362 
     | 
    
         
            +
             
     | 
| 
      
 363 
     | 
    
         
            +
            open #{fname}.pdf
         
     | 
| 
      
 364 
     | 
    
         
            +
             
     | 
| 
      
 365 
     | 
    
         
            +
            "
         
     | 
| 
       270 
366 
     | 
    
         
             
              end
         
     | 
| 
       271 
367 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), 'test_helper')
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'couchtiny'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'couchtiny/document'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'workflow'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class User < CouchTiny::Document
         
     | 
| 
      
 7 
     | 
    
         
            +
              include Workflow
         
     | 
| 
      
 8 
     | 
    
         
            +
              workflow do
         
     | 
| 
      
 9 
     | 
    
         
            +
                state :submitted do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  event :activate_via_link, :transitions_to => :proved_email
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
                state :proved_email
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def load_workflow_state
         
     | 
| 
      
 16 
     | 
    
         
            +
                self[:workflow_state]
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def persist_workflow_state(new_value)
         
     | 
| 
      
 20 
     | 
    
         
            +
                self[:workflow_state] = new_value
         
     | 
| 
      
 21 
     | 
    
         
            +
                save!
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            class CouchtinyExample < Test::Unit::TestCase
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 29 
     | 
    
         
            +
                db = CouchTiny::Database.url("http://127.0.0.1:5984/test-workflow")
         
     | 
| 
      
 30 
     | 
    
         
            +
                db.delete_database! rescue nil
         
     | 
| 
      
 31 
     | 
    
         
            +
                db.create_database!
         
     | 
| 
      
 32 
     | 
    
         
            +
                User.use_database db
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              test 'CouchDB persistence' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                user = User.new :email => 'manya@example.com'
         
     | 
| 
      
 37 
     | 
    
         
            +
                user.save!
         
     | 
| 
      
 38 
     | 
    
         
            +
                assert user.submitted?
         
     | 
| 
      
 39 
     | 
    
         
            +
                user.activate_via_link!
         
     | 
| 
      
 40 
     | 
    
         
            +
                assert user.proved_email?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                reloaded_user = User.get user.id
         
     | 
| 
      
 43 
     | 
    
         
            +
                puts reloaded_user.inspect
         
     | 
| 
      
 44 
     | 
    
         
            +
                assert reloaded_user.proved_email?, 'Reloaded user should have the desired workflow state'
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
    
        data/test/main_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,420 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), 'test_helper')
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            $VERBOSE = false
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'sqlite3'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'workflow'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'mocha'
         
     | 
| 
      
 8 
     | 
    
         
            +
            #require 'ruby-debug'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ActiveRecord::Migration.verbose = false
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            class Order < ActiveRecord::Base
         
     | 
| 
      
 13 
     | 
    
         
            +
              include Workflow
         
     | 
| 
      
 14 
     | 
    
         
            +
              workflow do
         
     | 
| 
      
 15 
     | 
    
         
            +
                state :submitted do
         
     | 
| 
      
 16 
     | 
    
         
            +
                  event :accept, :transitions_to => :accepted, :meta => {:doc_weight => 8} do |reviewer, args|
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
                state :accepted do
         
     | 
| 
      
 20 
     | 
    
         
            +
                  event :ship, :transitions_to => :shipped
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
                state :shipped
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            class LegacyOrder < ActiveRecord::Base
         
     | 
| 
      
 28 
     | 
    
         
            +
              include Workflow
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              workflow_column :foo_bar # use this legacy database column for persistence
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              workflow do
         
     | 
| 
      
 33 
     | 
    
         
            +
                state :submitted do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  event :accept, :transitions_to => :accepted, :meta => {:doc_weight => 8} do |reviewer, args|
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
                state :accepted do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  event :ship, :transitions_to => :shipped
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
                state :shipped
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            class MainTest < Test::Unit::TestCase
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              def exec(sql)
         
     | 
| 
      
 49 
     | 
    
         
            +
                ActiveRecord::Base.connection.execute sql
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 53 
     | 
    
         
            +
                ActiveRecord::Base.establish_connection(
         
     | 
| 
      
 54 
     | 
    
         
            +
                  :adapter => "sqlite3",
         
     | 
| 
      
 55 
     | 
    
         
            +
                  :database  => ":memory:" #"tmp/test"
         
     | 
| 
      
 56 
     | 
    
         
            +
                )
         
     | 
| 
      
 57 
     | 
    
         
            +
                ActiveRecord::Base.connection.reconnect! # eliminate ActiveRecord warning. TODO: delete as soon as ActiveRecord is fixed
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                ActiveRecord::Schema.define do
         
     | 
| 
      
 60 
     | 
    
         
            +
                  create_table :orders do |t|
         
     | 
| 
      
 61 
     | 
    
         
            +
                    t.string :title, :null => false
         
     | 
| 
      
 62 
     | 
    
         
            +
                    t.string :workflow_state
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                exec "INSERT INTO orders(title, workflow_state) VALUES('some order', 'accepted')"
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                ActiveRecord::Schema.define do
         
     | 
| 
      
 69 
     | 
    
         
            +
                  create_table :legacy_orders do |t|
         
     | 
| 
      
 70 
     | 
    
         
            +
                    t.string :title, :null => false
         
     | 
| 
      
 71 
     | 
    
         
            +
                    t.string :foo_bar
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                exec "INSERT INTO legacy_orders(title, foo_bar) VALUES('some order', 'accepted')"
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              def teardown
         
     | 
| 
      
 80 
     | 
    
         
            +
                ActiveRecord::Base.connection.disconnect!
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              def assert_state(title, expected_state, klass = Order)
         
     | 
| 
      
 84 
     | 
    
         
            +
                o = klass.find_by_title(title)
         
     | 
| 
      
 85 
     | 
    
         
            +
                assert_equal expected_state, o.read_attribute(klass.workflow_column)
         
     | 
| 
      
 86 
     | 
    
         
            +
                o
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
              test 'immediately save the new workflow_state on state machine transition' do
         
     | 
| 
      
 90 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 91 
     | 
    
         
            +
                o.ship!
         
     | 
| 
      
 92 
     | 
    
         
            +
                assert_state 'some order', 'shipped'
         
     | 
| 
      
 93 
     | 
    
         
            +
              end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
              test 'immediately save the new workflow_state on state machine transition with custom column name' do
         
     | 
| 
      
 96 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted', LegacyOrder
         
     | 
| 
      
 97 
     | 
    
         
            +
                o.ship!
         
     | 
| 
      
 98 
     | 
    
         
            +
                assert_state 'some order', 'shipped', LegacyOrder
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              test 'persist workflow_state in the db and reload' do
         
     | 
| 
      
 102 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 103 
     | 
    
         
            +
                assert_equal :accepted, o.current_state.name
         
     | 
| 
      
 104 
     | 
    
         
            +
                o.ship!
         
     | 
| 
      
 105 
     | 
    
         
            +
                o.save!
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                assert_state 'some order', 'shipped'
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                o.reload
         
     | 
| 
      
 110 
     | 
    
         
            +
                assert_equal 'shipped', o.read_attribute(:workflow_state)
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              test 'persist workflow_state in the db with_custom_name and reload' do
         
     | 
| 
      
 114 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted', LegacyOrder
         
     | 
| 
      
 115 
     | 
    
         
            +
                assert_equal :accepted, o.current_state.name
         
     | 
| 
      
 116 
     | 
    
         
            +
                o.ship!
         
     | 
| 
      
 117 
     | 
    
         
            +
                o.save!
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                assert_state 'some order', 'shipped', LegacyOrder
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                o.reload
         
     | 
| 
      
 122 
     | 
    
         
            +
                assert_equal 'shipped', o.read_attribute(:foo_bar)
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
              test 'default workflow column should be workflow_state' do
         
     | 
| 
      
 126 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 127 
     | 
    
         
            +
                assert_equal :workflow_state, o.class.workflow_column
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              test 'custom workflow column should be foo_bar' do
         
     | 
| 
      
 131 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted', LegacyOrder
         
     | 
| 
      
 132 
     | 
    
         
            +
                assert_equal :foo_bar, o.class.workflow_column
         
     | 
| 
      
 133 
     | 
    
         
            +
              end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
              test 'access workflow specification' do
         
     | 
| 
      
 136 
     | 
    
         
            +
                assert_equal 3, Order.workflow_spec.states.length
         
     | 
| 
      
 137 
     | 
    
         
            +
                assert_equal ['submitted', 'accepted', 'shipped'].sort,
         
     | 
| 
      
 138 
     | 
    
         
            +
                  Order.workflow_spec.state_names.map{|n| n.to_s}.sort
         
     | 
| 
      
 139 
     | 
    
         
            +
              end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
              test 'current state object' do
         
     | 
| 
      
 142 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 143 
     | 
    
         
            +
                assert_equal 'accepted', o.current_state.to_s
         
     | 
| 
      
 144 
     | 
    
         
            +
                assert_equal 1, o.current_state.events.length
         
     | 
| 
      
 145 
     | 
    
         
            +
              end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
              test 'on_entry and on_exit invoked' do
         
     | 
| 
      
 148 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 149 
     | 
    
         
            +
                callbacks = mock()
         
     | 
| 
      
 150 
     | 
    
         
            +
                callbacks.expects(:my_on_exit_new).once
         
     | 
| 
      
 151 
     | 
    
         
            +
                callbacks.expects(:my_on_entry_old).once
         
     | 
| 
      
 152 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 153 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 154 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 155 
     | 
    
         
            +
                    state :new do
         
     | 
| 
      
 156 
     | 
    
         
            +
                      event :age, :transitions_to => :old
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    on_exit do
         
     | 
| 
      
 159 
     | 
    
         
            +
                      callbacks.my_on_exit_new
         
     | 
| 
      
 160 
     | 
    
         
            +
                    end
         
     | 
| 
      
 161 
     | 
    
         
            +
                    state :old
         
     | 
| 
      
 162 
     | 
    
         
            +
                    on_entry do
         
     | 
| 
      
 163 
     | 
    
         
            +
                      callbacks.my_on_entry_old
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
                    on_exit do
         
     | 
| 
      
 166 
     | 
    
         
            +
                      fail "wrong on_exit executed"
         
     | 
| 
      
 167 
     | 
    
         
            +
                    end
         
     | 
| 
      
 168 
     | 
    
         
            +
                  end
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                o = c.new
         
     | 
| 
      
 172 
     | 
    
         
            +
                assert_equal 'new', o.current_state.to_s
         
     | 
| 
      
 173 
     | 
    
         
            +
                o.age!
         
     | 
| 
      
 174 
     | 
    
         
            +
              end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
              test 'on_transition invoked' do
         
     | 
| 
      
 177 
     | 
    
         
            +
                callbacks = mock()
         
     | 
| 
      
 178 
     | 
    
         
            +
                callbacks.expects(:on_tran).once # this is validated at the end
         
     | 
| 
      
 179 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 180 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 181 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 182 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 183 
     | 
    
         
            +
                    state :one do
         
     | 
| 
      
 184 
     | 
    
         
            +
                      event :increment, :transitions_to => :two
         
     | 
| 
      
 185 
     | 
    
         
            +
                    end
         
     | 
| 
      
 186 
     | 
    
         
            +
                    state :two
         
     | 
| 
      
 187 
     | 
    
         
            +
                    on_transition do |from, to, triggering_event, *event_args|
         
     | 
| 
      
 188 
     | 
    
         
            +
                      callbacks.on_tran
         
     | 
| 
      
 189 
     | 
    
         
            +
                    end
         
     | 
| 
      
 190 
     | 
    
         
            +
                  end
         
     | 
| 
      
 191 
     | 
    
         
            +
                end
         
     | 
| 
      
 192 
     | 
    
         
            +
                assert_not_nil c.workflow_spec.on_transition_proc
         
     | 
| 
      
 193 
     | 
    
         
            +
                c.new.increment!
         
     | 
| 
      
 194 
     | 
    
         
            +
              end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
              test 'access event meta information' do
         
     | 
| 
      
 197 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 198 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 199 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 200 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 201 
     | 
    
         
            +
                    state :main, :meta => {:importance => 8}
         
     | 
| 
      
 202 
     | 
    
         
            +
                    state :supplemental, :meta => {:importance => 1}
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
                end
         
     | 
| 
      
 205 
     | 
    
         
            +
                assert_equal 1, c.workflow_spec.states[:supplemental].meta[:importance]
         
     | 
| 
      
 206 
     | 
    
         
            +
              end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
              test 'initial state' do
         
     | 
| 
      
 209 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 210 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 211 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 212 
     | 
    
         
            +
                  workflow { state :one; state :two }
         
     | 
| 
      
 213 
     | 
    
         
            +
                end
         
     | 
| 
      
 214 
     | 
    
         
            +
                assert_equal 'one', c.new.current_state.to_s
         
     | 
| 
      
 215 
     | 
    
         
            +
              end
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
      
 217 
     | 
    
         
            +
              test 'nil as initial state' do
         
     | 
| 
      
 218 
     | 
    
         
            +
                exec "INSERT INTO orders(title, workflow_state) VALUES('nil state', NULL)"
         
     | 
| 
      
 219 
     | 
    
         
            +
                o = Order.find_by_title('nil state')
         
     | 
| 
      
 220 
     | 
    
         
            +
                assert o.submitted?, 'if workflow_state is nil, the initial state should be assumed'
         
     | 
| 
      
 221 
     | 
    
         
            +
                assert !o.shipped?
         
     | 
| 
      
 222 
     | 
    
         
            +
              end
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
              test 'initial state immediately set as ActiveRecord attribute for new objects' do
         
     | 
| 
      
 225 
     | 
    
         
            +
                o = Order.create(:title => 'new object')
         
     | 
| 
      
 226 
     | 
    
         
            +
                assert_equal 'submitted', o.read_attribute(:workflow_state)
         
     | 
| 
      
 227 
     | 
    
         
            +
              end
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
      
 229 
     | 
    
         
            +
              test 'question methods for state' do
         
     | 
| 
      
 230 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 231 
     | 
    
         
            +
                assert o.accepted?
         
     | 
| 
      
 232 
     | 
    
         
            +
                assert !o.shipped?
         
     | 
| 
      
 233 
     | 
    
         
            +
              end
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
              test 'correct exception for event, that is not allowed in current state' do
         
     | 
| 
      
 236 
     | 
    
         
            +
                o = assert_state 'some order', 'accepted'
         
     | 
| 
      
 237 
     | 
    
         
            +
                assert_raise Workflow::NoTransitionAllowed do
         
     | 
| 
      
 238 
     | 
    
         
            +
                  o.accept!
         
     | 
| 
      
 239 
     | 
    
         
            +
                end
         
     | 
| 
      
 240 
     | 
    
         
            +
              end
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
              test 'multiple events with the same name and different arguments lists from different states'
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
              test 'implicit transition callback' do
         
     | 
| 
      
 245 
     | 
    
         
            +
                args = mock()
         
     | 
| 
      
 246 
     | 
    
         
            +
                args.expects(:my_tran).once # this is validated at the end
         
     | 
| 
      
 247 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 248 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 249 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 250 
     | 
    
         
            +
                  def my_transition(args)
         
     | 
| 
      
 251 
     | 
    
         
            +
                    args.my_tran
         
     | 
| 
      
 252 
     | 
    
         
            +
                  end
         
     | 
| 
      
 253 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 254 
     | 
    
         
            +
                    state :one do
         
     | 
| 
      
 255 
     | 
    
         
            +
                      event :my_transition, :transitions_to => :two
         
     | 
| 
      
 256 
     | 
    
         
            +
                    end
         
     | 
| 
      
 257 
     | 
    
         
            +
                    state :two
         
     | 
| 
      
 258 
     | 
    
         
            +
                  end
         
     | 
| 
      
 259 
     | 
    
         
            +
                end
         
     | 
| 
      
 260 
     | 
    
         
            +
                c.new.my_transition!(args)
         
     | 
| 
      
 261 
     | 
    
         
            +
              end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
              test 'Single table inheritance (STI)' do
         
     | 
| 
      
 264 
     | 
    
         
            +
                class BigOrder < Order
         
     | 
| 
      
 265 
     | 
    
         
            +
                end
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                bo = BigOrder.new
         
     | 
| 
      
 268 
     | 
    
         
            +
                assert bo.submitted?
         
     | 
| 
      
 269 
     | 
    
         
            +
                assert !bo.accepted?
         
     | 
| 
      
 270 
     | 
    
         
            +
              end
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
              test 'Two-level inheritance' do
         
     | 
| 
      
 273 
     | 
    
         
            +
                class BigOrder < Order
         
     | 
| 
      
 274 
     | 
    
         
            +
                end
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                class EvenBiggerOrder < BigOrder
         
     | 
| 
      
 277 
     | 
    
         
            +
                end
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
                assert EvenBiggerOrder.new.submitted?
         
     | 
| 
      
 280 
     | 
    
         
            +
              end
         
     | 
| 
      
 281 
     | 
    
         
            +
             
     | 
| 
      
 282 
     | 
    
         
            +
              test 'Iheritance with workflow definition override' do
         
     | 
| 
      
 283 
     | 
    
         
            +
                class BigOrder < Order
         
     | 
| 
      
 284 
     | 
    
         
            +
                end
         
     | 
| 
      
 285 
     | 
    
         
            +
             
     | 
| 
      
 286 
     | 
    
         
            +
                class SpecialBigOrder < BigOrder
         
     | 
| 
      
 287 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 288 
     | 
    
         
            +
                    state :start_big
         
     | 
| 
      
 289 
     | 
    
         
            +
                  end
         
     | 
| 
      
 290 
     | 
    
         
            +
                end
         
     | 
| 
      
 291 
     | 
    
         
            +
             
     | 
| 
      
 292 
     | 
    
         
            +
                special = SpecialBigOrder.new
         
     | 
| 
      
 293 
     | 
    
         
            +
                assert_equal 'start_big', special.current_state.to_s
         
     | 
| 
      
 294 
     | 
    
         
            +
              end
         
     | 
| 
      
 295 
     | 
    
         
            +
             
     | 
| 
      
 296 
     | 
    
         
            +
              test 'Better error message for missing target state' do
         
     | 
| 
      
 297 
     | 
    
         
            +
                class Problem
         
     | 
| 
      
 298 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 299 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 300 
     | 
    
         
            +
                    state :initial do
         
     | 
| 
      
 301 
     | 
    
         
            +
                      event :solve, :transitions_to => :solved
         
     | 
| 
      
 302 
     | 
    
         
            +
                    end
         
     | 
| 
      
 303 
     | 
    
         
            +
                  end
         
     | 
| 
      
 304 
     | 
    
         
            +
                end
         
     | 
| 
      
 305 
     | 
    
         
            +
                assert_raise Workflow::WorkflowError do
         
     | 
| 
      
 306 
     | 
    
         
            +
                  Problem.new.solve!
         
     | 
| 
      
 307 
     | 
    
         
            +
                end
         
     | 
| 
      
 308 
     | 
    
         
            +
              end
         
     | 
| 
      
 309 
     | 
    
         
            +
             
     | 
| 
      
 310 
     | 
    
         
            +
              # Intermixing of transition graph definition (states, transitions)
         
     | 
| 
      
 311 
     | 
    
         
            +
              # on the one side and implementation of the actions on the other side
         
     | 
| 
      
 312 
     | 
    
         
            +
              # for a bigger state machine can introduce clutter.
         
     | 
| 
      
 313 
     | 
    
         
            +
              #
         
     | 
| 
      
 314 
     | 
    
         
            +
              # To reduce this clutter it is now possible to use state entry- and
         
     | 
| 
      
 315 
     | 
    
         
            +
              # exit- hooks defined through a naming convention. For example, if there
         
     | 
| 
      
 316 
     | 
    
         
            +
              # is a state :pending, then you can hook in by defining method
         
     | 
| 
      
 317 
     | 
    
         
            +
              # `def on_pending_exit(new_state, event, *args)` instead of using a
         
     | 
| 
      
 318 
     | 
    
         
            +
              # block:
         
     | 
| 
      
 319 
     | 
    
         
            +
              #
         
     | 
| 
      
 320 
     | 
    
         
            +
              #     state :pending do
         
     | 
| 
      
 321 
     | 
    
         
            +
              #       on_entry do
         
     | 
| 
      
 322 
     | 
    
         
            +
              #         # your implementation here
         
     | 
| 
      
 323 
     | 
    
         
            +
              #       end
         
     | 
| 
      
 324 
     | 
    
         
            +
              #     end
         
     | 
| 
      
 325 
     | 
    
         
            +
              #
         
     | 
| 
      
 326 
     | 
    
         
            +
              # If both a function with a name according to naming convention and the
         
     | 
| 
      
 327 
     | 
    
         
            +
              # on_entry/on_exit block are given, then only on_entry/on_exit block is used.
         
     | 
| 
      
 328 
     | 
    
         
            +
              test 'on_entry and on_exit hooks in separate methods' do
         
     | 
| 
      
 329 
     | 
    
         
            +
                c = Class.new
         
     | 
| 
      
 330 
     | 
    
         
            +
                c.class_eval do
         
     | 
| 
      
 331 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 332 
     | 
    
         
            +
                  attr_reader :history
         
     | 
| 
      
 333 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 334 
     | 
    
         
            +
                    @history = []
         
     | 
| 
      
 335 
     | 
    
         
            +
                  end
         
     | 
| 
      
 336 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 337 
     | 
    
         
            +
                    state :new do
         
     | 
| 
      
 338 
     | 
    
         
            +
                      event :next, :transitions_to => :next_state
         
     | 
| 
      
 339 
     | 
    
         
            +
                    end
         
     | 
| 
      
 340 
     | 
    
         
            +
                    state :next_state
         
     | 
| 
      
 341 
     | 
    
         
            +
                  end
         
     | 
| 
      
 342 
     | 
    
         
            +
             
     | 
| 
      
 343 
     | 
    
         
            +
                  def on_next_state_entry(prior_state, event, *args)
         
     | 
| 
      
 344 
     | 
    
         
            +
                    @history << "on_next_state_entry #{event} #{prior_state} ->"
         
     | 
| 
      
 345 
     | 
    
         
            +
                  end
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
                  def on_new_exit(new_state, event, *args)
         
     | 
| 
      
 348 
     | 
    
         
            +
                    @history << "on_new_exit #{event} -> #{new_state}"
         
     | 
| 
      
 349 
     | 
    
         
            +
                  end
         
     | 
| 
      
 350 
     | 
    
         
            +
                end
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
                o = c.new
         
     | 
| 
      
 353 
     | 
    
         
            +
                assert_equal 'new', o.current_state.to_s
         
     | 
| 
      
 354 
     | 
    
         
            +
                assert_equal [], o.history
         
     | 
| 
      
 355 
     | 
    
         
            +
                o.next!
         
     | 
| 
      
 356 
     | 
    
         
            +
                assert_equal ['on_new_exit next -> next_state', 'on_next_state_entry next new ->'], o.history
         
     | 
| 
      
 357 
     | 
    
         
            +
             
     | 
| 
      
 358 
     | 
    
         
            +
              end
         
     | 
| 
      
 359 
     | 
    
         
            +
             
     | 
| 
      
 360 
     | 
    
         
            +
              test 'diagram generation' do
         
     | 
| 
      
 361 
     | 
    
         
            +
                begin
         
     | 
| 
      
 362 
     | 
    
         
            +
                  $stdout = StringIO.new('', 'w')
         
     | 
| 
      
 363 
     | 
    
         
            +
                  Workflow::create_workflow_diagram(Order, 'doc')
         
     | 
| 
      
 364 
     | 
    
         
            +
                  assert_match(/open.+\.pdf/, $stdout.string,
         
     | 
| 
      
 365 
     | 
    
         
            +
                    'PDF should be generate and a hint be given to the user.')
         
     | 
| 
      
 366 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 367 
     | 
    
         
            +
                  $stdout = STDOUT
         
     | 
| 
      
 368 
     | 
    
         
            +
                end
         
     | 
| 
      
 369 
     | 
    
         
            +
              end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
      
 371 
     | 
    
         
            +
              test 'halt stops the transition' do
         
     | 
| 
      
 372 
     | 
    
         
            +
                c = Class.new do
         
     | 
| 
      
 373 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 374 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 375 
     | 
    
         
            +
                    state :young do
         
     | 
| 
      
 376 
     | 
    
         
            +
                      event :age, :transitions_to => :old
         
     | 
| 
      
 377 
     | 
    
         
            +
                    end
         
     | 
| 
      
 378 
     | 
    
         
            +
                    state :old
         
     | 
| 
      
 379 
     | 
    
         
            +
                  end
         
     | 
| 
      
 380 
     | 
    
         
            +
             
     | 
| 
      
 381 
     | 
    
         
            +
                  def age(by=1)
         
     | 
| 
      
 382 
     | 
    
         
            +
                    halt 'too fast' if by > 100
         
     | 
| 
      
 383 
     | 
    
         
            +
                  end
         
     | 
| 
      
 384 
     | 
    
         
            +
                end
         
     | 
| 
      
 385 
     | 
    
         
            +
             
     | 
| 
      
 386 
     | 
    
         
            +
                joe = c.new
         
     | 
| 
      
 387 
     | 
    
         
            +
                assert joe.young?
         
     | 
| 
      
 388 
     | 
    
         
            +
                joe.age! 120
         
     | 
| 
      
 389 
     | 
    
         
            +
                assert joe.young?, 'Transition should have been halted'
         
     | 
| 
      
 390 
     | 
    
         
            +
                assert_equal 'too fast', joe.halted_because
         
     | 
| 
      
 391 
     | 
    
         
            +
              end
         
     | 
| 
      
 392 
     | 
    
         
            +
             
     | 
| 
      
 393 
     | 
    
         
            +
              test 'halt! raises exception' do
         
     | 
| 
      
 394 
     | 
    
         
            +
                article_class = Class.new do
         
     | 
| 
      
 395 
     | 
    
         
            +
                  include Workflow
         
     | 
| 
      
 396 
     | 
    
         
            +
                  workflow do
         
     | 
| 
      
 397 
     | 
    
         
            +
                    state :new do
         
     | 
| 
      
 398 
     | 
    
         
            +
                      event :reject, :transitions_to => :rejected
         
     | 
| 
      
 399 
     | 
    
         
            +
                    end
         
     | 
| 
      
 400 
     | 
    
         
            +
                    state :rejected
         
     | 
| 
      
 401 
     | 
    
         
            +
                  end
         
     | 
| 
      
 402 
     | 
    
         
            +
             
     | 
| 
      
 403 
     | 
    
         
            +
                  def reject(reason)
         
     | 
| 
      
 404 
     | 
    
         
            +
                    halt! 'We do not reject articles unless the reason is important' \
         
     | 
| 
      
 405 
     | 
    
         
            +
                      unless reason =~ /important/i
         
     | 
| 
      
 406 
     | 
    
         
            +
                  end
         
     | 
| 
      
 407 
     | 
    
         
            +
                end
         
     | 
| 
      
 408 
     | 
    
         
            +
             
     | 
| 
      
 409 
     | 
    
         
            +
                article = article_class.new
         
     | 
| 
      
 410 
     | 
    
         
            +
                assert article.new?
         
     | 
| 
      
 411 
     | 
    
         
            +
                assert_raise Workflow::TransitionHalted do
         
     | 
| 
      
 412 
     | 
    
         
            +
                  article.reject! 'Too funny'
         
     | 
| 
      
 413 
     | 
    
         
            +
                end
         
     | 
| 
      
 414 
     | 
    
         
            +
                assert article.new?, 'Transition should have been halted'
         
     | 
| 
      
 415 
     | 
    
         
            +
                article.reject! 'Important: too short'
         
     | 
| 
      
 416 
     | 
    
         
            +
                assert article.rejected?, 'Transition should happen now'
         
     | 
| 
      
 417 
     | 
    
         
            +
              end
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
            end
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     |