workflow 2.0.2 → 3.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4834f20238e73dbf0304747da6f098612e395c92a6b839908c7c0a613ad272b8
4
- data.tar.gz: 40a8fa57362ed445be012847fe283db2b417225ea3db4ea4c02bcad957630b57
3
+ metadata.gz: ecf93e8750c9a77e512c840987b34f0616fd6304ed9f2bc98372aaea992fcfe9
4
+ data.tar.gz: 5e86611e43d7d49f61e814e3c6804664b2134c293c768ccb73ebd8bcb09b07c3
5
5
  SHA512:
6
- metadata.gz: 9cb4cd4a9738529cdffc867d04716dbe0bce0df361e84fe55570563624441728e41d2bfec5639cbae6e64dbcf86f047abd73adb542140cb0ecdf92f92be39237
7
- data.tar.gz: 1cbbddb5014d83e9ace2b03e2d5e60cf01acbc8f1579d942b68267d144b3513f01fb23797219450e44aca85ae476e4675d57e97fcb149043be16d951c17608c1
6
+ metadata.gz: e77f206fc4206d3407e33b3d044b5c291784a4f544bc01a21aacc3f8f92db0fa011a02acfe0987b352e01ddf7b391337ae3a4bde8616d44dfa331c48cfa86595
7
+ data.tar.gz: 33042e85d5f98fbef4c43094da563fb7b22fa2d175220489a062a0295ed52dc2f1445ecc941ce8094c0b858c468431458fadb73f80ec7c56c2b53c813310f260
data/README.adoc ADDED
@@ -0,0 +1,588 @@
1
+ :doctype: book
2
+ :toc: macro
3
+ :toclevels: 1
4
+ :sectlinks:
5
+ :idprefix:
6
+
7
+ # Workflow
8
+
9
+ image:https://img.shields.io/gem/v/workflow.svg[link=https://rubygems.org/gems/workflow]
10
+ image:https://github.com/geekq/workflow/actions/workflows/test.yml/badge.svg[link=https://github.com/geekq/workflow/actions/workflows/test.yml]
11
+ image:https://codeclimate.com/github/geekq/workflow/badges/gpa.svg[link=https://codeclimate.com/github/geekq/workflow]
12
+ image:https://codeclimate.com/github/geekq/workflow/badges/coverage.svg[link=https://codeclimate.com/github/geekq/workflow/coverage]
13
+
14
+ Note: you can find documentation for specific workflow rubygem versions
15
+ at http://rubygems.org/gems/workflow : select a version (optional,
16
+ default is latest release), click "Documentation" link. When reading on
17
+ github.com, the README refers to the upcoming release.
18
+
19
+ toc::[]
20
+
21
+ What is workflow?
22
+ -----------------
23
+
24
+ Workflow is a finite-state-machine-inspired API for modeling and
25
+ interacting with what we tend to refer to as 'workflow'.
26
+
27
+ A lot of business modeling tends to involve workflow-like concepts, and
28
+ the aim of this library is to make the expression of these concepts as
29
+ clear as possible, using similar terminology as found in state machine
30
+ theory.
31
+
32
+ So, a workflow has a state. It can only be in one state at a time. When
33
+ a workflow changes state, we call that a transition. Transitions occur
34
+ on an event, so events cause transitions to occur. Additionally, when an
35
+ event fires, other arbitrary code can be executed, we call those actions.
36
+ So any given state has a bunch of events, any event in a state causes a
37
+ transition to another state and potentially causes code to be executed
38
+ (an action). We can hook into states when they are entered, and exited
39
+ from, and we can cause transitions to fail (guards), and we can hook in
40
+ to every transition that occurs ever for whatever reason we can come up
41
+ with.
42
+
43
+ Now, all that's a mouthful, but we'll demonstrate the API bit by bit
44
+ with a real-ish world example.
45
+
46
+ Let's say we're modeling article submission from journalists. An article
47
+ is written, then submitted. When it's submitted, it's awaiting review.
48
+ Someone reviews the article, and then either accepts or rejects it.
49
+ Here is the expression of this workflow using the API:
50
+
51
+ ```rb
52
+ class Article
53
+ include Workflow
54
+ workflow do
55
+ state :new do
56
+ event :submit, :transitions_to => :awaiting_review
57
+ end
58
+ state :awaiting_review do
59
+ event :review, :transitions_to => :being_reviewed
60
+ end
61
+ state :being_reviewed do
62
+ event :accept, :transitions_to => :accepted
63
+ event :reject, :transitions_to => :rejected
64
+ end
65
+ state :accepted
66
+ state :rejected
67
+ end
68
+ end
69
+ ```
70
+
71
+ Nice, isn't it!
72
+
73
+ Note: the first state in the definition (`:new` in the example, but you
74
+ can name it as you wish) is used as the initial state - newly created
75
+ objects start their life cycle in that state.
76
+
77
+ Let's create an article instance and check in which state it is:
78
+
79
+ ```rb
80
+ article = Article.new
81
+ article.accepted? # => false
82
+ article.new? # => true
83
+ ```
84
+
85
+ You can also access the whole `current_state` object including the list
86
+ of possible events and other meta information:
87
+
88
+ article.current_state
89
+ => #<Workflow::State:0x7f1e3d6731f0 @events={
90
+ :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
91
+ @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
92
+ name:new, meta{}
93
+
94
+ You can also check, whether a state comes before or after another state (by the
95
+ order they were defined):
96
+
97
+ ```rb
98
+ article.current_state # => being_reviewed
99
+ article.current_state < :accepted # => true
100
+ article.current_state >= :accepted # => false
101
+ article.current_state.between? :awaiting_review, :rejected # => true
102
+ ```
103
+
104
+ Now we can call the submit event, which transitions to the
105
+ `:awaiting_review` state:
106
+
107
+ ```rb
108
+ article.submit!
109
+ article.awaiting_review? # => true
110
+ ```
111
+
112
+ Events are actually instance methods on a workflow, and depending on the
113
+ state you're in, you'll have a different set of events used to
114
+ transition to other states.
115
+
116
+ It is also easy to check, if a certain transition is possible from the
117
+ current state . `article.can_submit?` checks if there is a `:submit`
118
+ event (transition) defined for the current state.
119
+
120
+
121
+ Getting started
122
+ ---------------
123
+
124
+ === Installation
125
+
126
+ ```sh
127
+ gem install workflow
128
+ ```
129
+
130
+ **Important**: If you're interested in graphing your workflow state machine, you will also need to
131
+ install the `activesupport` and `ruby-graphviz` gems.
132
+
133
+ Versions up to and including 1.0.0 are also available as a single file download -
134
+ link:https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb[lib/workflow.rb file].
135
+
136
+
137
+ === Examples
138
+
139
+ After installation or downloading the library you can easily try out
140
+ all the example code from this README in irb.
141
+
142
+ $ irb
143
+ require 'rubygems'
144
+ require 'workflow'
145
+
146
+ Now just copy and paste the source code from the beginning of this README
147
+ file snippet by snippet and observe the output.
148
+
149
+
150
+ ### Transition event handler
151
+
152
+ The best way is to use convention over configuration and to define a
153
+ method with the same name as the event. Then it is automatically invoked
154
+ when event is raised. For the Article workflow defined earlier it would
155
+ be:
156
+
157
+ ```rb
158
+ class Article
159
+ def reject
160
+ puts 'sending email to the author explaining the reason...'
161
+ end
162
+ end
163
+ ```
164
+
165
+ `article.review!; article.reject!` will cause state transition to
166
+ `being_reviewed` state, persist the new state (if integrated with
167
+ ActiveRecord), invoke this user defined `reject` method and finally
168
+ persist the `rejected` state.
169
+
170
+ Note: on successful transition from one state to another the workflow
171
+ gem immediately persists the new workflow state with `update_column()`,
172
+ bypassing any ActiveRecord callbacks including `updated_at` update.
173
+ This way it is possible to deal with the validation and to save the
174
+ pending changes to a record at some later point instead of the moment
175
+ when transition occurs.
176
+
177
+ You can also define event handler accepting/requiring additional
178
+ arguments:
179
+
180
+ ```rb
181
+ class Article
182
+ def review(reviewer = '')
183
+ puts "[#{reviewer}] is now reviewing the article"
184
+ end
185
+ end
186
+
187
+ article2 = Article.new
188
+ article2.submit!
189
+ article2.review!('Homer Simpson') # => [Homer Simpson] is now reviewing the article
190
+ ```
191
+
192
+ Alternative way is to use a block (only recommended for short event
193
+ implementation without further code nesting):
194
+
195
+ ```rb
196
+ event :review, :transitions_to => :being_reviewed do |reviewer|
197
+ # store the reviewer
198
+ end
199
+ ```
200
+
201
+ We've noticed, that mixing the list of events and states with the blocks
202
+ invoked for particular transitions leads to a bumpy and poorly readable code
203
+ due to a deep nesting. We tried (and dismissed) lambdas for this. Eventually
204
+ we decided to invoke an optional user defined callback method with the same
205
+ name as the event (convention over configuration) as explained before.
206
+
207
+ State persistence
208
+ -----------------
209
+
210
+ === ActiveRecord
211
+
212
+ Note: Workflow 2.0 is a major refactoring for the `worklow` library.
213
+ If your application suddenly breaks after the workflow 2.0 release, you've
214
+ probably got your Gemfile wrong ;-). workflow uses
215
+ https://guides.rubygems.org/patterns/#semantic-versioning[semantic versioning].
216
+ For highest compatibility please reference the desired major+minor version.
217
+
218
+ Note on ActiveRecord/Rails 4.\*, 5.\* Support:
219
+
220
+ Since integration with ActiveRecord makes over 90% of the issues and
221
+ maintenance effort, and also to allow for an independent (faster) release cycle
222
+ for Rails support, starting with workflow **version 2.0** in January 2019 the
223
+ support for ActiveRecord (4.\*, 5.\* and newer) has been extracted into a separate
224
+ gem. Read at
225
+ https://github.com/geekq/workflow-activerecord[workflow-activerecord], how to
226
+ include the right gem.
227
+
228
+ To use legacy built-in ActiveRecord 2.3 - 4.* support, reference Workflow 1.2 in
229
+ your Gemfile:
230
+
231
+ gem 'workflow', '~> 1.2'
232
+
233
+
234
+ === Custom workflow state persistence
235
+
236
+ If you do not use a relational database and ActiveRecord, you can still
237
+ integrate the workflow very easily. To implement persistence you just
238
+ need to override `load_workflow_state` and
239
+ `persist_workflow_state(new_value)` methods. Next section contains an example for
240
+ using CouchDB, a document oriented database.
241
+
242
+ http://tim.lossen.de/[Tim Lossen] implemented support
243
+ for http://github.com/tlossen/remodel[remodel] / http://github.com/antirez/redis[redis]
244
+ key-value store.
245
+
246
+ === Integration with CouchDB
247
+
248
+ We are using the compact http://github.com/geekq/couchtiny[couchtiny library]
249
+ here. But the implementation would look similar for the popular
250
+ couchrest library.
251
+
252
+ ```rb
253
+ require 'couchtiny'
254
+ require 'couchtiny/document'
255
+ require 'workflow'
256
+
257
+ class User < CouchTiny::Document
258
+ include Workflow
259
+ workflow do
260
+ state :submitted do
261
+ event :activate_via_link, :transitions_to => :proved_email
262
+ end
263
+ state :proved_email
264
+ end
265
+
266
+ def load_workflow_state
267
+ self[:workflow_state]
268
+ end
269
+
270
+ def persist_workflow_state(new_value)
271
+ self[:workflow_state] = new_value
272
+ save!
273
+ end
274
+ end
275
+ ```
276
+
277
+ Please also have a look at
278
+ http://github.com/geekq/workflow/blob/develop/test/couchtiny_example.rb[the full source code].
279
+
280
+
281
+ === Adapters to support other databases
282
+
283
+ I get a lot of requests to integrate persistence support for different
284
+ databases, object-relational adapters, column stores, document
285
+ databases.
286
+
287
+ To enable highest possible quality, avoid too many dependencies and to
288
+ avoid unneeded maintenance burden on the `workflow` core it is best to
289
+ implement such support as a separate gem.
290
+
291
+ Only support for the ActiveRecord will remain for the foreseeable
292
+ future. So Rails beginners can expect `workflow` to work with Rails out
293
+ of the box. Other already included adapters stay for a while but should
294
+ be extracted to separate gems.
295
+
296
+ If you want to implement support for your favorite ORM mapper or your
297
+ favorite NoSQL database, you just need to implement a module which
298
+ overrides the persistence methods `load_workflow_state` and
299
+ `persist_workflow_state`. Example:
300
+
301
+ ```rb
302
+ module Workflow
303
+ module SuperCoolDb
304
+ module InstanceMethods
305
+ def load_workflow_state
306
+ # Load and return the workflow_state from some storage.
307
+ # You can use self.class.workflow_column configuration.
308
+ end
309
+
310
+ def persist_workflow_state(new_value)
311
+ # save the new_value workflow state
312
+ end
313
+ end
314
+
315
+ module ClassMethods
316
+ # class methods of your adapter go here
317
+ end
318
+
319
+ def self.included(klass)
320
+ klass.send :include, InstanceMethods
321
+ klass.extend ClassMethods
322
+ end
323
+ end
324
+ end
325
+ ```
326
+
327
+ The user of the adapter can use it then as:
328
+
329
+ ```rb
330
+ class Article
331
+ include Workflow
332
+ include Workflow:SuperCoolDb
333
+ workflow do
334
+ state :submitted
335
+ # ...
336
+ end
337
+ end
338
+ ```
339
+
340
+ I can then link to your implementation from this README. Please let me
341
+ also know, if you need any interface beyond `load_workflow_state` and
342
+ `persist_workflow_state` methods to implement an adapter for your
343
+ favorite database.
344
+
345
+ Advanced usage
346
+ --------------
347
+
348
+ ### Conditional event transitions
349
+
350
+ Conditions can be a "method name symbol" with a corresponding instance method, a `proc` or `lambda` which are added to events, like so:
351
+
352
+ ```rb
353
+ state :off
354
+ event :turn_on, :transition_to => :on,
355
+ :if => :sufficient_battery_level?
356
+
357
+ event :turn_on, :transition_to => :low_battery,
358
+ :if => proc { |device| device.battery_level > 0 }
359
+ end
360
+
361
+ # corresponding instance method
362
+ def sufficient_battery_level?
363
+ battery_level > 10
364
+ end
365
+ ```
366
+
367
+ When calling a `device.can_<fire_event>?` check, or attempting a `device.<event>!`, each event is checked in turn:
368
+
369
+ * With no `:if` check, proceed as usual.
370
+ * If an `:if` check is present, proceed if it evaluates to true, or drop to the next event.
371
+ * If you've run out of events to check (eg. `battery_level == 0`), then the transition isn't possible.
372
+
373
+ You can also pass additional arguments, which can be evaluated by :if methods or procs. See examples in
374
+ link:test/conditionals_test.rb#L45[conditionals_test.rb]
375
+
376
+ ### Advanced transition hooks
377
+
378
+ #### on_entry/on_exit
379
+
380
+ We already had a look at the declaring callbacks for particular workflow
381
+ events. If you would like to react to all transitions to/from the same state
382
+ in the same way you can use the on_entry/on_exit hooks. You can either define it
383
+ with a block inside the workflow definition or through naming
384
+ convention, e.g. for the state :pending just define the method
385
+ `on_pending_exit(new_state, event, *args)` somewhere in your class.
386
+
387
+ #### on_transition
388
+
389
+ If you want to be informed about everything happening everywhere, e.g. for
390
+ logging then you can use the universal `on_transition` hook:
391
+
392
+ ```rb
393
+ workflow do
394
+ state :one do
395
+ event :increment, :transitions_to => :two
396
+ end
397
+ state :two
398
+ on_transition do |from, to, triggering_event, *event_args|
399
+ Log.info "#{from} -> #{to}"
400
+ end
401
+ end
402
+ ```
403
+
404
+ #### on_error
405
+
406
+ If you want to do custom exception handling internal to workflow, you can define an `on_error` hook in your workflow.
407
+ For example:
408
+
409
+ ```rb
410
+ workflow do
411
+ state :first do
412
+ event :forward, :transitions_to => :second
413
+ end
414
+ state :second
415
+
416
+ on_error do |error, from, to, event, *args|
417
+ Log.info "Exception(#{error.class}) on #{from} -> #{to}"
418
+ end
419
+ end
420
+ ```
421
+
422
+ If forward! results in an exception, `on_error` is invoked and the workflow stays in a 'first' state. This capability
423
+ is particularly useful if your errors are transient and you want to queue up a job to retry in the future without
424
+ affecting the existing workflow state.
425
+
426
+ ### Guards
427
+
428
+ If you want to halt the transition conditionally, you can just raise an
429
+ exception in your [transition event handler](#transition_event_handler).
430
+ There is a helper called `halt!`, which raises the
431
+ Workflow::TransitionHalted exception. You can provide an additional
432
+ `halted_because` parameter.
433
+
434
+ ```rb
435
+ def reject(reason)
436
+ halt! 'We do not reject articles unless the reason is important' \
437
+ unless reason =~ /important/i
438
+ end
439
+ ```
440
+
441
+ The traditional `halt` (without the exclamation mark) is still supported
442
+ too. This just prevents the state change without raising an
443
+ exception.
444
+
445
+ You can check `halted?` and `halted_because` values later.
446
+
447
+ ### Hook order
448
+
449
+ The whole event sequence is as follows:
450
+
451
+ * before_transition
452
+ * event specific action
453
+ * on_transition (if action did not halt)
454
+ * on_exit
455
+ * PERSIST WORKFLOW STATE (i.e. transition) or on_error
456
+ * on_entry
457
+ * after_transition
458
+
459
+
460
+ ### Accessing your workflow specification
461
+
462
+ You can easily reflect on workflow specification programmatically - for
463
+ the whole class or for the current object. Examples:
464
+
465
+ ```rb
466
+ article2.current_state.events # lists possible events from here
467
+ article2.current_state.events[:reject].transitions_to # => :rejected
468
+
469
+ Article.workflow_spec.states.keys
470
+ #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
471
+
472
+ Article.workflow_spec.state_names
473
+ #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
474
+
475
+ # list all events for all states
476
+ Article.workflow_spec.states.values.collect &:events
477
+ ```
478
+
479
+ You can also store and later retrieve additional meta data for every
480
+ state and every event:
481
+
482
+ ```rb
483
+ class MyProcess
484
+ include Workflow
485
+ workflow do
486
+ state :main, :meta => {:importance => 8}
487
+ state :supplemental, :meta => {:importance => 1}
488
+ end
489
+ end
490
+ puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1
491
+ ```
492
+
493
+ The workflow library itself uses this feature to tweak the graphical
494
+ representation of the workflow. See below.
495
+
496
+
497
+ ### Defining workflow dynamically from JSON
498
+
499
+ For an advance example please see
500
+ link:https://github.com/geekq/workflow/blob/develop/test/workflow_from_json_test.rb[workflow_from_json_test.rb].
501
+
502
+
503
+ ### Compose workflow definition with `include`
504
+
505
+ In case you have very extensive workflow definition or would like to reuse
506
+ workflow definition for different classes, you can include parts like in
507
+ the link:https://github.com/geekq/workflow/blob/develop/test/main_test.rb#L95-L110[`including a child workflow definition` example].
508
+
509
+ Documenting with diagrams
510
+ -------------------------
511
+
512
+ You can generate a graphical representation of the workflow for
513
+ a particular class for documentation purposes.
514
+ Use `Workflow::create_workflow_diagram(class)` in your rake task like:
515
+
516
+ ```rb
517
+ namespace :doc do
518
+ desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
519
+ task :workflow => :environment do
520
+ require 'workflow/draw'
521
+ Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
522
+ end
523
+ end
524
+ ```
525
+
526
+
527
+ Changelog
528
+ ---------
529
+
530
+ === New in the version 3.1.0
531
+
532
+ * link:https://github.com/geekq/workflow/pull/227[#227] Allow event arguments to be taken into account when selecting the event
533
+ * link:https://github.com/geekq/workflow/pull/232[#232] Add ability to include partial workflow definitions for composability
534
+ * link:https://github.com/geekq/workflow/pull/241[#241] Example for defining workflow dynamically from JSON
535
+
536
+ === New in the version 3.0.0
537
+
538
+ * link:https://github.com/geekq/workflow/pull/228[#228] Support for Ruby 3 keyword args, provided by @agirling
539
+ * retire Ruby 2.6 since it has reached end of live; please use workflow 2.x, if you still depend on that Ruby version
540
+ * link:https://github.com/geekq/workflow/pull/229[#229] Switch from travis CI to GihHub actions for continuous integration
541
+
542
+ ### New in the versions 2.x
543
+
544
+ * extract persistence adapters, Rails/ActiveRecord integration is now a separate gem
545
+ workflow-activerecord
546
+
547
+ Support, Participation
548
+ ----------------------
549
+
550
+ ### Reporting bugs
551
+
552
+ <http://github.com/geekq/workflow/issues>
553
+
554
+ ### Development Setup
555
+
556
+ ```sh
557
+ sudo apt-get install graphviz # Linux
558
+ brew install graphviz # Mac OS
559
+ cd workflow
560
+ gem install bundler
561
+ bundle install
562
+ # run all the tests
563
+ bundle exec rake test
564
+ ```
565
+
566
+ ### Check list for you pull request
567
+
568
+ * [ ] unit tests for the new behavior provided: new tests fail without you change, all tests succeed with your change
569
+ * [ ] documentation update included
570
+
571
+ ### Other 3rd party libraries
572
+
573
+ https://github.com/kwent/active_admin-workflow[ActiveAdmin-Workflow] - is an
574
+ integration with https://github.com/activeadmin/activeadmin[ActiveAdmin].
575
+
576
+ ### About
577
+
578
+ Author: Vladimir Dobriakov, <https://infrastructure-as-code.de>
579
+
580
+ Copyright (c) 2010-2024 Vladimir Dobriakov and Contributors
581
+
582
+ Copyright (c) 2008-2009 Vodafone
583
+
584
+ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
585
+
586
+ Based on the work of Ryan Allen and Scott Barron
587
+
588
+ Licensed under MIT license, see the MIT-LICENSE file.
@@ -15,12 +15,22 @@ module Workflow
15
15
  end
16
16
  end
17
17
 
18
- def condition_applicable?(object)
18
+ def condition_applicable?(object, event_arguments)
19
19
  if condition
20
20
  if condition.is_a?(Symbol)
21
- object.send(condition)
21
+ m = object.method(condition)
22
+ # Conditionals can now take the arguments of the trasition action into account #227
23
+ # But in case the current conditional wants to ignore any event_argument on its decision -
24
+ # does not accept parameters, also support that.
25
+ if m.arity == 0 # no additional parameters accepted
26
+ object.send(condition)
27
+ else
28
+ object.send(condition, *event_arguments)
29
+ end
22
30
  else
23
- condition.call(object)
31
+ # since blocks can ignore extra arguments without raising an error in Ruby,
32
+ # no `if` is needed - compare with `arity` switch in above methods handling
33
+ condition.call(object, *event_arguments)
24
34
  end
25
35
  else
26
36
  true
@@ -26,9 +26,9 @@ module Workflow
26
26
  end
27
27
  end
28
28
 
29
- def first_applicable(name, object_context)
29
+ def first_applicable(name, object_context, event_arguments)
30
30
  (self[name] || []).detect do |event|
31
- event.condition_applicable?(object_context) && event
31
+ event.condition_applicable?(object_context, event_arguments) && event
32
32
  end
33
33
  end
34
34
 
@@ -20,6 +20,10 @@ module Workflow
20
20
 
21
21
  private
22
22
 
23
+ def include(proc)
24
+ instance_eval(&proc)
25
+ end
26
+
23
27
  def state(name, meta = {:meta => {}}, &events_and_etc)
24
28
  # meta[:meta] to keep the API consistent..., gah
25
29
  new_state = Workflow::State.new(name, self, meta[:meta])
@@ -1,3 +1,3 @@
1
1
  module Workflow
2
- VERSION = "2.0.2"
2
+ VERSION = "3.1.0.pre"
3
3
  end