rails-workflow 1.4.4.4 → 1.4.5.4
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.
- 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.
|