validating-workflow 0.7.2 → 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +7 -3
- data/Rakefile +5 -5
- data/VERSION +1 -1
- data/lib/workflow/state_dependent_validations.rb +30 -12
- data/lib/workflow.rb +180 -140
- metadata +33 -57
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
What is workflow?
|
2
|
-
|
1
|
+
What is validating-workflow?
|
2
|
+
----------------------------
|
3
3
|
|
4
|
-
Workflow is a finite-state-machine-inspired API for modeling and
|
4
|
+
Validating-Workflow is a finite-state-machine-inspired API for modeling and
|
5
5
|
interacting with what we tend to refer to as 'workflow'.
|
6
6
|
|
7
7
|
A lot of business modeling tends to involve workflow-like concepts, and
|
@@ -20,6 +20,10 @@ from, and we can cause transitions to fail (guards), and we can hook in
|
|
20
20
|
to every transition that occurs ever for whatever reason we can come up
|
21
21
|
with.
|
22
22
|
|
23
|
+
For convenience, validating-workflow adds certain validation options for
|
24
|
+
ActiveModel so you may declaratively specify what to validate in which
|
25
|
+
states or transitions when defining your validators.
|
26
|
+
|
23
27
|
Now, all that's a mouthful, but we'll demonstrate the API bit by bit
|
24
28
|
with a real-ish world example.
|
25
29
|
|
data/Rakefile
CHANGED
@@ -19,12 +19,12 @@ end
|
|
19
19
|
begin
|
20
20
|
require 'jeweler'
|
21
21
|
Jeweler::Tasks.new do |gemspec|
|
22
|
-
gemspec.name =
|
22
|
+
gemspec.name = 'workflow'
|
23
23
|
gemspec.rubyforge_project = 'workflow'
|
24
|
-
gemspec.email =
|
25
|
-
gemspec.homepage =
|
26
|
-
gemspec.authors = [
|
27
|
-
gemspec.summary =
|
24
|
+
gemspec.email = 'vladimir@geekq.net'
|
25
|
+
gemspec.homepage = 'http://www.geekq.net/workflow/'
|
26
|
+
gemspec.authors = ['Vladimir Dobriakov', 'Willem van Kerkhof']
|
27
|
+
gemspec.summary = 'A replacement for acts_as_state_machine.'
|
28
28
|
gemspec.description = <<-EOS
|
29
29
|
Workflow is a finite-state-machine-inspired API for modeling and interacting
|
30
30
|
with what we tend to refer to as 'workflow'.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
1
|
+
0.7.6
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_model/validations'
|
2
|
+
|
1
3
|
module Workflow
|
2
4
|
module StateDependentValidations
|
3
5
|
module StateDependency
|
@@ -7,18 +9,30 @@ module Workflow
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def validate_with_state_dependency(record)
|
10
|
-
if not record.respond_to?(:current_state) or
|
12
|
+
if not record.respond_to?(:current_state) or
|
13
|
+
perform_validation_for_state?(record.current_state) or
|
14
|
+
perform_validation_for_transition?(record.in_transition) or
|
15
|
+
perform_validation_for_transition?("#{record.in_exit}_exit") or
|
16
|
+
perform_validation_for_transition?("#{record.in_entry}_entry")
|
11
17
|
validate_without_state_dependency(record)
|
12
18
|
end
|
13
19
|
end
|
14
20
|
|
15
21
|
protected
|
16
22
|
def perform_validation_for_state?(state)
|
17
|
-
(unless_in_state_option.empty? and if_in_state_option.empty?
|
23
|
+
(unless_in_state_option.empty? and if_in_state_option.empty? and
|
24
|
+
unless_in_transition_option.empty? and if_in_transition_option.empty?) or
|
18
25
|
(if_in_state_option.any? and if_in_state_option.include?(state.to_s)) or
|
19
26
|
(unless_in_state_option.any? and not unless_in_state_option.include?(state.to_s))
|
20
27
|
end
|
21
28
|
|
29
|
+
def perform_validation_for_transition?(transition)
|
30
|
+
(unless_in_state_option.empty? and if_in_state_option.empty? and
|
31
|
+
unless_in_transition_option.empty? and if_in_transition_option.empty?) or
|
32
|
+
(if_in_transition_option.any? and if_in_transition_option.include?(transition.to_s)) or
|
33
|
+
(unless_in_transition_option.any? and not unless_in_transition_option.include?(transition.to_s))
|
34
|
+
end
|
35
|
+
|
22
36
|
def if_in_state_option
|
23
37
|
@if_in_state_option ||= [options[:if_in_state]].flatten.compact.collect(&:to_s)
|
24
38
|
end
|
@@ -26,19 +40,23 @@ module Workflow
|
|
26
40
|
def unless_in_state_option
|
27
41
|
@unless_in_state_option ||= [options[:unless_in_state]].flatten.compact.collect(&:to_s)
|
28
42
|
end
|
43
|
+
|
44
|
+
def if_in_transition_option
|
45
|
+
@if_in_transition_option ||= [options[:if_in_transition]].flatten.compact.collect(&:to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def unless_in_transition_option
|
49
|
+
@unless_in_transition_option ||= [options[:unless_in_transition]].flatten.compact.collect(&:to_s)
|
50
|
+
end
|
29
51
|
end
|
30
52
|
|
31
53
|
end
|
32
54
|
end
|
33
55
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
validator.send :include, Workflow::StateDependentValidations::StateDependency
|
40
|
-
end
|
56
|
+
module ActiveModel::Validations
|
57
|
+
[AcceptanceValidator, ConfirmationValidator, ExclusionValidator,
|
58
|
+
FormatValidator, InclusionValidator, LengthValidator,
|
59
|
+
NumericalityValidator, PresenceValidator].each do |validator|
|
60
|
+
validator.send :include, Workflow::StateDependentValidations::StateDependency
|
41
61
|
end
|
42
|
-
|
43
|
-
# raise 'state dependent validation only works with ActiveModel::Validations'
|
44
|
-
# end
|
62
|
+
end
|
data/lib/workflow.rb
CHANGED
@@ -2,10 +2,14 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
# See also README.markdown for documentation
|
4
4
|
module Workflow
|
5
|
+
autoload :ActiveModelPersistence, 'workflow/active_model_persistence'
|
6
|
+
autoload :MongoidPersistence, 'workflow/mongoid_persistence'
|
7
|
+
autoload :RemodelPersistence, 'workflow/remodel_persistence'
|
8
|
+
autoload :Transactional, 'workflow/transactional'
|
5
9
|
|
6
10
|
class Specification
|
7
11
|
|
8
|
-
attr_accessor :states, :initial_state, :meta, :on_transition_proc
|
12
|
+
attr_accessor :states, :initial_state, :meta, :on_transition_proc, :on_failed_transition_proc
|
9
13
|
|
10
14
|
def initialize(meta = {}, &specification)
|
11
15
|
@states = Hash.new
|
@@ -22,31 +26,41 @@ module Workflow
|
|
22
26
|
def state(name, meta = {:meta => {}}, &events_and_etc)
|
23
27
|
# meta[:meta] to keep the API consistent..., gah
|
24
28
|
new_state = Workflow::State.new(name, meta[:meta])
|
25
|
-
@initial_state
|
29
|
+
@initial_state = new_state if @states.empty?
|
26
30
|
@states[name.to_sym] = new_state
|
27
|
-
@scoped_state
|
31
|
+
@scoped_state = new_state
|
28
32
|
instance_eval(&events_and_etc) if events_and_etc
|
29
33
|
end
|
30
34
|
|
31
35
|
def event(name, args = {}, &action)
|
32
36
|
target = args[:transitions_to] || args[:transition_to]
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
if target.nil?
|
38
|
+
raise WorkflowDefinitionError.new \
|
39
|
+
"missing ':transitions_to' in workflow event definition for '#{name}'"
|
40
|
+
end
|
36
41
|
@scoped_state.events[name.to_sym] =
|
37
|
-
Workflow::Event.new(name, target, (args[:meta]
|
42
|
+
Workflow::Event.new(name, target, (args[:meta] || {}), &action)
|
43
|
+
end
|
44
|
+
|
45
|
+
def allow(name, args={}, &action)
|
46
|
+
args[:transitions_to] ||= args[:transition_to] || @scoped_state.to_sym
|
47
|
+
event name, args, &action
|
38
48
|
end
|
39
49
|
|
40
|
-
def on_entry(&
|
41
|
-
@scoped_state.on_entry =
|
50
|
+
def on_entry(&proc_to_run)
|
51
|
+
@scoped_state.on_entry = proc_to_run
|
42
52
|
end
|
43
53
|
|
44
|
-
def on_exit(&
|
45
|
-
@scoped_state.on_exit =
|
54
|
+
def on_exit(&proc_to_run)
|
55
|
+
@scoped_state.on_exit = proc_to_run
|
46
56
|
end
|
47
57
|
|
48
|
-
def on_transition(&
|
49
|
-
@on_transition_proc =
|
58
|
+
def on_transition(&proc_to_run)
|
59
|
+
@on_transition_proc = proc_to_run
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_failed_transition(&proc_to_run)
|
63
|
+
@on_failed_transition_proc = proc_to_run
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
@@ -92,6 +106,18 @@ module Workflow
|
|
92
106
|
@name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
|
93
107
|
end
|
94
108
|
|
109
|
+
def perform_validation?
|
110
|
+
!self.meta[:skip_all_validations]
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_sym
|
114
|
+
@name.to_sym
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_s
|
118
|
+
@name.to_s
|
119
|
+
end
|
120
|
+
|
95
121
|
end
|
96
122
|
|
97
123
|
module WorkflowClassMethods
|
@@ -113,7 +139,15 @@ module Workflow
|
|
113
139
|
state_name = state.name
|
114
140
|
module_eval do
|
115
141
|
define_method "#{state_name}?" do
|
116
|
-
state_name == current_state.name
|
142
|
+
state_name.to_sym == current_state.name.to_sym
|
143
|
+
end
|
144
|
+
|
145
|
+
define_method "in_#{state_name}_exit?" do
|
146
|
+
self.in_exit and self.in_exit.to_sym == state_name.to_sym
|
147
|
+
end
|
148
|
+
|
149
|
+
define_method "in_#{state_name}_entry?" do
|
150
|
+
self.in_entry and self.in_entry.to_sym == state_name.to_sym
|
117
151
|
end
|
118
152
|
end
|
119
153
|
|
@@ -124,9 +158,19 @@ module Workflow
|
|
124
158
|
process_event!(event_name, *args)
|
125
159
|
end
|
126
160
|
|
161
|
+
# this allows checks like can_approve? or can_reject_item?
|
162
|
+
# note we don't have a more generic can?(:approve) method.
|
163
|
+
# this is fully intentional, since this way it is far easier
|
164
|
+
# to overwrite the can_...? mehtods in a model than it would be
|
165
|
+
# with a generic can?(...) method.
|
127
166
|
define_method "can_#{event_name}?" do
|
128
167
|
return self.current_state.events.include? event_name
|
129
168
|
end
|
169
|
+
|
170
|
+
define_method "in_transition_#{event_name}?" do
|
171
|
+
return false unless self.in_transition
|
172
|
+
return self.in_transition.to_sym == event_name.to_sym
|
173
|
+
end
|
130
174
|
end
|
131
175
|
end
|
132
176
|
end
|
@@ -134,6 +178,8 @@ module Workflow
|
|
134
178
|
end
|
135
179
|
|
136
180
|
module WorkflowInstanceMethods
|
181
|
+
attr_accessor :in_entry, :in_exit, :in_transition
|
182
|
+
|
137
183
|
def current_state
|
138
184
|
loaded_state = load_workflow_state
|
139
185
|
res = spec.states[loaded_state.to_sym] if loaded_state
|
@@ -153,38 +199,36 @@ module Workflow
|
|
153
199
|
end
|
154
200
|
|
155
201
|
def process_event!(name, *args)
|
202
|
+
assure_transition_allowed! name
|
156
203
|
event = current_state.events[name.to_sym]
|
157
|
-
|
158
|
-
|
159
|
-
"There is no event #{name.to_sym} defined for the #{current_state} state"
|
160
|
-
end
|
204
|
+
assure_target_state_exists!(event)
|
205
|
+
set_transition_flags(current_state, spec.states[event.transitions_to], event)
|
161
206
|
@halted_because = nil
|
162
|
-
@halted
|
163
|
-
return_value
|
207
|
+
@halted = false
|
208
|
+
return_value = run_action(event.action, *args) || run_action_callback(event.name, *args)
|
164
209
|
if @halted
|
165
|
-
|
210
|
+
run_on_failed_transition(*args)
|
211
|
+
return_value = false
|
166
212
|
else
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
# ... or possibly validate twice!
|
172
|
-
run_on_transition(current_state, target_state, name, *args) # if valid?
|
173
|
-
if valid?
|
174
|
-
transition(current_state, target_state, name, *args)
|
213
|
+
if event.perform_validation? and not valid?
|
214
|
+
run_on_failed_transition(*args)
|
215
|
+
@halted = true # make sure this one is not reset in the on_failed_transition callback
|
216
|
+
return_value = false
|
175
217
|
else
|
176
|
-
|
177
|
-
@halted = true
|
178
|
-
return false
|
218
|
+
transition(*args)
|
179
219
|
end
|
180
|
-
return_value.nil? ? true : return_value
|
181
220
|
end
|
221
|
+
return_value.nil? ? true : return_value
|
182
222
|
end
|
183
223
|
|
184
|
-
def
|
185
|
-
|
186
|
-
|
187
|
-
|
224
|
+
def set_transition_flags(current_state, target_state, event)
|
225
|
+
@in_exit = current_state
|
226
|
+
@in_entry = target_state
|
227
|
+
@in_transition = event
|
228
|
+
end
|
229
|
+
|
230
|
+
def clear_transition_flags
|
231
|
+
set_transition_flags nil, nil, nil
|
188
232
|
end
|
189
233
|
|
190
234
|
def halt(reason = nil)
|
@@ -193,8 +237,7 @@ module Workflow
|
|
193
237
|
end
|
194
238
|
|
195
239
|
def halt!(reason = nil)
|
196
|
-
|
197
|
-
@halted = true
|
240
|
+
halt reason
|
198
241
|
raise TransitionHalted.new(reason)
|
199
242
|
end
|
200
243
|
|
@@ -213,27 +256,49 @@ module Workflow
|
|
213
256
|
c.workflow_spec
|
214
257
|
end
|
215
258
|
|
216
|
-
|
259
|
+
protected
|
260
|
+
|
261
|
+
def assure_transition_allowed!(name)
|
262
|
+
unless self.send "can_#{name}?"
|
263
|
+
prohibit_transition! name
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def prohibit_transition!(name)
|
268
|
+
raise NoTransitionAllowed.new \
|
269
|
+
"There is no event #{name} defined for the #{current_state} state."
|
270
|
+
end
|
217
271
|
|
218
|
-
def
|
272
|
+
def assure_target_state_exists!(event)
|
219
273
|
# Create a meaningful error message instead of
|
220
274
|
# "undefined method `on_entry' for nil:NilClass"
|
221
275
|
# Reported by Kyle Burton
|
222
276
|
if !spec.states[event.transitions_to]
|
223
|
-
raise WorkflowError.new
|
224
|
-
"transitions_to[#{event.transitions_to}] is not a declared state."
|
277
|
+
raise WorkflowError.new \
|
278
|
+
"Event[#{event.name}]'s transitions_to[#{event.transitions_to}] is not a declared state."
|
225
279
|
end
|
226
280
|
end
|
227
281
|
|
228
|
-
def transition(
|
229
|
-
run_on_exit(
|
230
|
-
|
231
|
-
|
282
|
+
def transition(*args)
|
283
|
+
run_on_exit(*args)
|
284
|
+
run_on_transition(*args)
|
285
|
+
val = persist_workflow_state wf_target_state.name
|
286
|
+
run_on_entry(*args)
|
232
287
|
val
|
233
288
|
end
|
234
289
|
|
235
|
-
def run_on_transition(
|
236
|
-
instance_exec(
|
290
|
+
def run_on_transition(*args)
|
291
|
+
instance_exec(self.wf_prior_state.name, self.wf_target_state.name, self.wf_event_name, *args, &spec.on_transition_proc) if spec.on_transition_proc
|
292
|
+
end
|
293
|
+
|
294
|
+
def run_on_failed_transition(*args)
|
295
|
+
if spec.on_failed_transition_proc
|
296
|
+
return_value = instance_exec(self.wf_prior_state.name, self.wf_target_state.name, self.wf_event_name, *args, &spec.on_failed_transition_proc)
|
297
|
+
else
|
298
|
+
return_value = halt(:validation_failed)
|
299
|
+
end
|
300
|
+
clear_transition_flags
|
301
|
+
return return_value
|
237
302
|
end
|
238
303
|
|
239
304
|
def run_action(action, *args)
|
@@ -244,26 +309,42 @@ module Workflow
|
|
244
309
|
self.send action_name.to_sym, *args if self.respond_to?(action_name.to_sym)
|
245
310
|
end
|
246
311
|
|
247
|
-
def run_on_entry(
|
248
|
-
if
|
249
|
-
instance_exec(
|
312
|
+
def run_on_entry(*args)
|
313
|
+
if self.wf_target_state.on_entry
|
314
|
+
instance_exec(self.wf_prior_state.name, self.wf_event_name, *args, &self.wf_target_state.on_entry)
|
250
315
|
else
|
251
|
-
hook_name = "on_#{
|
252
|
-
self.send hook_name,
|
316
|
+
hook_name = "on_#{self.wf_target_state.name}_entry"
|
317
|
+
self.send hook_name, self.wf_prior_state, self.wf_event_name, *args if self.respond_to? hook_name
|
253
318
|
end
|
254
319
|
end
|
255
320
|
|
256
|
-
def run_on_exit(
|
257
|
-
if state
|
258
|
-
if
|
259
|
-
instance_exec(
|
321
|
+
def run_on_exit(*args)
|
322
|
+
if self.wf_prior_state # no on_exit for entry into initial state
|
323
|
+
if self.wf_prior_state.on_exit
|
324
|
+
instance_exec(self.wf_target_state.name, self.wf_event_name, *args, &self.wf_prior_state.on_exit)
|
260
325
|
else
|
261
|
-
hook_name = "on_#{
|
262
|
-
self.send hook_name,
|
326
|
+
hook_name = "on_#{self.wf_prior_state.name}_exit"
|
327
|
+
self.send hook_name, self.wf_target_state, self.wf_event_name, *args if self.respond_to? hook_name
|
263
328
|
end
|
264
329
|
end
|
265
330
|
end
|
266
331
|
|
332
|
+
def wf_prior_state
|
333
|
+
@in_exit
|
334
|
+
end
|
335
|
+
|
336
|
+
def wf_target_state
|
337
|
+
@in_entry
|
338
|
+
end
|
339
|
+
|
340
|
+
def wf_event_name
|
341
|
+
@in_transition.name
|
342
|
+
end
|
343
|
+
|
344
|
+
def wf_event
|
345
|
+
@in_transition
|
346
|
+
end
|
347
|
+
|
267
348
|
# load_workflow_state and persist_workflow_state
|
268
349
|
# can be overriden to handle the persistence of the workflow state.
|
269
350
|
#
|
@@ -280,66 +361,17 @@ module Workflow
|
|
280
361
|
end
|
281
362
|
end
|
282
363
|
|
283
|
-
module ActiveRecordInstanceMethods
|
284
|
-
def load_workflow_state
|
285
|
-
read_attribute(self.class.workflow_column)
|
286
|
-
end
|
287
|
-
|
288
|
-
# On transition the new workflow state is immediately saved in the
|
289
|
-
# database.
|
290
|
-
def persist_workflow_state(new_value)
|
291
|
-
update_attribute self.class.workflow_column, new_value
|
292
|
-
end
|
293
|
-
|
294
|
-
private
|
295
|
-
|
296
|
-
# Motivation: even if NULL is stored in the workflow_state database column,
|
297
|
-
# the current_state is correctly recognized in the Ruby code. The problem
|
298
|
-
# arises when you want to SELECT records filtering by the value of initial
|
299
|
-
# state. That's why it is important to save the string with the name of the
|
300
|
-
# initial state in all the new records.
|
301
|
-
def write_initial_state
|
302
|
-
write_attribute self.class.workflow_column, current_state.to_s
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
module RemodelInstanceMethods
|
307
|
-
def load_workflow_state
|
308
|
-
send(self.class.workflow_column)
|
309
|
-
end
|
310
|
-
|
311
|
-
def persist_workflow_state(new_value)
|
312
|
-
update(self.class.workflow_column => new_value)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
module MongoidInstanceMethods
|
317
|
-
include ActiveRecordInstanceMethods
|
318
|
-
# implementation of abstract method: saves new workflow state to DB
|
319
|
-
def persist_workflow_state(new_value)
|
320
|
-
self.write_attribute(self.class.workflow_column, new_value.to_s)
|
321
|
-
self.save! :validate => false
|
322
|
-
end
|
323
|
-
end
|
324
|
-
|
325
364
|
def self.included(klass)
|
326
365
|
klass.send :include, WorkflowInstanceMethods
|
327
366
|
klass.extend WorkflowClassMethods
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
end
|
337
|
-
elsif Object.const_defined?(:Mongoid)
|
338
|
-
if klass.include? Mongoid::Document
|
339
|
-
klass.send :include, MongoidInstanceMethods
|
340
|
-
klass.after_initialize :write_initial_state
|
341
|
-
end
|
342
|
-
end
|
367
|
+
|
368
|
+
# [ActiveModelPersistence, MongoidPersistence, RemodelPersistence].each do |konst|
|
369
|
+
# if konst.happy_to_be_included_in? klass
|
370
|
+
# raise "including #{konst}"
|
371
|
+
# raise "including #{konst}"
|
372
|
+
# klass.send :include, konst
|
373
|
+
# end
|
374
|
+
# end
|
343
375
|
end
|
344
376
|
|
345
377
|
# Generates a `dot` graph of the workflow.
|
@@ -372,34 +404,42 @@ module Workflow
|
|
372
404
|
workflow_name = "#{klass.name.tableize}_workflow".gsub('/', '_')
|
373
405
|
fname = File.join(target_dir, "generated_#{workflow_name}")
|
374
406
|
File.open("#{fname}.dot", 'w') do |file|
|
375
|
-
file.puts
|
376
|
-
|
407
|
+
file.puts klass.new.workflow_diagram(graph_options)
|
408
|
+
end
|
409
|
+
`dot -Tpdf -o'#{fname}.pdf' '#{fname}.dot'`
|
410
|
+
puts "A PDF file was generated at '#{fname}.pdf'"
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns a representation of the state diagram for the
|
414
|
+
# calling model as a string in dot language.
|
415
|
+
# See Workflow.create_workflow_diagram for more deails
|
416
|
+
def workflow_diagram(graph_options)
|
417
|
+
str = <<-EOS
|
418
|
+
digraph #{self.class} {
|
377
419
|
graph [#{graph_options}];
|
378
420
|
node [shape=box];
|
379
421
|
edge [len=1];
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
422
|
+
EOS
|
423
|
+
|
424
|
+
self.class.workflow_spec.states.each do |state_name, state|
|
425
|
+
state_meta = state.meta
|
426
|
+
if state == self.class.workflow_spec.initial_state
|
427
|
+
str << %Q{ #{state.name} [label="#{state.name}", shape=circle];\n}
|
428
|
+
else
|
429
|
+
str << %Q{ #{state.name} [label="#{state.name}", shape=#{state_meta[:terminal] ? 'doublecircle' : 'box, style=rounded'}];\n}
|
430
|
+
end
|
431
|
+
state.events.each do |event_name, event|
|
432
|
+
event_meta = event.meta
|
433
|
+
event_meta[:doc_weight] = 6 if event_meta[:main_path]
|
434
|
+
if event_meta[:doc_weight]
|
435
|
+
weight_prop = ", weight=#{event_meta[:doc_weight]}, penwidth=#{event_meta[:doc_weight] / 2 || 0.0}\n"
|
436
|
+
else
|
437
|
+
weight_prop = ''
|
392
438
|
end
|
439
|
+
str << %Q{ #{state.name} -> #{event.transitions_to} [label="#{event_name.to_s.humanize}" #{weight_prop}];\n}
|
393
440
|
end
|
394
|
-
file.puts "}"
|
395
|
-
file.puts
|
396
441
|
end
|
397
|
-
|
398
|
-
|
399
|
-
Please run the following to open the generated file:
|
400
|
-
|
401
|
-
open '#{fname}.pdf'
|
402
|
-
|
403
|
-
"
|
442
|
+
str << "}\n"
|
443
|
+
return str
|
404
444
|
end
|
405
445
|
end
|
metadata
CHANGED
@@ -1,46 +1,30 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: validating-workflow
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 7
|
9
|
-
- 2
|
10
|
-
version: 0.7.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.6
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Vladimir Dobriakov
|
14
9
|
- Willem van Kerkhof
|
15
10
|
autorequire:
|
16
11
|
bindir: bin
|
17
12
|
cert_chain: []
|
18
|
-
|
19
|
-
date: 2010-10-16 00:00:00 +02:00
|
20
|
-
default_executable:
|
13
|
+
date: 2014-02-27 00:00:00.000000000 Z
|
21
14
|
dependencies: []
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
* convenient access to the workflow specification: list states, possible events
|
31
|
-
for particular state
|
32
|
-
* state and transition dependent validations for ActiveModel
|
33
|
-
|
34
|
-
email:
|
35
|
-
- vladimir@geekq.net
|
36
|
-
- wvk@consolving.de
|
15
|
+
description: ! "Validating-Workflow is a finite-state-machine-inspired API for modeling
|
16
|
+
and interacting\n with what we tend to refer to as 'workflow'.\n\n * nice
|
17
|
+
DSL to describe your states, events and transitions\n * robust integration with
|
18
|
+
ActiveRecord and non relational data stores\n * various hooks for single transitions,
|
19
|
+
entering state etc.\n * convenient access to the workflow specification: list
|
20
|
+
states, possible events\n for particular state\n * state and transition
|
21
|
+
dependent validations for ActiveModel\n"
|
22
|
+
email: vladimir@geekq.net, wvk@consolving.de
|
37
23
|
executables: []
|
38
|
-
|
39
24
|
extensions: []
|
40
|
-
|
41
|
-
extra_rdoc_files:
|
25
|
+
extra_rdoc_files:
|
42
26
|
- README.markdown
|
43
|
-
files:
|
27
|
+
files:
|
44
28
|
- .gitignore
|
45
29
|
- MIT-LICENSE
|
46
30
|
- README.markdown
|
@@ -55,44 +39,36 @@ files:
|
|
55
39
|
- test/without_active_record_test.rb
|
56
40
|
- workflow.rb
|
57
41
|
- lib/workflow/state_dependent_validations.rb
|
58
|
-
has_rdoc: true
|
59
42
|
homepage: http://www.geekq.net/workflow/
|
60
43
|
licenses: []
|
61
|
-
|
62
44
|
post_install_message:
|
63
|
-
rdoc_options:
|
45
|
+
rdoc_options:
|
64
46
|
- --charset=UTF-8
|
65
|
-
require_paths:
|
47
|
+
require_paths:
|
66
48
|
- lib
|
67
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
50
|
none: false
|
69
|
-
requirements:
|
70
|
-
- -
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
|
73
|
-
|
74
|
-
- 0
|
75
|
-
version: "0"
|
76
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
56
|
none: false
|
78
|
-
requirements:
|
79
|
-
- -
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
|
82
|
-
segments:
|
83
|
-
- 0
|
84
|
-
version: "0"
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
85
61
|
requirements: []
|
86
|
-
|
87
|
-
|
88
|
-
rubygems_version: 1.3.7
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.23
|
89
64
|
signing_key:
|
90
65
|
specification_version: 3
|
91
|
-
summary: A replacement for acts_as_state_machine.
|
92
|
-
test_files:
|
66
|
+
summary: A replacement for acts_as_state_machine, an enhancement of workflow.
|
67
|
+
test_files:
|
93
68
|
- test/couchtiny_example.rb
|
94
69
|
- test/main_test.rb
|
95
70
|
- test/test_helper.rb
|
96
71
|
- test/without_active_record_test.rb
|
97
72
|
- test/multiple_workflows_test.rb
|
98
73
|
- test/readme_example.rb
|
74
|
+
has_rdoc:
|