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 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.