rails-workflow 1.4.4.4 → 1.4.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.yardopts +1 -1
- data/README.markdown +4 -664
- data/lib/active_support/overloads.rb +32 -0
- data/lib/workflow.rb +31 -37
- data/lib/workflow/callbacks.rb +5 -5
- data/lib/workflow/callbacks/{transition_callback_wrapper.rb → transition_callback.rb} +23 -23
- data/lib/workflow/callbacks/transition_callbacks/method_wrapper.rb +102 -0
- data/lib/workflow/callbacks/transition_callbacks/proc_wrapper.rb +48 -0
- data/lib/workflow/event.rb +55 -12
- data/lib/workflow/specification.rb +6 -6
- data/lib/workflow/state.rb +38 -10
- data/lib/workflow/version.rb +1 -1
- data/rails-workflow.gemspec +1 -1
- metadata +7 -5
- data/lib/workflow/callbacks/transition_callback_method_wrapper.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c00b8e437381e8b05f1f9b870667c24c06b9ec64
|
4
|
+
data.tar.gz: 429a9bbdff4aab6ff053d2f395d2be1bbafbba2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 732795994aa948a71b19bbe1fe1c5da5ea6814de9a146615d0eb5ae1fac2cd67c174619b9d1e6c64a943b94c32e6ccbab151c668bbcb8d56c532f02d4e7d46f1
|
7
|
+
data.tar.gz: a44fb7792d8f027fb953d9b789a12bbad7d837002f3a14b9acfda52375e3f274391fb352f3c29ad6f43477123b7f715726a91a38a2f794c7ce50aec8a698eef5
|
data/.gitignore
CHANGED
data/.yardopts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--markup=markdown
|
2
|
-
|
2
|
+
--output-dir docs
|
data/README.markdown
CHANGED
@@ -1,675 +1,15 @@
|
|
1
|
-
|
2
|
-
-----------------
|
1
|
+
# Workflow For Rails Apps
|
3
2
|
|
4
|
-
|
3
|
+
https://tylergannon.github.io/rails-workflow/
|
5
4
|
|
6
|
-
## What's different in rails-workflow
|
7
5
|
|
8
|
-
|
9
|
-
to enable a more flexible application of callbacks.
|
10
|
-
* Slightly terser syntax for event definition.
|
11
|
-
* Cleaner support for using conditional ActiveRecord validations to validate state transitions.
|
6
|
+
2016 Tyler Gannon http://tylergannon.github.io
|
12
7
|
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
gem install rails-workflow
|
17
|
-
|
18
|
-
## Configuration
|
19
|
-
|
20
|
-
No configuraion is required, but the following configurations can be placed inside an initializer:
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
# config/initializers/workflow.rb
|
24
|
-
Workflow.configure do |config|
|
25
|
-
# Set false to avoid the extra call to the database, if you'll be saving the object after transition.
|
26
|
-
self.persist_workflow_state_immediately = true
|
27
|
-
# Set true to also change the `:updated_at` during state transition.
|
28
|
-
self.touch_on_update_column = false
|
29
|
-
end
|
30
|
-
|
31
|
-
```
|
32
|
-
|
33
|
-
Ruby Version
|
34
|
-
--------
|
35
|
-
|
36
|
-
I've only tested with Ruby 2.3. ;)
|
37
|
-
|
38
|
-
# Basic workflow definition:
|
39
|
-
|
40
|
-
```ruby
|
41
|
-
class Article
|
42
|
-
include Workflow
|
43
|
-
workflow do
|
44
|
-
state :new do
|
45
|
-
on :submit, to: :awaiting_review
|
46
|
-
end
|
47
|
-
state :awaiting_review do
|
48
|
-
on :review, to: :being_reviewed
|
49
|
-
end
|
50
|
-
state :being_reviewed do
|
51
|
-
on :accept, to: :accepted
|
52
|
-
on :reject, to: :rejected
|
53
|
-
end
|
54
|
-
state :accepted
|
55
|
-
state :rejected
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
```
|
60
|
-
|
61
|
-
## Invoking State Transitions
|
62
|
-
|
63
|
-
You may call the method named for the event itself, or else the more generic `transition!` method
|
64
|
-
|
65
|
-
```ruby
|
66
|
-
a = Article.new
|
67
|
-
a.current_state.name
|
68
|
-
# => :new
|
69
|
-
a.submit!
|
70
|
-
a.current_state.name
|
71
|
-
# => :awaiting_review
|
72
|
-
# ... etc
|
73
|
-
```
|
74
|
-
|
75
|
-
```ruby
|
76
|
-
a = Article.new
|
77
|
-
a.transition! :submit
|
78
|
-
a.current_state.name
|
79
|
-
# => :awaiting_review
|
80
|
-
```
|
81
|
-
|
82
|
-
The transition will return a truthy value if it succeeds: either the return value
|
83
|
-
of the event-specific callback, if one is defined, or else the name of the new state
|
84
|
-
|
85
|
-
```ruby
|
86
|
-
puts a.transition!(:submit)
|
87
|
-
# => :awaiting_review
|
88
|
-
```
|
89
|
-
|
90
|
-
If the transition does not finish and no exception is raised, the method returns `false`.
|
91
|
-
|
92
|
-
Generally this would be because of a validation failure, so checking the model for errors
|
93
|
-
would be the next course of action.
|
94
|
-
|
95
|
-
You can also pass arguments to the event, though nothing will happen with them except
|
96
|
-
as you've defined in your callbacks (described below)
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
a.submit!(author: 'Fanny Schmittenbauer', awesomeness: 29)
|
100
|
-
```
|
101
|
-
|
102
|
-
Access an object representing the current state of the entity,
|
103
|
-
including available events and transitions:
|
104
|
-
|
105
|
-
```ruby
|
106
|
-
article.current_state
|
107
|
-
# => <State name=:new events(1)=[<Event name=:submit transitions(1)=[<to=<State name=:awaiting_review events(1)=[<Event name=:review transitions(1)=...
|
108
|
-
```
|
109
|
-
|
110
|
-
On Ruby 1.9 and above, you can check whether a state comes before or
|
111
|
-
after another state (by the order they were defined):
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
article.current_state
|
115
|
-
# => being_reviewed
|
116
|
-
article.current_state < :accepted
|
117
|
-
# => true
|
118
|
-
article.current_state >= :accepted
|
119
|
-
# => false
|
120
|
-
article.current_state.between? :awaiting_review, :rejected
|
121
|
-
# => true
|
122
|
-
```
|
123
|
-
Now we can call the submit event, which transitions to the
|
124
|
-
<tt>:awaiting_review</tt> state:
|
125
|
-
|
126
|
-
article.submit!
|
127
|
-
article.awaiting_review? # => true
|
128
|
-
|
129
|
-
# Multiple Possible Targets For A Given Event
|
130
|
-
|
131
|
-
The first matching condition will determine the target state.
|
132
|
-
An error will be raised if none match, so a catchall at the end is a good idea.
|
133
|
-
|
134
|
-
```ruby
|
135
|
-
class Article
|
136
|
-
include Workflow
|
137
|
-
workflow do
|
138
|
-
state :new do
|
139
|
-
on :submit do
|
140
|
-
to :awaiting_review, if: :today_is_wednesday?
|
141
|
-
to :being_reviewed, unless: "author.name == 'Foo Bar'"
|
142
|
-
to :accepted, if: -> {author.role == 'Admin'}
|
143
|
-
to :rejected, if: [:bad_hair_day?, :in_a_bad_mood?]
|
144
|
-
to :the_bad_place
|
145
|
-
end
|
146
|
-
end
|
147
|
-
state :awaiting_review do
|
148
|
-
on :review, to: :being_reviewed
|
149
|
-
end
|
150
|
-
state :being_reviewed do
|
151
|
-
on :accept, to: :accepted
|
152
|
-
on :reject, to: :rejected
|
153
|
-
end
|
154
|
-
state :accepted
|
155
|
-
state :rejected
|
156
|
-
state :the_bad_place
|
157
|
-
end
|
158
|
-
end
|
159
|
-
```
|
160
|
-
|
161
|
-
Callbacks
|
162
|
-
-------------------------
|
163
|
-
|
164
|
-
The DSL syntax here is very much similar to ActionController or ActiveRecord callbacks.
|
165
|
-
|
166
|
-
Three classes of callbacks:
|
167
|
-
|
168
|
-
* :transition callbacks representing named events.
|
169
|
-
* `before_transition only: :submit`
|
170
|
-
* `after_transition except: :submit`
|
171
|
-
* :exit callbacks that match on the state the transition leaves
|
172
|
-
* `before_exit only: :being_reviewed #will run on the :accept or the :reject event`
|
173
|
-
* :enter callbacks that match on the target state for the transition
|
174
|
-
* `before_enter only: :being_reviewed` #will run on the :review event
|
175
|
-
|
176
|
-
Callbacks run in this order:
|
177
|
-
|
178
|
-
* `before_transition`, `around_transition`
|
179
|
-
* `before_exit`, `around_exit`
|
180
|
-
* `before_enter`, `around_enter`
|
181
|
-
* **State Transition**
|
182
|
-
* `after_enter`
|
183
|
-
* `after_exit`
|
184
|
-
* `after_transition`
|
185
|
-
|
186
|
-
Within each group, the callbacks fire in the order they are set.
|
187
|
-
|
188
|
-
### Halting callbacks
|
189
|
-
Inside any `:before` callback, you can halt the callback chain:
|
190
|
-
|
191
|
-
```ruby
|
192
|
-
before_enter do
|
193
|
-
throw :abort
|
194
|
-
end
|
195
|
-
```
|
196
|
-
|
197
|
-
Note that this will halt the callback chain without an error,
|
198
|
-
so you won't get an exception in your `on_error` block, if you have one.
|
199
|
-
|
200
|
-
## Around Transition
|
201
|
-
|
202
|
-
Allows you to run code surrounding the state transition.
|
203
|
-
|
204
|
-
```ruby
|
205
|
-
around_transition :wrap_in_transaction
|
206
|
-
|
207
|
-
def wrap_in_transaction(&block)
|
208
|
-
Article.transaction(&block)
|
209
|
-
end
|
210
|
-
```
|
211
|
-
|
212
|
-
You can also define the callback using a block:
|
213
|
-
|
214
|
-
```ruby
|
215
|
-
around_transition do |object, transition|
|
216
|
-
object.with_lock do
|
217
|
-
transition.call
|
218
|
-
end
|
219
|
-
end
|
220
|
-
```
|
221
|
-
|
222
|
-
## before_transition
|
223
|
-
|
224
|
-
Allows you to run code prior to the state transition.
|
225
|
-
If you `halt` or `throw :abort` within a `before_transition`, the callback chain
|
226
|
-
will be halted, the transition will be canceled and the event action
|
227
|
-
will return false.
|
228
|
-
|
229
|
-
```ruby
|
230
|
-
before_transition :check_title
|
231
|
-
|
232
|
-
def check_title
|
233
|
-
halt('Title was bad.') unless title == "Good Title"
|
234
|
-
end
|
235
|
-
```
|
236
|
-
|
237
|
-
Or again, in block expression:
|
238
|
-
|
239
|
-
```ruby
|
240
|
-
before_transition do |article|
|
241
|
-
throw :abort unless article.title == "Good Title"
|
242
|
-
end
|
243
|
-
```
|
244
|
-
## After Transition
|
245
|
-
|
246
|
-
Runs code after the transition.
|
247
|
-
|
248
|
-
```ruby
|
249
|
-
after_transition :check_title
|
250
|
-
```
|
251
|
-
|
252
|
-
## Prepend Transitions
|
253
|
-
|
254
|
-
To add a callback to the beginning of the sequence:
|
255
|
-
|
256
|
-
```ruby
|
257
|
-
prepend_before_transition :some_before_transition
|
258
|
-
prepend_around_transition :some_around_transition
|
259
|
-
prepend_after_transition :some_after_transition
|
260
|
-
```
|
261
|
-
|
262
|
-
## Skip Transitions
|
263
|
-
|
264
|
-
```ruby
|
265
|
-
skip_before_transition :some_before_transition
|
266
|
-
```
|
267
|
-
|
268
|
-
|
269
|
-
## Conditions
|
270
|
-
|
271
|
-
### if/unless
|
272
|
-
|
273
|
-
The callback will run `if` or `unless` the named method returns a truthy value.
|
274
|
-
|
275
|
-
```ruby
|
276
|
-
before_transition :do_something, if: :valid?
|
277
|
-
|
278
|
-
# Array conditions apply if all aggregated conditions apply.
|
279
|
-
before_transition :do_something, if: [:valid?, :kosher?]
|
280
|
-
before_transition :do_something, if: [:valid?, "title == 'Good Title'"]
|
281
|
-
before_transition :do_something, unless: [:valid?, -> {title == 'Good Title'}]
|
282
|
-
```
|
283
|
-
|
284
|
-
### only/except
|
285
|
-
|
286
|
-
The three callback classes accept `:only` and `:except` parameters, and treat them slightly differnetly.
|
287
|
-
|
288
|
-
You can use `:only` and `:except` in conjunction with `:if` and `:unless`.
|
289
|
-
|
290
|
-
* **Transition Callbacks** match on the name of the event being executed.
|
291
|
-
* `before_transition only: :submit` will run when the `:submit` event is fired
|
292
|
-
* `before_transition except: [:submit, :reject]` will run on any event except the two named
|
293
|
-
* **Exit Callbacks** match on the name of the state being exited
|
294
|
-
* `before_exit only: :new` will run when an event causes the object to leave the `:new` state.
|
295
|
-
* **Enter Callbacks** match on the name of the state being entered
|
296
|
-
* `before_enter only: [:cancelled, :rejected]` will run when an event leaves the object `:cancelled` or `:rejected`.
|
297
|
-
|
298
|
-
## Parameterized Callbacks
|
299
|
-
|
300
|
-
If you're passing parameters through the `transition!` method, you can receive
|
301
|
-
them easily in your callbacks. Workflow::Callbacks will inspect the parameters of
|
302
|
-
your block or method and pass just the ones you ask for.
|
303
|
-
|
304
|
-
Three magic names apply to parameter names defined on callbacks:
|
305
|
-
|
306
|
-
* `event` will be set to the name of the event being processed.
|
307
|
-
* `to` will be set to the name of the destination state.
|
308
|
-
* `from` will be set to the name of the state being exited.
|
309
|
-
|
310
|
-
Other parameters will be `shift`ed from what you pass in, so they should be taken
|
311
|
-
back in order. As in:
|
312
|
-
|
313
|
-
```ruby
|
314
|
-
def my_after_submit(from, cool, *args)
|
315
|
-
logger.warn [from, cool, args]
|
316
|
-
end
|
317
|
-
after_transition :my_after_submit, only: :submit
|
318
|
-
|
319
|
-
an_article.submit! :rad, 1, 2, 3
|
320
|
-
# => [:submit, :rad, [1, 2, 3]]
|
321
|
-
```
|
322
|
-
|
323
|
-
You can also use keyword arguments:
|
324
|
-
|
325
|
-
```ruby
|
326
|
-
def my_after_submit(to, hype:, **args)
|
327
|
-
logger.warn [to, hype, args]
|
328
|
-
end
|
329
|
-
after_transition :my_after_submit, only: :submit
|
330
|
-
|
331
|
-
an_article.submit! a: 1, b: 2, hype: 4.5, c: 3
|
332
|
-
# => [:awaiting_review, 4.5, {a: 1, b: 2, c: 3}]
|
333
|
-
```
|
334
|
-
|
335
|
-
Around callbacks should be sure to yield the block given:
|
336
|
-
|
337
|
-
```ruby
|
338
|
-
after_transition :my_after_submit, only: :submit
|
339
|
-
|
340
|
-
def my_after_submit(to, hype:, **args)
|
341
|
-
logger.warn [to, hype, args]
|
342
|
-
yield
|
343
|
-
end
|
344
|
-
|
345
|
-
def my_after_submit(to, hype:, **args, &bloci)
|
346
|
-
logger.warn [to, hype, args]
|
347
|
-
block.call
|
348
|
-
end
|
349
|
-
|
350
|
-
```
|
351
|
-
|
352
|
-
Note that in order to use block-style callbacks with special arguments, unless you
|
353
|
-
use strictly keyword arguments, the first
|
354
|
-
parameters to your block must be the object and callback block (for around callbacks):
|
355
|
-
|
356
|
-
```ruby
|
357
|
-
before_transition do |reviewer:|
|
358
|
-
logger.debug reviewer.name
|
359
|
-
end
|
360
|
-
Article.last.transition! :submit, reviewer: current_user
|
361
|
-
|
362
|
-
before_transition do |obj, reviewer|
|
363
|
-
logger.debug reviewer.name
|
364
|
-
end
|
365
|
-
Article.last.transition! :submit, current_user
|
366
|
-
|
367
|
-
around_transition do |obj, callbacks, reviewer|
|
368
|
-
logger.debug reviewer.name
|
369
|
-
callbacks.call
|
370
|
-
end
|
371
|
-
Article.last.transition! :submit, current_user
|
372
|
-
|
373
|
-
```
|
374
|
-
|
375
|
-
If you don't like keyword arguments you can use standard arguments, but you
|
376
|
-
need to receive the model as the first argument to your block, and you have to
|
377
|
-
configure the `event_args` for the transition context, within your workflow definition.
|
378
|
-
|
379
|
-
```ruby
|
380
|
-
before_transition, only: :submit do |article, review_date, reviewer:|
|
381
|
-
puts review_date
|
382
|
-
end
|
383
|
-
|
384
|
-
Article.last.transition! :submit, Date.today, reviewer: current_user
|
385
|
-
|
386
|
-
```
|
387
|
-
|
388
|
-
## Catching Errors
|
389
|
-
|
390
|
-
```ruby
|
391
|
-
class WorkflowModel
|
392
|
-
include Workflow
|
393
|
-
|
394
|
-
# Some possibilities:
|
395
|
-
on_error StandardError, rescue: "self.errors << 'oops!'"
|
396
|
-
on_error StandardError, rescue: :notify_error_service!
|
397
|
-
|
398
|
-
# Default error class is Exception
|
399
|
-
on_error unless: "logger.nil?" do |ex|
|
400
|
-
logger.warn ex.message
|
401
|
-
raise ApplicationError.new('Whoopsies!')
|
402
|
-
end
|
403
|
-
|
404
|
-
on_error ensure: ->{self.always_run_this!}, only: :process
|
405
|
-
|
406
|
-
on_error SomeAppError, ensure: ->{self.always_run_this!} do |ex|
|
407
|
-
# SomeAppError and its subclasses will be rescued and this block will run.
|
408
|
-
# The ensure proc will be run in the ensure block.
|
409
|
-
logger.debug "Couldn't complete transition: #{transition_context.event} because: #{ex.message}"
|
410
|
-
end
|
411
|
-
|
412
|
-
workflow do
|
413
|
-
state :initial do
|
414
|
-
on :process, to: :processing
|
415
|
-
on :different_process, to: :processing
|
416
|
-
end
|
417
|
-
state :processing do
|
418
|
-
on :finish, to: :done
|
419
|
-
end
|
420
|
-
state :done
|
421
|
-
end
|
422
|
-
end
|
423
|
-
```
|
424
|
-
|
425
|
-
## Ensuring code will run
|
426
|
-
|
427
|
-
```ruby
|
428
|
-
|
429
|
-
# This will happen no matter what, whenever the process! event is run.
|
430
|
-
ensure_after_transitions only: :process do
|
431
|
-
self.messages << :foo
|
432
|
-
end
|
433
|
-
|
434
|
-
ensure_after_transitions :clean_up_resources!
|
435
|
-
|
436
|
-
```
|
437
|
-
|
438
|
-
## Conditional Validations
|
439
|
-
|
440
|
-
If you are using `ActiveRecord`, you'll have access to a set of methods which
|
441
|
-
describe the current transition underway.
|
442
|
-
|
443
|
-
Inside the same Article class which was begun above, the following three
|
444
|
-
validations would all run when the `submit` event is used to transition
|
445
|
-
from `new` to `awaiting_review`.
|
446
|
-
|
447
|
-
```ruby
|
448
|
-
validates :title, presence: true, if: :transitioning_to_awaiting_review?
|
449
|
-
validates :body, presence: true, if: :transitioning_from_new?
|
450
|
-
validates :author, presence: true, if: :transitioning_via_event_submit?
|
451
|
-
```
|
452
|
-
|
453
|
-
### Halting if validations fail
|
454
|
-
|
455
|
-
# This will create a transition callback which will stop the event
|
456
|
-
# and return false if validations fail.
|
457
|
-
|
458
|
-
halt_transition_unless_valid!
|
459
|
-
|
460
|
-
# This is the same as doing
|
461
|
-
|
462
|
-
before_transition do
|
463
|
-
throw :abort unless valid?
|
464
|
-
end
|
465
|
-
|
466
|
-
### Checking A Transition
|
467
|
-
|
468
|
-
Call `can_transition?` to determine whether the validations would pass if a
|
469
|
-
given event was called:
|
470
|
-
|
471
|
-
```ruby
|
472
|
-
if article.can_transition?(:submit)
|
473
|
-
# Do something interesting
|
474
|
-
end
|
475
|
-
```
|
476
|
-
|
477
|
-
# Transition Context
|
478
|
-
|
479
|
-
During transition you can refer to the `transition_context` object on your model,
|
480
|
-
for information about the current transition. See [Workflow::TransitionContext].
|
481
|
-
|
482
|
-
## Naming Event Arguments
|
483
|
-
|
484
|
-
If you will normally call each of your events with the same arguments, the following
|
485
|
-
will help:
|
486
|
-
|
487
|
-
```ruby
|
488
|
-
class Article < ApplicationRecord
|
489
|
-
include Workflow
|
490
|
-
|
491
|
-
before_transition :check_reviewer
|
492
|
-
|
493
|
-
def check_reviewer
|
494
|
-
# Ability is a class from the cancan gem: https://github.com/CanCanCommunity/cancancan
|
495
|
-
halt('Access denied') unless Ability.new(transition_context.reviewer).can?(:review, self)
|
496
|
-
end
|
497
|
-
|
498
|
-
workflow do
|
499
|
-
event_args :reviewer, :reviewed_at
|
500
|
-
state :new do
|
501
|
-
on :review, to: :reviewed
|
502
|
-
end
|
503
|
-
state :reviewed
|
504
|
-
end
|
505
|
-
end
|
506
|
-
```
|
507
|
-
|
508
|
-
Transition event handler
|
509
|
-
------------------------
|
510
|
-
|
511
|
-
You can define a method with the same name as the event. Then it is automatically invoked
|
512
|
-
when event is raised. For the Article workflow defined earlier it would
|
513
|
-
be:
|
514
|
-
|
515
|
-
```ruby
|
516
|
-
class Article
|
517
|
-
def reject
|
518
|
-
puts 'sending email to the author explaining the reason...'
|
519
|
-
end
|
520
|
-
end
|
521
|
-
```
|
522
|
-
|
523
|
-
`article.review!; article.reject!` will cause state transition to
|
524
|
-
`being_reviewed` state, persist the new state (if integrated with
|
525
|
-
ActiveRecord), invoke this user defined `reject` method and finally
|
526
|
-
persist the `rejected` state.
|
527
|
-
|
528
|
-
|
529
|
-
You can also define event handler accepting/requiring additional
|
530
|
-
arguments:
|
531
|
-
|
532
|
-
```ruby
|
533
|
-
class Article
|
534
|
-
def review(reviewer = '')
|
535
|
-
puts "[#{reviewer}] is now reviewing the article"
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
article2 = Article.new
|
540
|
-
article2.submit!
|
541
|
-
article2.review!('Homer Simpson') # => [Homer Simpson] is now reviewing the article
|
542
|
-
```
|
543
|
-
|
544
|
-
Integration with ActiveRecord
|
545
|
-
-----------------------------
|
546
|
-
|
547
|
-
Workflow library can handle the state persistence fully automatically. You
|
548
|
-
only need to define a string field on the table called `workflow_state`
|
549
|
-
and include the workflow mixin in your model class as usual:
|
550
|
-
|
551
|
-
```ruby
|
552
|
-
class Order < ActiveRecord::Base
|
553
|
-
include Workflow
|
554
|
-
workflow do
|
555
|
-
# list states and transitions here
|
556
|
-
end
|
557
|
-
end
|
558
|
-
```
|
559
|
-
|
560
|
-
On a database record loading all the state check methods e.g.
|
561
|
-
`article.state`, `article.awaiting_review?` are immediately available.
|
562
|
-
For new records or if the `workflow_state` field is not set the state
|
563
|
-
defaults to the first state declared in the workflow specification. In
|
564
|
-
our example it is `:new`, so `Article.new.new?` returns true and
|
565
|
-
`Article.new.approved?` returns false.
|
566
|
-
|
567
|
-
At the end of a successful state transition like `article.approve!` the
|
568
|
-
new state is immediately saved in the database.
|
569
|
-
|
570
|
-
You can change this behaviour by overriding `persist_workflow_state`
|
571
|
-
method.
|
572
|
-
|
573
|
-
### Scopes
|
574
|
-
|
575
|
-
Workflow library also adds automatically generated scopes with names based on
|
576
|
-
states names:
|
577
|
-
|
578
|
-
```ruby
|
579
|
-
class Order < ActiveRecord::Base
|
580
|
-
include Workflow
|
581
|
-
workflow do
|
582
|
-
state :approved
|
583
|
-
state :pending
|
584
|
-
end
|
585
|
-
end
|
586
|
-
|
587
|
-
# returns all orders with `approved` state
|
588
|
-
Order.with_approved_state
|
589
|
-
|
590
|
-
# returns all orders with `pending` state
|
591
|
-
Order.with_pending_state
|
592
|
-
```
|
593
|
-
|
594
|
-
### Wrap State Transition in a locking transaction
|
595
|
-
|
596
|
-
Wrap your transition in a locking transaction to ensure that any exceptions
|
597
|
-
raised later in the transition sequence will roll back earlier changes made to
|
598
|
-
the record:
|
599
|
-
|
600
|
-
```ruby
|
601
|
-
class Order < ActiveRecord::Base
|
602
|
-
include Workflow
|
603
|
-
|
604
|
-
wrap_transition_in_transaction!
|
605
|
-
# which is the same as the following:
|
606
|
-
|
607
|
-
around_transition do |model, transition|
|
608
|
-
model.with_lock do
|
609
|
-
transition.call
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
workflow do
|
614
|
-
state :approved
|
615
|
-
state :pending
|
616
|
-
end
|
617
|
-
end
|
618
|
-
```
|
619
|
-
|
620
|
-
Accessing your workflow specification
|
621
|
-
-------------------------------------
|
622
|
-
|
623
|
-
You can easily reflect on workflow specification programmatically - for
|
624
|
-
the whole class or for the current object. Examples:
|
625
|
-
|
626
|
-
```ruby
|
627
|
-
article2.current_state.events # lists possible events from here
|
628
|
-
|
629
|
-
Article.workflow_spec.states.map &:name
|
630
|
-
#=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
|
631
|
-
|
632
|
-
# list all events for all states
|
633
|
-
Article.workflow_spec.states.map(&:events).flatten
|
634
|
-
```
|
635
|
-
|
636
|
-
You can also store and later retrieve additional meta data for every
|
637
|
-
state and every event:
|
638
|
-
|
639
|
-
```ruby
|
640
|
-
class MyProcess
|
641
|
-
include Workflow
|
642
|
-
workflow do
|
643
|
-
state :main, meta: {importance: 8} do
|
644
|
-
on :change, to: :supplemental, meta: {whatever: true}
|
645
|
-
end
|
646
|
-
state :supplemental, meta: {importance: 1}
|
647
|
-
end
|
648
|
-
end
|
649
|
-
puts MyProcess.workflow_spec.find_state(:supplemental).meta[:importance] # => 1
|
650
|
-
```
|
651
|
-
|
652
|
-
Earlier versions
|
653
|
-
----------------
|
654
|
-
|
655
|
-
The `workflow` gem is the work of Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>.
|
656
|
-
|
657
|
-
This project is a fork of his work, and the bulk of the workflow specification code
|
658
|
-
and DSL are virtually unchanged.
|
659
|
-
|
660
|
-
|
661
|
-
About
|
662
|
-
-----
|
663
|
-
Author: Tyler Gannon [https://github.com/tylergannon]
|
664
|
-
|
665
|
-
Original Author: Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>
|
9
|
+
### Credits
|
666
10
|
|
667
11
|
Copyright (c) 2010-2014 Vladimir Dobriakov, www.mobile-web-consulting.de
|
668
|
-
|
669
12
|
Copyright (c) 2008-2009 Vodafone
|
670
|
-
|
671
13
|
Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
|
672
|
-
|
673
14
|
Based on the work of Ryan Allen and Scott Barron
|
674
|
-
|
675
15
|
Licensed under MIT license, see the MIT-LICENSE file.
|