validating-workflow 0.7.2 → 0.7.6
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 +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:
|