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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fef57b053c2e5b632d435a9e6c98dc8da4b28117
4
- data.tar.gz: 90121871ed2d3df2a3417276683a36a44687d87a
3
+ metadata.gz: c00b8e437381e8b05f1f9b870667c24c06b9ec64
4
+ data.tar.gz: 429a9bbdff4aab6ff053d2f395d2be1bbafbba2c
5
5
  SHA512:
6
- metadata.gz: b7c57936b5570cc3c0eea2973a07cdc1419333d3e02ca370c2a69fe49aafe151b33d0d53bc43304fdc32ac7f46c0bf541687c525955f2652f6ad735a873cfc69
7
- data.tar.gz: 159494b51e3e3ce814b34390f25fbc4e367993b893beb2bb517e161e3c7b50f2d38c3c3b7ccaa7dfa9d4c752317839d62fc7c825f7f0334019f57497efafce05
6
+ metadata.gz: 732795994aa948a71b19bbe1fe1c5da5ea6814de9a146615d0eb5ae1fac2cd67c174619b9d1e6c64a943b94c32e6ccbab151c668bbcb8d56c532f02d4e7d46f1
7
+ data.tar.gz: a44fb7792d8f027fb953d9b789a12bbad7d837002f3a14b9acfda52375e3f274391fb352f3c29ad6f43477123b7f715726a91a38a2f794c7ce50aec8a698eef5
data/.gitignore CHANGED
@@ -10,7 +10,7 @@ Gemfile*.lock
10
10
  InstalledFiles
11
11
  _yardoc
12
12
  coverage
13
- doc/
13
+ docs
14
14
  lib/bundler/man
15
15
  pkg
16
16
  rdoc
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
- What is workflow?
2
- -----------------
1
+ # Workflow For Rails Apps
3
2
 
4
- This Gem is a fork of Vladimir Dobriakov's [Workflow Gem](http://github.com/geekq/workflow). Credit goes to him for the inspiration, architecture and basic syntax.
3
+ https://tylergannon.github.io/rails-workflow/
5
4
 
6
- ## What's different in rails-workflow
7
5
 
8
- * Use of [ActiveSupport::Callbacks](http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html)
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
- ## Installation
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.