rails-workflow 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 411f127c2c5c4be97813b1f68fe9e64224463dac
4
+ data.tar.gz: 2622936f556615b749b913f553b546b2782ae724
5
+ SHA512:
6
+ metadata.gz: 5e95a1eecf1dbc84a320e14027a9b3a4de4bb20ef8e8ca68ad41957ee91e65ebb8992957aab4814dd3d87c32522d19b93ffa6733979d2073337e778fc2e88457
7
+ data.tar.gz: b26bfe36a5d64de2b6d72735439fe334137fa0b73fb75e90a32b8305cd14b473a1d0378ac7ed9c3127e6e513e9182f18442d7d9ccda6a3ff2d6ac36a04758bbd
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ nbproject
2
+ html/
3
+ *.swp
4
+ *.gem
5
+ *.rbc
6
+ .bundle
7
+ .config
8
+ .yardoc
9
+ Gemfile*.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
21
+
22
+ .byebug_history
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ before_install:
2
+ - sudo apt-get install -qq graphviz
3
+ before_script:
4
+ - gem list
5
+ - bundle show
6
+
7
+ rvm:
8
+ - 2.3.1
9
+ gemfile:
10
+ - gemfiles/Gemfile.rails-edge
11
+
12
+ matrix:
13
+ include:
14
+ - rvm: 1.9.3
15
+ gemfile: gemfiles/Gemfile.rails-3.x
16
+
17
+ - rvm: 2.0.0
18
+ gemfile: gemfiles/Gemfile.rails-3.x
19
+
20
+ - rvm: 2.0.0
21
+ gemfile: gemfiles/Gemfile.rails-4.0
22
+
23
+ - rvm: 2.3.1
24
+ gemfile: gemfiles/Gemfile.rails-4.0
25
+
26
+ - rvm: 2.3.1
27
+ gemfile: gemfiles/Gemfile.rails-5.0
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --markup=markdown
2
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+ gem 'byebug'
3
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 Vodafone
2
+ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,426 @@
1
+ What is workflow?
2
+ -----------------
3
+
4
+ This Gem is a fork of Vladimir Dobriakov's [Workflow Gem](http://github.com/geekq/workflow). Credit goes to him for the core code. Please read [the original README](http://github.com/geekq/workflow) for a full introduction,
5
+ as this README skims through much of that content and focuses on new / changed features.
6
+
7
+ ## What's different in rails-workflow
8
+
9
+ The primary difference here is the use of [ActiveSupport::Callbacks](http://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html)
10
+ to enable a more flexible application of callbacks.
11
+ You now have access to the same DSL you're used to from [ActionController Callbacks](http://guides.rubyonrails.org/action_controller_overview.html#filters),
12
+ including the ability to wrap state transitions in an `around_transition`, to place
13
+ conditional logic on application of callbacks, or to have callbacks run for only
14
+ a set of state-change events.
15
+
16
+ I've made `ActiveRecord` and `ActiveSupport` into runtime dependencies.
17
+
18
+ You can also take advantage of ActiveRecord's conditional validation syntax,
19
+ to apply validations only to specific state transitions.
20
+
21
+
22
+ Installation
23
+ ------------
24
+
25
+ gem install rails-workflow
26
+
27
+
28
+ Ruby Version
29
+ --------
30
+
31
+ I've only tested with Ruby 2.3. ;) Time to upgrade.
32
+
33
+
34
+ # Basic workflow definition:
35
+
36
+ class Article
37
+ include Workflow
38
+ workflow do
39
+ state :new do
40
+ event :submit, :transitions_to => :awaiting_review
41
+ end
42
+ state :awaiting_review do
43
+ event :review, :transitions_to => :being_reviewed
44
+ end
45
+ state :being_reviewed do
46
+ event :accept, :transitions_to => :accepted
47
+ event :reject, :transitions_to => :rejected
48
+ end
49
+ state :accepted
50
+ state :rejected
51
+ end
52
+ end
53
+
54
+ Access an object representing the current state of the entity,
55
+ including available events and transitions:
56
+
57
+ article.current_state
58
+ => #<Workflow::State:0x7f1e3d6731f0 @events={
59
+ :submit=>#<Workflow::Event:0x7f1e3d6730d8 @action=nil,
60
+ @transitions_to=:awaiting_review, @name=:submit, @meta={}>},
61
+ name:new, meta{}
62
+
63
+ On Ruby 1.9 and above, you can check whether a state comes before or
64
+ after another state (by the order they were defined):
65
+
66
+ article.current_state
67
+ => being_reviewed
68
+ article.current_state < :accepted
69
+ => true
70
+ article.current_state >= :accepted
71
+ => false
72
+ article.current_state.between? :awaiting_review, :rejected
73
+ => true
74
+
75
+ Now we can call the submit event, which transitions to the
76
+ <tt>:awaiting_review</tt> state:
77
+
78
+ article.submit!
79
+ article.awaiting_review? # => true
80
+
81
+
82
+ Callbacks
83
+ -------------------------
84
+
85
+ The DSL syntax here is very much similar to ActionController or ActiveRecord callbacks.
86
+
87
+ Callbacks with this strategy used the same as [ActionController Callbacks](http://guides.rubyonrails.org/action_controller_overview.html#filters).
88
+
89
+ You can configure any number of `before`, `around`, or `after` transition callbacks.
90
+
91
+ `before_transition` and `around_transition` are called in the order they are set,
92
+ and `after_transition` callbacks are called in reverse order.
93
+
94
+ ## Around Transition
95
+
96
+ Allows you to run code surrounding the state transition.
97
+
98
+ around_transition :wrap_in_transaction
99
+
100
+ def wrap_in_transaction(&block)
101
+ Article.transaction(&block)
102
+ end
103
+
104
+ You can also define the callback using a block:
105
+
106
+ around_transition do |object, transition|
107
+ object.with_lock do
108
+ transition.call
109
+ end
110
+ end
111
+
112
+ ### Replacement for workflow's `on_error` proc:
113
+
114
+ around_transition :catch_errors
115
+
116
+ def catch_errors
117
+ begin
118
+ yield
119
+ rescue SomeApplicationError => ex
120
+ logger.error 'Oh noes!'
121
+ end
122
+ end
123
+
124
+
125
+ ## before_transition
126
+
127
+ Allows you to run code prior to the state transition.
128
+ If you `halt` or `throw :abort` within a `before_transition`, the callback chain
129
+ will be halted, the transition will be canceled and the event action
130
+ will return false.
131
+
132
+ before_transition :check_title
133
+
134
+ def check_title
135
+ halt('Title was bad.') unless title == "Good Title"
136
+ end
137
+
138
+ Or again, in block expression:
139
+
140
+ before_transition do |article|
141
+ throw :abort unless article.title == "Good Title"
142
+ end
143
+
144
+ ## After Transition
145
+
146
+ Runs code after the transition.
147
+
148
+ after_transition :check_title
149
+
150
+
151
+ ## Prepend Transitions
152
+
153
+ To add a callback to the beginning of the sequence:
154
+
155
+ prepend_before_transition :some_before_transition
156
+ prepend_around_transition :some_around_transition
157
+ prepend_after_transition :some_after_transition
158
+
159
+ ## Skip Transitions
160
+
161
+ skip_before_transition :some_before_transition
162
+
163
+
164
+ ## Conditions
165
+
166
+ ### if/unless
167
+
168
+ The callback will run `if` or `unless` the named method returns a truthy value.
169
+
170
+ before_transition :do_something, if: :valid?
171
+
172
+ ### only/except
173
+
174
+ The callback will run `if` or `unless` the event being processed is in the list given
175
+
176
+ # Run this callback only on the `accept` and `publish` events.
177
+ before_transition :do_something, only: [:accept, :publish]
178
+
179
+ # Run this callback on events other than the `accept` and `publish` events.
180
+ before_transition :do_something_else, except: [:accept, :publish]
181
+
182
+ ## Conditional Validations
183
+
184
+ If you are using `ActiveRecord`, you'll have access to a set of methods which
185
+ describe the current transition underway.
186
+
187
+ Inside the same Article class which was begun above, the following three
188
+ validations would all run when the `submit` event is used to transition
189
+ from `new` to `awaiting_review`.
190
+
191
+ validates :title, presence: true, if: :transitioning_to_awaiting_review?
192
+ validates :body, presence: true, if: :transitioning_from_new?
193
+ validates :author, presence: true, if: :transitioning_via_event_submit?
194
+
195
+ ### Halting if validations fail
196
+
197
+ # This will create a transition callback which will stop the event
198
+ # and return false if validations fail.
199
+ halt_transition_unless_valid!
200
+
201
+ # This is the same as
202
+
203
+ ### Checking A Transition
204
+
205
+ Call `can_transition?` to determine whether the validations would pass if a
206
+ given event was called:
207
+
208
+ if article.can_transition?(:submit)
209
+ # Do something interesting
210
+ end
211
+
212
+ # Transition Context
213
+
214
+ During transition you can refer to the `transition_context` object on your model,
215
+ for information about the current transition. See [Workflow::TransitionContext].
216
+
217
+ ## Naming Event Arguments
218
+
219
+ If you will normally call each of your events with the same arguments, the following
220
+ will help:
221
+
222
+ class Article < ApplicationRecord
223
+ include Workflow
224
+
225
+ before_transition :check_reviewer
226
+
227
+ def check_reviewer
228
+ # Ability is a class from the cancan gem: https://github.com/CanCanCommunity/cancancan
229
+ halt('Access denied') unless Ability.new(transition_context.reviewer).can?(:review, self)
230
+ end
231
+
232
+ workflow do
233
+ event_args :reviewer, :reviewed_at
234
+ state :new do
235
+ event :review, transitions_to: :reviewed
236
+ end
237
+ state :reviewed
238
+ end
239
+ end
240
+
241
+
242
+ Transition event handler
243
+ ------------------------
244
+
245
+ The best way is to use convention over configuration and to define a
246
+ method with the same name as the event. Then it is automatically invoked
247
+ when event is raised. For the Article workflow defined earlier it would
248
+ be:
249
+
250
+ class Article
251
+ def reject
252
+ puts 'sending email to the author explaining the reason...'
253
+ end
254
+ end
255
+
256
+ `article.review!; article.reject!` will cause state transition to
257
+ `being_reviewed` state, persist the new state (if integrated with
258
+ ActiveRecord), invoke this user defined `reject` method and finally
259
+ persist the `rejected` state.
260
+
261
+ Note: on successful transition from one state to another the workflow
262
+ gem immediately persists the new workflow state with `update_column()`,
263
+ bypassing any ActiveRecord callbacks including `updated_at` update.
264
+ This way it is possible to deal with the validation and to save the
265
+ pending changes to a record at some later point instead of the moment
266
+ when transition occurs.
267
+
268
+ You can also define event handler accepting/requiring additional
269
+ arguments:
270
+
271
+ class Article
272
+ def review(reviewer = '')
273
+ puts "[#{reviewer}] is now reviewing the article"
274
+ end
275
+ end
276
+
277
+ article2 = Article.new
278
+ article2.submit!
279
+ article2.review!('Homer Simpson') # => [Homer Simpson] is now reviewing the article
280
+
281
+
282
+ Integration with ActiveRecord
283
+ -----------------------------
284
+
285
+ Workflow library can handle the state persistence fully automatically. You
286
+ only need to define a string field on the table called `workflow_state`
287
+ and include the workflow mixin in your model class as usual:
288
+
289
+ class Order < ActiveRecord::Base
290
+ include Workflow
291
+ workflow do
292
+ # list states and transitions here
293
+ end
294
+ end
295
+
296
+ On a database record loading all the state check methods e.g.
297
+ `article.state`, `article.awaiting_review?` are immediately available.
298
+ For new records or if the `workflow_state` field is not set the state
299
+ defaults to the first state declared in the workflow specification. In
300
+ our example it is `:new`, so `Article.new.new?` returns true and
301
+ `Article.new.approved?` returns false.
302
+
303
+ At the end of a successful state transition like `article.approve!` the
304
+ new state is immediately saved in the database.
305
+
306
+ You can change this behaviour by overriding `persist_workflow_state`
307
+ method.
308
+
309
+ ### Scopes
310
+
311
+ Workflow library also adds automatically generated scopes with names based on
312
+ states names:
313
+
314
+ class Order < ActiveRecord::Base
315
+ include Workflow
316
+ workflow do
317
+ state :approved
318
+ state :pending
319
+ end
320
+ end
321
+
322
+ # returns all orders with `approved` state
323
+ Order.with_approved_state
324
+
325
+ # returns all orders with `pending` state
326
+ Order.with_pending_state
327
+
328
+ ### Wrap State Transition in a locking transaction
329
+
330
+ Wrap your transition in a locking transaction to ensure that any exceptions
331
+ raised later in the transition sequence will roll back earlier changes made to
332
+ the record:
333
+
334
+ class Order < ActiveRecord::Base
335
+ include Workflow
336
+ workflow transactional: true do
337
+ state :approved
338
+ state :pending
339
+ end
340
+ end
341
+
342
+ Conditional event transitions
343
+ -----------------------------
344
+
345
+ Conditions can be a "method name symbol" with a corresponding instance method, a `proc` or `lambda` which are added to events, like so:
346
+
347
+ state :off
348
+ event :turn_on, :transition_to => :on,
349
+ :if => :sufficient_battery_level?
350
+
351
+ event :turn_on, :transition_to => :low_battery,
352
+ :if => proc { |device| device.battery_level > 0 }
353
+ end
354
+
355
+ # corresponding instance method
356
+ def sufficient_battery_level?
357
+ battery_level > 10
358
+ end
359
+
360
+ When calling a `device.can_<fire_event>?` check, or attempting a `device.<event>!`, each event is checked in turn:
361
+
362
+ * With no `:if` check, proceed as usual.
363
+ * If an `:if` check is present, proceed if it evaluates to true, or drop to the next event.
364
+ * If you've run out of events to check (eg. `battery_level == 0`), then the transition isn't possible.
365
+
366
+
367
+
368
+ Accessing your workflow specification
369
+ -------------------------------------
370
+
371
+ You can easily reflect on workflow specification programmatically - for
372
+ the whole class or for the current object. Examples:
373
+
374
+ article2.current_state.events # lists possible events from here
375
+ article2.current_state.events[:reject].transitions_to # => :rejected
376
+
377
+ Article.workflow_spec.states.keys
378
+ #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
379
+
380
+ Article.workflow_spec.state_names
381
+ #=> [:rejected, :awaiting_review, :being_reviewed, :accepted, :new]
382
+
383
+ # list all events for all states
384
+ Article.workflow_spec.states.values.collect &:events
385
+
386
+
387
+ You can also store and later retrieve additional meta data for every
388
+ state and every event:
389
+
390
+ class MyProcess
391
+ include Workflow
392
+ workflow do
393
+ state :main, :meta => {:importance => 8}
394
+ state :supplemental, :meta => {:importance => 1}
395
+ end
396
+ end
397
+ puts MyProcess.workflow_spec.states[:supplemental].meta[:importance] # => 1
398
+
399
+ The workflow library itself uses this feature to tweak the graphical
400
+ representation of the workflow. See below.
401
+
402
+
403
+ Earlier versions
404
+ ----------------
405
+
406
+ The `workflow` gem is the work of Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>.
407
+
408
+ This project is a fork of his work, and the bulk of the workflow specification code
409
+ and DSL are virtually unchanged.
410
+
411
+
412
+ About
413
+ -----
414
+ Author: Tyler Gannon [https://github.com/tylergannon]
415
+
416
+ Original Author: Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>
417
+
418
+ Copyright (c) 2010-2014 Vladimir Dobriakov, www.mobile-web-consulting.de
419
+
420
+ Copyright (c) 2008-2009 Vodafone
421
+
422
+ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
423
+
424
+ Based on the work of Ryan Allen and Scott Barron
425
+
426
+ Licensed under MIT license, see the MIT-LICENSE file.