workflow 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown DELETED
@@ -1,655 +0,0 @@
1
- [![Version ](https://img.shields.io/gem/v/workflow.svg?maxAge=2592000)](https://rubygems.org/gems/workflow)
2
- [![Build Status ](https://travis-ci.org/geekq/workflow.svg)](https://travis-ci.org/geekq/workflow)
3
- [![Code Climate ](https://codeclimate.com/github/geekq/workflow/badges/gpa.svg)](https://codeclimate.com/github/geekq/workflow)
4
- [![Test Coverage](https://codeclimate.com/github/geekq/workflow/badges/coverage.svg)](https://codeclimate.com/github/geekq/workflow/coverage)
5
-
6
- # Workflow
7
-
8
- Note: you can find documentation for specific workflow rubygem versions
9
- at http://rubygems.org/gems/workflow : select a version (optional,
10
- default is latest release), click "Documentation" link. When reading on
11
- github.com, the README refers to the upcoming release.
12
-
13
- **Note: Workflow 2.0 is a major refactoring of the library.
14
- For different options/troubleshooting using it with your Rails application see
15
- [State persistence with ActiveRecord](#state-persistence-with-activerecord).**
16
-
17
- What is workflow?
18
- -----------------
19
-
20
- Workflow is a finite-state-machine-inspired API for modeling and
21
- interacting with what we tend to refer to as 'workflow'.
22
-
23
- A lot of business modeling tends to involve workflow-like concepts, and
24
- the aim of this library is to make the expression of these concepts as
25
- clear as possible, using similar terminology as found in state machine
26
- theory.
27
-
28
- So, a workflow has a state. It can only be in one state at a time. When
29
- a workflow changes state, we call that a transition. Transitions occur
30
- on an event, so events cause transitions to occur. Additionally, when an
31
- event fires, other arbitrary code can be executed, we call those actions.
32
- So any given state has a bunch of events, any event in a state causes a
33
- transition to another state and potentially causes code to be executed
34
- (an action). We can hook into states when they are entered, and exited
35
- from, and we can cause transitions to fail (guards), and we can hook in
36
- to every transition that occurs ever for whatever reason we can come up
37
- with.
38
-
39
- Now, all that's a mouthful, but we'll demonstrate the API bit by bit
40
- with a real-ish world example.
41
-
42
- Let's say we're modeling article submission from journalists. An article
43
- is written, then submitted. When it's submitted, it's awaiting review.
44
- Someone reviews the article, and then either accepts or rejects it.
45
- Here is the expression of this workflow using the API:
46
-
47
- class Article
48
- include Workflow
49
- workflow do
50
- state :new do
51
- event :submit, :transitions_to => :awaiting_review
52
- end
53
- state :awaiting_review do
54
- event :review, :transitions_to => :being_reviewed
55
- end
56
- state :being_reviewed do
57
- event :accept, :transitions_to => :accepted
58
- event :reject, :transitions_to => :rejected
59
- end
60
- state :accepted
61
- state :rejected
62
- end
63
- end
64
-
65
- Nice, isn't it!
66
-
67
- Note: the first state in the definition (`:new` in the example, but you
68
- can name it as you wish) is used as the initial state - newly created
69
- objects start their life cycle in that state.
70
-
71
- Let's create an article instance and check in which state it is:
72
-
73
- article = Article.new
74
- article.accepted? # => false
75
- article.new? # => true
76
-
77
- You can also access the whole `current_state` object including the list
78
- of possible events and other meta information:
79
-
80
- article.current_state
81
- => #<Workflow::State:0x7f1e3d6731f0 @events={
82
- :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
83
- @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
84
- name:new, meta{}
85
-
86
- You can also check, whether a state comes before or after another state (by the
87
- order they were defined):
88
-
89
- article.current_state
90
- => being_reviewed
91
- article.current_state < :accepted
92
- => true
93
- article.current_state >= :accepted
94
- => false
95
- article.current_state.between? :awaiting_review, :rejected
96
- => true
97
-
98
- Now we can call the submit event, which transitions to the
99
- <tt>:awaiting_review</tt> state:
100
-
101
- article.submit!
102
- article.awaiting_review? # => true
103
-
104
- Events are actually instance methods on a workflow, and depending on the
105
- state you're in, you'll have a different set of events used to
106
- transition to other states.
107
-
108
- It is also easy to check, if a certain transition is possible from the
109
- current state . `article.can_submit?` checks if there is a `:submit`
110
- event (transition) defined for the current state.
111
-
112
-
113
- Installation
114
- ------------
115
-
116
- gem install workflow
117
-
118
- **Important**: If you're interested in graphing your workflow state machine, you will also need to
119
- install the `activesupport` and `ruby-graphviz` gems.
120
-
121
- Versions up to and including 1.0.0 are also available as a single file download -
122
- [lib/workflow.rb file](https://github.com/geekq/workflow/blob/v1.0.0/lib/workflow.rb).
123
-
124
-
125
- Examples
126
- --------
127
-
128
- After installation or downloading of the library you can easily try out
129
- all the example code from this README in irb.
130
-
131
- $ irb
132
- require 'rubygems'
133
- require 'workflow'
134
-
135
- Now just copy and paste the source code from the beginning of this README
136
- file snippet by snippet and observe the output.
137
-
138
-
139
- Transition event handler
140
- ------------------------
141
-
142
- The best way is to use convention over configuration and to define a
143
- method with the same name as the event. Then it is automatically invoked
144
- when event is raised. For the Article workflow defined earlier it would
145
- be:
146
-
147
- class Article
148
- def reject
149
- puts 'sending email to the author explaining the reason...'
150
- end
151
- end
152
-
153
- `article.review!; article.reject!` will cause state transition to
154
- `being_reviewed` state, persist the new state (if integrated with
155
- ActiveRecord), invoke this user defined `reject` method and finally
156
- persist the `rejected` state.
157
-
158
- Note: on successful transition from one state to another the workflow
159
- gem immediately persists the new workflow state with `update_column()`,
160
- bypassing any ActiveRecord callbacks including `updated_at` update.
161
- This way it is possible to deal with the validation and to save the
162
- pending changes to a record at some later point instead of the moment
163
- when transition occurs.
164
-
165
- You can also define event handler accepting/requiring additional
166
- arguments:
167
-
168
- class Article
169
- def review(reviewer = '')
170
- puts "[#{reviewer}] is now reviewing the article"
171
- end
172
- end
173
-
174
- article2 = Article.new
175
- article2.submit!
176
- article2.review!('Homer Simpson') # => [Homer Simpson] is now reviewing the article
177
-
178
-
179
- ### The old, deprecated way
180
-
181
- The old way, using a block is still supported but deprecated:
182
-
183
- event :review, :transitions_to => :being_reviewed do |reviewer|
184
- # store the reviewer
185
- end
186
-
187
- We've noticed, that mixing the list of events and states with the blocks
188
- invoked for particular transitions leads to a bumpy and poorly readable code
189
- due to a deep nesting. We tried (and dismissed) lambdas for this. Eventually
190
- we decided to invoke an optional user defined callback method with the same
191
- name as the event (convention over configuration) as explained before.
192
-
193
- State persistence with ActiveRecord
194
- -----------------------------------
195
-
196
- Note: Workflow 2.0 is a major refactoring for the `worklow` library.
197
- If your application suddenly breaks after the workflow 2.0 release, you've
198
- probably got your Gemfile wrong ;-). workflow uses [semantic versioning][]. For
199
- highest compatibility please reference the desired major+minor version.
200
-
201
- Note on ActiveRecord/Rails 4.\*, 5.\* Support:
202
-
203
- Since integration with ActiveRecord makes over 90% of the issues and
204
- maintenance effort, and also to allow for an independent (faster) release cycle
205
- for Rails support, starting with workflow **version 2.0** in January 2019 the
206
- support for ActiveRecord (4.\*, 5.\* and newer) has been extracted into a separate
207
- gem. Read at [workflow-activerecord][], how to include the right gem.
208
-
209
- To use legacy built-in ActiveRecord 2.3 - 4.* support, reference Workflow 1.2 in
210
- your Gemfile:
211
-
212
- gem 'workflow', '~> 1.2'
213
-
214
- [semantic versioning]: https://guides.rubygems.org/patterns/#semantic-versioning
215
- [workflow-activerecord]: https://github.com/geekq/workflow-activerecord
216
-
217
- Custom workflow state persistence
218
- ---------------------------------
219
-
220
- If you do not use a relational database and ActiveRecord, you can still
221
- integrate the workflow very easily. To implement persistence you just
222
- need to override `load_workflow_state` and
223
- `persist_workflow_state(new_value)` methods. Next section contains an example for
224
- using CouchDB, a document oriented database.
225
-
226
- [Tim Lossen](http://tim.lossen.de/) implemented support
227
- for [remodel](http://github.com/tlossen/remodel) / [redis](http://github.com/antirez/redis)
228
- key-value store.
229
-
230
- Integration with CouchDB
231
- ------------------------
232
-
233
- We are using the compact [couchtiny library](http://github.com/geekq/couchtiny)
234
- here. But the implementation would look similar for the popular
235
- couchrest library.
236
-
237
- require 'couchtiny'
238
- require 'couchtiny/document'
239
- require 'workflow'
240
-
241
- class User < CouchTiny::Document
242
- include Workflow
243
- workflow do
244
- state :submitted do
245
- event :activate_via_link, :transitions_to => :proved_email
246
- end
247
- state :proved_email
248
- end
249
-
250
- def load_workflow_state
251
- self[:workflow_state]
252
- end
253
-
254
- def persist_workflow_state(new_value)
255
- self[:workflow_state] = new_value
256
- save!
257
- end
258
- end
259
-
260
- Please also have a look at
261
- [the full source code](http://github.com/geekq/workflow/blob/master/test/couchtiny_example.rb).
262
-
263
-
264
- Adapters to support other databases
265
- -----------------------------------
266
-
267
- I get a lot of requests to integrate persistence support for different
268
- databases, object-relational adapters, column stores, document
269
- databases.
270
-
271
- To enable highest possible quality, avoid too many dependencies and to
272
- avoid unneeded maintenance burden on the `workflow` core it is best to
273
- implement such support as a separate gem.
274
-
275
- Only support for the ActiveRecord will remain for the foreseeable
276
- future. So Rails beginners can expect `workflow` to work with Rails out
277
- of the box. Other already included adapters stay for a while but should
278
- be extracted to separate gems.
279
-
280
- If you want to implement support for your favorite ORM mapper or your
281
- favorite NoSQL database, you just need to implement a module which
282
- overrides the persistence methods `load_workflow_state` and
283
- `persist_workflow_state`. Example:
284
-
285
- module Workflow
286
- module SuperCoolDb
287
- module InstanceMethods
288
- def load_workflow_state
289
- # Load and return the workflow_state from some storage.
290
- # You can use self.class.workflow_column configuration.
291
- end
292
-
293
- def persist_workflow_state(new_value)
294
- # save the new_value workflow state
295
- end
296
- end
297
-
298
- module ClassMethods
299
- # class methods of your adapter go here
300
- end
301
-
302
- def self.included(klass)
303
- klass.send :include, InstanceMethods
304
- klass.extend ClassMethods
305
- end
306
- end
307
- end
308
-
309
- The user of the adapter can use it then as:
310
-
311
- class Article
312
- include Workflow
313
- include Workflow:SuperCoolDb
314
- workflow do
315
- state :submitted
316
- # ...
317
- end
318
- end
319
-
320
- I can then link to your implementation from this README. Please let me
321
- also know, if you need any interface beyond `load_workflow_state` and
322
- `persist_workflow_state` methods to implement an adapter for your
323
- favorite database.
324
-
325
-
326
- Accessing your workflow specification
327
- -------------------------------------
328
-
329
- You can easily reflect on workflow specification programmatically - for
330
- the whole class or for the current object. Examples:
331
-
332
- article2.current_state.events # lists possible events from here
333
- article2.current_state.events[:reject].transitions_to # => :rejected
334
-
335
- Article.workflow_spec.states.keys
336
- #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
337
-
338
- Article.workflow_spec.state_names
339
- #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
340
-
341
- # list all events for all states
342
- Article.workflow_spec.states.values.collect &:events
343
-
344
-
345
- You can also store and later retrieve additional meta data for every
346
- state and every event:
347
-
348
- class MyProcess
349
- include Workflow
350
- workflow do
351
- state :main, :meta => {:importance => 8}
352
- state :supplemental, :meta => {:importance => 1}
353
- end
354
- end
355
- puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1
356
-
357
- The workflow library itself uses this feature to tweak the graphical
358
- representation of the workflow. See below.
359
-
360
-
361
- Conditional event transitions
362
- -----------------------------
363
-
364
- Conditions can be a "method name symbol" with a corresponding instance method, a `proc` or `lambda` which are added to events, like so:
365
-
366
- state :off
367
- event :turn_on, :transition_to => :on,
368
- :if => :sufficient_battery_level?
369
-
370
- event :turn_on, :transition_to => :low_battery,
371
- :if => proc { |device| device.battery_level > 0 }
372
- end
373
-
374
- # corresponding instance method
375
- def sufficient_battery_level?
376
- battery_level > 10
377
- end
378
-
379
- When calling a `device.can_<fire_event>?` check, or attempting a `device.<event>!`, each event is checked in turn:
380
-
381
- * With no `:if` check, proceed as usual.
382
- * If an `:if` check is present, proceed if it evaluates to true, or drop to the next event.
383
- * If you've run out of events to check (eg. `battery_level == 0`), then the transition isn't possible.
384
-
385
-
386
- Advanced transition hooks
387
- -------------------------
388
-
389
- ### on_entry/on_exit
390
-
391
- We already had a look at the declaring callbacks for particular workflow
392
- events. If you would like to react to all transitions to/from the same state
393
- in the same way you can use the on_entry/on_exit hooks. You can either define it
394
- with a block inside the workflow definition or through naming
395
- convention, e.g. for the state :pending just define the method
396
- `on_pending_exit(new_state, event, *args)` somewhere in your class.
397
-
398
- ### on_transition
399
-
400
- If you want to be informed about everything happening everywhere, e.g. for
401
- logging then you can use the universal `on_transition` hook:
402
-
403
- workflow do
404
- state :one do
405
- event :increment, :transitions_to => :two
406
- end
407
- state :two
408
- on_transition do |from, to, triggering_event, *event_args|
409
- Log.info "#{from} -> #{to}"
410
- end
411
- end
412
-
413
- Please also have a look at the [advanced end to end
414
- example][advanced_hooks_and_validation_test].
415
-
416
- [advanced_hooks_and_validation_test]: http://github.com/geekq/workflow/blob/master/test/advanced_hooks_and_validation_test.rb
417
-
418
- ### on_error
419
-
420
- If you want to do custom exception handling internal to workflow, you can define an `on_error` hook in your workflow.
421
- For example:
422
-
423
- workflow do
424
- state :first do
425
- event :forward, :transitions_to => :second
426
- end
427
- state :second
428
-
429
- on_error do |error, from, to, event, *args|
430
- Log.info "Exception(#error.class) on #{from} -> #{to}"
431
- end
432
- end
433
-
434
- If forward! results in an exception, `on_error` is invoked and the workflow stays in a 'first' state. This capability
435
- is particularly useful if your errors are transient and you want to queue up a job to retry in the future without
436
- affecting the existing workflow state.
437
-
438
- ### Guards
439
-
440
- If you want to halt the transition conditionally, you can just raise an
441
- exception in your [transition event handler](#transition_event_handler).
442
- There is a helper called `halt!`, which raises the
443
- Workflow::TransitionHalted exception. You can provide an additional
444
- `halted_because` parameter.
445
-
446
- def reject(reason)
447
- halt! 'We do not reject articles unless the reason is important' \
448
- unless reason =~ /important/i
449
- end
450
-
451
- The traditional `halt` (without the exclamation mark) is still supported
452
- too. This just prevents the state change without raising an
453
- exception.
454
-
455
- You can check `halted?` and `halted_because` values later.
456
-
457
- ### Hook order
458
-
459
- The whole event sequence is as follows:
460
-
461
- * before_transition
462
- * event specific action
463
- * on_transition (if action did not halt)
464
- * on_exit
465
- * PERSIST WORKFLOW STATE, i.e. transition
466
- * on_entry
467
- * after_transition
468
-
469
-
470
- Documenting with diagrams
471
- -------------------------
472
-
473
- You can generate a graphical representation of the workflow for
474
- a particular class for documentation purposes.
475
- Use `Workflow::create_workflow_diagram(class)` in your rake task like:
476
-
477
- namespace :doc do
478
- desc "Generate a workflow graph for a model passed e.g. as 'MODEL=Order'."
479
- task :workflow => :environment do
480
- require 'workflow/draw'
481
- Workflow::Draw::workflow_diagram(ENV['MODEL'].constantize)
482
- end
483
- end
484
-
485
-
486
- Development Setup
487
- -----------------
488
-
489
- sudo apt-get install graphviz # Linux
490
- brew cask install graphviz # Mac OS
491
- cd workflow
492
- gem install bundler
493
- bundle install
494
- # run all the tests
495
- bundle exec rake test
496
-
497
-
498
- Changelog
499
- ---------
500
-
501
- ### New in the version 2.0.0
502
-
503
- * extract Rails/ActiveRecord integration into a separate gem
504
- workflow-activerecord
505
- * Remodel integration removed - needs to be a separate gem
506
-
507
- Special thanks to [voltechs][] for implementing Rails 5 support
508
- and helping to revive `workflow`!
509
-
510
- [voltechs]: https://github.com/voltechs
511
-
512
- ### New in the upcoming version 1.3.0 (never released)
513
-
514
- * Retiring Ruby 1.8.7 and Rails 2 support #118. If you still need this older
515
- versions despite security issues and missing updates, you can use
516
- workflow 1.2.0 or older. In your Gemfile put
517
-
518
- gem 'workflow', '~> 1.2.0'
519
-
520
- or when using github source just reference the v1.2.0 tag.
521
- * improved callback method handling: #113 and #125
522
-
523
- ### New in the version 1.2.0
524
-
525
- * Fix issue #98 protected on\_\* callbacks in Ruby 2
526
- * #106 Inherit exceptions from StandardError instead of Exception
527
- * #109 Conditional event transitions, contributed by [damncabbage](http://robhoward.id.au/)
528
- Please note: this introduces incompatible changes to the meta data API, see also #131.
529
- * New policy for supporting other databases - extract to separate
530
- gems. See the README section above.
531
- * #111 Custom Versions of Existing Adapters by [damncabbage](http://robhoward.id.au/)
532
-
533
-
534
- ### New in the version 1.1.0
535
-
536
- * Tested with ActiveRecord 4.0 (Rails 4.0)
537
- * Tested with Ruby 2.0
538
- * automatically generated scopes with names based on state names
539
- * clean workflow definition override for class inheritance - undefining
540
- the old convinience methods, s. <http://git.io/FZO02A>
541
-
542
- ### New in the version 1.0.0
543
-
544
- * **Support to private/protected callback methods.**
545
- See also issues [#53](https://github.com/geekq/workflow/pull/53)
546
- and [#58](https://github.com/geekq/workflow/pull/58). With the new
547
- implementation:
548
-
549
- * callback methods can be hidden (non public): both private methods
550
- in the immediate class and protected methods somewhere in the class
551
- hierarchy are supported
552
- * no unintentional calls on `fail!` and other Kernel methods
553
- * inheritance hierarchy with workflow is supported
554
-
555
- * using Rails' 3.1 `update_column` whenever available so only the
556
- workflow state column and not other pending attribute changes are
557
- saved on state transition. Fallback to `update_attribute` for older
558
- Rails and other ORMs. [commit](https://github.com/geekq/workflow/commit/7e091d8ded1aeeb0a86647bbf7d78ab3c9d0c458)
559
-
560
- ### New in the version 0.8.7
561
-
562
- * switch from [jeweler][] to pure bundler for building gems
563
-
564
- ### New in the version 0.8.0
565
-
566
- * check if a certain transition possible from the current state with
567
- `can_....?`
568
- * fix workflow_state persistence for multiple_workflows example
569
- * add before_transition and after_transition hooks as suggested by
570
- [kasperbn](https://github.com/kasperbn)
571
-
572
- ### New in the version 0.7.0
573
-
574
- * fix issue#10 Workflow::create_workflow_diagram documentation and path
575
- escaping
576
- * fix issue#7 workflow_column does not work STI (single table
577
- inheritance) ActiveRecord models
578
- * fix issue#5 Diagram generation fails for models in modules
579
-
580
- ### New in the version 0.6.0
581
-
582
- * enable multiple workflows by connecting workflow to object instances
583
- (using metaclass) instead of connecting to a class, s. "Multiple
584
- Workflows" section
585
-
586
- ### New in the version 0.5.0
587
-
588
- * fix issue#3 change the behaviour of halt! to immediately raise an
589
- exception. See also http://github.com/geekq/workflow/issues/#issue/3
590
-
591
- ### New in the version 0.4.0
592
-
593
- * completely rewritten the documentation to match my branch
594
- * switch to [jeweler][] for building gems
595
- * use [gemcutter][] for gem distribution
596
- * every described feature is backed up by an automated test
597
-
598
- [jeweler]: http://github.com/technicalpickles/jeweler
599
- [gemcutter]: http://gemcutter.org/gems/workflow
600
-
601
- ### New in the version 0.3.0
602
-
603
- Intermixing of transition graph definition (states, transitions)
604
- on the one side and implementation of the actions on the other side
605
- for a bigger state machine can introduce clutter.
606
-
607
- To reduce this clutter it is now possible to use state entry- and
608
- exit- hooks defined through a naming convention. For example, if there
609
- is a state :pending, then instead of using a
610
- block:
611
-
612
- state :pending do
613
- on_entry do
614
- # your implementation here
615
- end
616
- end
617
-
618
- you can hook in by defining method
619
-
620
- def on_pending_exit(new_state, event, *args)
621
- # your implementation here
622
- end
623
-
624
- anywhere in your class. You can also use a simpler function signature
625
- like `def on_pending_exit(*args)` if your are not interested in
626
- arguments. Please note: `def on_pending_exit()` with an empty list
627
- would not work.
628
-
629
- If both a function with a name according to naming convention and the
630
- on_entry/on_exit block are given, then only on_entry/on_exit block is used.
631
-
632
-
633
- Support
634
- -------
635
-
636
- ### Reporting bugs
637
-
638
- <http://github.com/geekq/workflow/issues>
639
-
640
-
641
- About
642
- -----
643
-
644
- Author: Vladimir Dobriakov, <https://infrastructure-as-code.de>
645
-
646
- Copyright (c) 2010-2019 Vladimir Dobriakov and Contributors
647
-
648
- Copyright (c) 2008-2009 Vodafone
649
-
650
- Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
651
-
652
- Based on the work of Ryan Allen and Scott Barron
653
-
654
- Licensed under MIT license, see the MIT-LICENSE file.
655
-