workflow 1.1.0 → 1.2.0
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/.travis.yml +6 -0
- data/README.markdown +121 -10
- data/gemfiles/Gemfile.rails-2.3.x +1 -1
- data/gemfiles/Gemfile.rails-3.x +1 -1
- data/gemfiles/Gemfile.rails-4.0 +13 -0
- data/gemfiles/Gemfile.rails-edge +2 -1
- data/lib/workflow.rb +15 -15
- data/lib/workflow/adapters/active_record.rb +6 -0
- data/lib/workflow/draw.rb +2 -2
- data/lib/workflow/errors.rb +5 -5
- data/lib/workflow/event.rb +16 -4
- data/lib/workflow/event_collection.rb +36 -0
- data/lib/workflow/specification.rb +6 -3
- data/lib/workflow/state.rb +1 -1
- data/lib/workflow/version.rb +1 -1
- data/test/adapter_hook_test.rb +52 -0
- data/test/advanced_examples_test.rb +5 -3
- data/test/advanced_hooks_and_validation_test.rb +1 -1
- data/test/main_test.rb +40 -0
- data/workflow.gemspec +1 -1
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b3ca21163c2fe680f13487bd176530fa13e09b0
|
4
|
+
data.tar.gz: 3413ad93c300e668a778db3480a7ab11ea3910f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78329832051f3798e4fc5742eb1ea0f8d10e4f342bcd42e5cf6cb680a7355747e1e12dbcc9a13bc17537843e59e238cc98edd72e67aff106d9b087b2f53901b8
|
7
|
+
data.tar.gz: 992fe9887c6825b047fbdb28dcc16f10994705fad01aeaedb27266e92469ab4e28f91784d8cb8cb7901b82ce1843b51c03f28abca4b9f8b2550a95b8c88e6c14
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.markdown
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
[](https://travis-ci.org/geekq/workflow)
|
1
|
+
[](https://travis-ci.org/geekq/workflow) Tested with [different Ruby and Rails versions](https://travis-ci.org/geekq/workflow)
|
2
2
|
|
3
|
+
Note: you can find documentation for specific workflow rubygem versions at
|
4
|
+
http://rubygems.org/gems/workflow: select a version (optional, default
|
5
|
+
is latest release), click "Documentation" link.
|
3
6
|
|
4
7
|
What is workflow?
|
5
8
|
-----------------
|
@@ -79,7 +82,7 @@ after another state (by the order they were defined):
|
|
79
82
|
=> true
|
80
83
|
article.current_state >= :accepted
|
81
84
|
=> false
|
82
|
-
article.between? :awaiting_review, :rejected
|
85
|
+
article.current_state.between? :awaiting_review, :rejected
|
83
86
|
=> true
|
84
87
|
|
85
88
|
Now we can call the submit event, which transitions to the
|
@@ -147,9 +150,10 @@ be:
|
|
147
150
|
end
|
148
151
|
end
|
149
152
|
|
150
|
-
`article.review!; article.reject!` will cause
|
151
|
-
|
152
|
-
method
|
153
|
+
`article.review!; article.reject!` will cause state transition to
|
154
|
+
`being_reviewed` state, persist the new state (if integrated with
|
155
|
+
ActiveRecord), invoke this user defined `reject` method and finally
|
156
|
+
persist the `rejected` state.
|
153
157
|
|
154
158
|
Note: on successful transition from one state to another the workflow
|
155
159
|
gem immediately persists the new workflow state with `update_column()`,
|
@@ -300,11 +304,87 @@ couchrest library.
|
|
300
304
|
Please also have a look at
|
301
305
|
[the full source code](http://github.com/geekq/workflow/blob/master/test/couchtiny_example.rb).
|
302
306
|
|
303
|
-
Integration with Mongoid
|
304
|
-
------------------------
|
305
307
|
|
306
|
-
|
307
|
-
|
308
|
+
Adapters to support other databases
|
309
|
+
-----------------------------------
|
310
|
+
|
311
|
+
I get a lot of requests to integrate persistence support for different
|
312
|
+
databases, object-relational adapters, column stores, document
|
313
|
+
databases.
|
314
|
+
|
315
|
+
To enable highest possible quality, avoid too many dependencies and to
|
316
|
+
avoid unneeded maintenance burden on the `workflow` core it is best to
|
317
|
+
implement such support as a separate gem.
|
318
|
+
|
319
|
+
Only support for the ActiveRecord will remain for the foreseeable
|
320
|
+
future. So Rails beginners can expect `workflow` to work with Rails out
|
321
|
+
of the box. Other already included adapters stay for a while but should
|
322
|
+
be extracted to separate gems.
|
323
|
+
|
324
|
+
If you want to implement support for your favorite ORM mapper or your
|
325
|
+
favorite NoSQL database, you just need to implement a module which
|
326
|
+
overrides the persistence methods `load_workflow_state` and
|
327
|
+
`persist_workflow_state`. Example:
|
328
|
+
|
329
|
+
module Workflow
|
330
|
+
module SuperCoolDb
|
331
|
+
module InstanceMethods
|
332
|
+
def load_workflow_state
|
333
|
+
# Load and return the workflow_state from some storage.
|
334
|
+
# You can use self.class.workflow_column configuration.
|
335
|
+
end
|
336
|
+
|
337
|
+
def persist_workflow_state(new_value)
|
338
|
+
# save the new_value workflow state
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
module ClassMethods
|
343
|
+
# class methods of your adapter go here
|
344
|
+
end
|
345
|
+
|
346
|
+
def self.included(klass)
|
347
|
+
klass.send :include, InstanceMethods
|
348
|
+
klass.extend ClassMethods
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
The user of the adapter can use it then as:
|
354
|
+
|
355
|
+
class Article
|
356
|
+
include Workflow
|
357
|
+
include Workflow:SuperCoolDb
|
358
|
+
workflow do
|
359
|
+
state :submitted
|
360
|
+
# ...
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
I can then link to your implementation from this README. Please let me
|
365
|
+
also know, if you need any interface beyond `load_workflow_state` and
|
366
|
+
`persist_workflow_state` methods to implement an adapter for your
|
367
|
+
favorite database.
|
368
|
+
|
369
|
+
|
370
|
+
Custom Versions of Existing Adapters
|
371
|
+
------------------------------------
|
372
|
+
|
373
|
+
Other adapters (such as a custom ActiveRecord plugin) can be selected by adding a `workflow_adapter` class method, eg.
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
class Example < ActiveRecord::Base
|
377
|
+
def self.workflow_adapter
|
378
|
+
MyCustomAdapter
|
379
|
+
end
|
380
|
+
include Workflow
|
381
|
+
|
382
|
+
# ...
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
(The above will include `MyCustomAdapter` *instead* of `Workflow::Adapter::ActiveRecord`.)
|
387
|
+
|
308
388
|
|
309
389
|
Accessing your workflow specification
|
310
390
|
-------------------------------------
|
@@ -341,6 +421,25 @@ The workflow library itself uses this feature to tweak the graphical
|
|
341
421
|
representation of the workflow. See below.
|
342
422
|
|
343
423
|
|
424
|
+
Conditional event transitions
|
425
|
+
-----------------------------
|
426
|
+
|
427
|
+
Conditions are procs or lambdas added to events, like so:
|
428
|
+
|
429
|
+
state :off
|
430
|
+
event :turn_on, :transition_to => :on,
|
431
|
+
:if => proc { |device| device.battery_level > 0 }
|
432
|
+
event :turn_on, :transition_to => :low_battery,
|
433
|
+
:if => proc { |device| device.battery_level > 10 }
|
434
|
+
end
|
435
|
+
|
436
|
+
When calling a `device.can_<fire_event>?` check, or attempting a `device.<event>!`, each event is checked in turn:
|
437
|
+
|
438
|
+
* With no :if check, proceed as usual.
|
439
|
+
* If an :if check is present, proceed if it evaluates to true, or drop to the next event.
|
440
|
+
* If you've run out of events to check (eg. battery_level == 0), then the transition isn't possible.
|
441
|
+
|
442
|
+
|
344
443
|
Advanced transition hooks
|
345
444
|
-------------------------
|
346
445
|
|
@@ -541,6 +640,16 @@ when using both a block and a callback method for an event, the block executes p
|
|
541
640
|
Changelog
|
542
641
|
---------
|
543
642
|
|
643
|
+
### New in the version 1.2.0
|
644
|
+
|
645
|
+
* Fix issue #98 protected on\_\* callbacks in Ruby 2
|
646
|
+
* #106 Inherit exceptions from StandardError instead of Exception
|
647
|
+
* #109 Conditional event transitions, contributed by [damncabbage](http://robhoward.id.au/)
|
648
|
+
* New policy for supporting other databases - extract to separate
|
649
|
+
gems. See the README section above.
|
650
|
+
* #111 Custom Versions of Existing Adapters by [damncabbage](http://robhoward.id.au/)
|
651
|
+
|
652
|
+
|
544
653
|
### New in the version 1.1.0
|
545
654
|
|
546
655
|
* Tested with ActiveRecord 4.0 (Rails 4.0)
|
@@ -651,7 +760,9 @@ Support
|
|
651
760
|
About
|
652
761
|
-----
|
653
762
|
|
654
|
-
Author: Vladimir Dobriakov, <http://www.
|
763
|
+
Author: Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>
|
764
|
+
|
765
|
+
Copyright (c) 2010-2014 Vladimir Dobriakov, www.mobile-web-consulting.de
|
655
766
|
|
656
767
|
Copyright (c) 2008-2009 Vodafone
|
657
768
|
|
data/gemfiles/Gemfile.rails-3.x
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
group :development do
|
4
|
+
gem "minitest", "< 5.0.0" # 5.0.0 introduced incompatible changes renaming all the classes
|
5
|
+
gem "rdoc", ">= 3.12"
|
6
|
+
gem "bundler", ">= 1.0.0"
|
7
|
+
gem "activerecord", "~>4.0"
|
8
|
+
gem 'protected_attributes'
|
9
|
+
gem "sqlite3"
|
10
|
+
gem "mocha"
|
11
|
+
gem "rake"
|
12
|
+
gem "ruby-graphviz", "~> 1.0.0"
|
13
|
+
end
|
data/gemfiles/Gemfile.rails-edge
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
3
|
group :development do
|
4
|
+
gem "minitest", "< 5.0.0" # 5.0.0 introduced incompatible changes renaming all the classes
|
4
5
|
gem "rdoc", ">= 3.12"
|
5
6
|
gem "bundler", ">= 1.0.0"
|
6
7
|
gem "activerecord"
|
7
8
|
gem "sqlite3"
|
8
9
|
gem "mocha"
|
9
10
|
gem "rake"
|
10
|
-
gem "ruby-graphviz", "
|
11
|
+
gem "ruby-graphviz", "~> 1.0.0"
|
11
12
|
gem 'protected_attributes'
|
12
13
|
end
|
data/lib/workflow.rb
CHANGED
@@ -38,7 +38,7 @@ module Workflow
|
|
38
38
|
undef_method "#{state_name}?"
|
39
39
|
end
|
40
40
|
|
41
|
-
state.events.
|
41
|
+
state.events.flat.each do |event|
|
42
42
|
event_name = event.name
|
43
43
|
module_eval do
|
44
44
|
undef_method "#{event_name}!".to_sym
|
@@ -57,7 +57,7 @@ module Workflow
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
state.events.
|
60
|
+
state.events.flat.each do |event|
|
61
61
|
event_name = event.name
|
62
62
|
module_eval do
|
63
63
|
define_method "#{event_name}!".to_sym do |*args|
|
@@ -65,7 +65,7 @@ module Workflow
|
|
65
65
|
end
|
66
66
|
|
67
67
|
define_method "can_#{event_name}?" do
|
68
|
-
return
|
68
|
+
return !!current_state.events.first_applicable(event_name, self)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -94,7 +94,7 @@ module Workflow
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def process_event!(name, *args)
|
97
|
-
event = current_state.events
|
97
|
+
event = current_state.events.first_applicable(name, self)
|
98
98
|
raise NoTransitionAllowed.new(
|
99
99
|
"There is no event #{name.to_sym} defined for the #{current_state} state") \
|
100
100
|
if event.nil?
|
@@ -111,7 +111,7 @@ module Workflow
|
|
111
111
|
|
112
112
|
begin
|
113
113
|
return_value = run_action(event.action, *args) || run_action_callback(event.name, *args)
|
114
|
-
rescue
|
114
|
+
rescue StandardError => e
|
115
115
|
run_on_error(e, from, to, name, *args)
|
116
116
|
end
|
117
117
|
|
@@ -214,7 +214,7 @@ module Workflow
|
|
214
214
|
instance_exec(prior_state.name, triggering_event, *args, &state.on_entry)
|
215
215
|
else
|
216
216
|
hook_name = "on_#{state}_entry"
|
217
|
-
self.send hook_name, prior_state, triggering_event, *args if
|
217
|
+
self.send hook_name, prior_state, triggering_event, *args if has_callback?(hook_name)
|
218
218
|
end
|
219
219
|
end
|
220
220
|
|
@@ -224,7 +224,7 @@ module Workflow
|
|
224
224
|
instance_exec(new_state.name, triggering_event, *args, &state.on_exit)
|
225
225
|
else
|
226
226
|
hook_name = "on_#{state}_exit"
|
227
|
-
self.send hook_name, new_state, triggering_event, *args if
|
227
|
+
self.send hook_name, new_state, triggering_event, *args if has_callback?(hook_name)
|
228
228
|
end
|
229
229
|
end
|
230
230
|
end
|
@@ -262,15 +262,15 @@ module Workflow
|
|
262
262
|
|
263
263
|
klass.extend ClassMethods
|
264
264
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
265
|
+
# Look for a hook; otherwise detect based on ancestor class.
|
266
|
+
if klass.respond_to?(:workflow_adapter)
|
267
|
+
klass.send :include, klass.workflow_adapter
|
268
|
+
else
|
269
|
+
if Object.const_defined?(:ActiveRecord) && klass < ActiveRecord::Base
|
270
|
+
klass.send :include, Adapter::ActiveRecord
|
270
271
|
end
|
271
|
-
|
272
|
-
|
273
|
-
klass.send :include, Remodel::InstanceMethods
|
272
|
+
if Object.const_defined?(:Remodel) && klass < Adapter::Remodel::Entity
|
273
|
+
klass.send :include, Adapter::Remodel::InstanceMethods
|
274
274
|
end
|
275
275
|
end
|
276
276
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module Workflow
|
2
2
|
module Adapter
|
3
3
|
module ActiveRecord
|
4
|
+
def self.included(klass)
|
5
|
+
klass.send :include, Adapter::ActiveRecord::InstanceMethods
|
6
|
+
klass.send :extend, Adapter::ActiveRecord::Scopes
|
7
|
+
klass.before_validation :write_initial_state
|
8
|
+
end
|
9
|
+
|
4
10
|
module InstanceMethods
|
5
11
|
def load_workflow_state
|
6
12
|
read_attribute(self.class.workflow_column)
|
data/lib/workflow/draw.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
begin
|
2
2
|
require 'rubygems'
|
3
3
|
|
4
|
-
gem 'ruby-graphviz', '
|
4
|
+
gem 'ruby-graphviz', '~> 1.0.0'
|
5
5
|
gem 'activesupport'
|
6
6
|
|
7
7
|
require 'graphviz'
|
@@ -57,7 +57,7 @@ module Workflow
|
|
57
57
|
node = state.draw(graph)
|
58
58
|
node.fontname = options[:font]
|
59
59
|
|
60
|
-
state.events.each do |
|
60
|
+
state.events.flat.each do |event|
|
61
61
|
edge = event.draw(graph, state)
|
62
62
|
edge.fontname = options[:font]
|
63
63
|
end
|
data/lib/workflow/errors.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Workflow
|
2
|
-
class TransitionHalted <
|
2
|
+
class TransitionHalted < StandardError
|
3
3
|
|
4
4
|
attr_reader :halted_because
|
5
5
|
|
@@ -10,9 +10,9 @@ module Workflow
|
|
10
10
|
|
11
11
|
end
|
12
12
|
|
13
|
-
class NoTransitionAllowed <
|
13
|
+
class NoTransitionAllowed < StandardError; end
|
14
14
|
|
15
|
-
class WorkflowError <
|
15
|
+
class WorkflowError < StandardError; end
|
16
16
|
|
17
|
-
class WorkflowDefinitionError <
|
18
|
-
end
|
17
|
+
class WorkflowDefinitionError < StandardError; end
|
18
|
+
end
|
data/lib/workflow/event.rb
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
module Workflow
|
2
2
|
class Event
|
3
3
|
|
4
|
-
attr_accessor :name, :transitions_to, :meta, :action
|
4
|
+
attr_accessor :name, :transitions_to, :meta, :action, :condition
|
5
5
|
|
6
|
-
def initialize(name, transitions_to, meta = {}, &action)
|
7
|
-
@name
|
6
|
+
def initialize(name, transitions_to, condition = nil, meta = {}, &action)
|
7
|
+
@name = name
|
8
|
+
@transitions_to = transitions_to.to_sym
|
9
|
+
@meta = meta
|
10
|
+
@action = action
|
11
|
+
@condition = if condition.nil? || condition.respond_to?(:call)
|
12
|
+
condition
|
13
|
+
else
|
14
|
+
raise TypeError, 'condition must be nil or callable (eg. a proc or lambda)'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def condition_applicable?(object)
|
19
|
+
condition ? condition.call(object) : true
|
8
20
|
end
|
9
21
|
|
10
22
|
def draw(graph, from_state)
|
@@ -15,4 +27,4 @@ module Workflow
|
|
15
27
|
@name.to_s
|
16
28
|
end
|
17
29
|
end
|
18
|
-
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Workflow
|
2
|
+
class EventCollection < Hash
|
3
|
+
|
4
|
+
def [](name)
|
5
|
+
super name.to_sym # Normalize to symbol
|
6
|
+
end
|
7
|
+
|
8
|
+
def push(name, event)
|
9
|
+
key = name.to_sym
|
10
|
+
self[key] ||= []
|
11
|
+
self[key] << event
|
12
|
+
end
|
13
|
+
|
14
|
+
def flat
|
15
|
+
self.values.flatten.uniq do |event|
|
16
|
+
[:name, :transitions_to, :meta, :action].map { |m| event.send(m) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(name_or_obj)
|
21
|
+
case name_or_obj
|
22
|
+
when Event
|
23
|
+
flat.include? name_or_obj
|
24
|
+
else
|
25
|
+
!(self[name_or_obj].nil?)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def first_applicable(name, object_context)
|
30
|
+
(self[name] || []).detect do |event|
|
31
|
+
event.condition_applicable?(object_context) && event
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'workflow/state'
|
2
2
|
require 'workflow/event'
|
3
|
+
require 'workflow/event_collection'
|
3
4
|
require 'workflow/errors'
|
4
5
|
|
5
6
|
module Workflow
|
@@ -30,11 +31,13 @@ module Workflow
|
|
30
31
|
|
31
32
|
def event(name, args = {}, &action)
|
32
33
|
target = args[:transitions_to] || args[:transition_to]
|
34
|
+
condition = args[:if]
|
33
35
|
raise WorkflowDefinitionError.new(
|
34
36
|
"missing ':transitions_to' in workflow event definition for '#{name}'") \
|
35
37
|
if target.nil?
|
36
|
-
@scoped_state.events
|
37
|
-
Workflow::Event.new(name, target, (args[:meta] or {}), &action)
|
38
|
+
@scoped_state.events.push(
|
39
|
+
name, Workflow::Event.new(name, target, condition, (args[:meta] or {}), &action)
|
40
|
+
)
|
38
41
|
end
|
39
42
|
|
40
43
|
def on_entry(&proc)
|
@@ -61,4 +64,4 @@ module Workflow
|
|
61
64
|
@on_error_proc = proc
|
62
65
|
end
|
63
66
|
end
|
64
|
-
end
|
67
|
+
end
|
data/lib/workflow/state.rb
CHANGED
data/lib/workflow/version.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
require 'workflow'
|
3
|
+
class AdapterHookTest < ActiveRecordTestCase
|
4
|
+
test 'hook to choose adapter' do
|
5
|
+
|
6
|
+
ActiveRecord::Schema.define do
|
7
|
+
create_table(:examples) { |t| t.string :workflow_state }
|
8
|
+
end
|
9
|
+
|
10
|
+
class DefaultAdapter < ActiveRecord::Base
|
11
|
+
self.table_name = :examples
|
12
|
+
include Workflow
|
13
|
+
workflow do
|
14
|
+
state(:initial) { event :progress, :transitions_to => :last }
|
15
|
+
state(:last)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ChosenByHookAdapter < ActiveRecord::Base
|
20
|
+
self.table_name = :examples
|
21
|
+
attr_reader :foo
|
22
|
+
def self.workflow_adapter
|
23
|
+
Module.new do
|
24
|
+
def load_workflow_state
|
25
|
+
@foo if defined?(@foo)
|
26
|
+
end
|
27
|
+
def persist_workflow_state(new_value)
|
28
|
+
@foo = new_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
include Workflow
|
34
|
+
workflow do
|
35
|
+
state(:initial) { event :progress, :transitions_to => :last }
|
36
|
+
state(:last)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
default = DefaultAdapter.create
|
41
|
+
assert default.initial?
|
42
|
+
default.progress!
|
43
|
+
assert default.last?
|
44
|
+
assert DefaultAdapter.find(default.id).last?, 'should have persisted via ActiveRecord'
|
45
|
+
|
46
|
+
hook = ChosenByHookAdapter.create
|
47
|
+
assert hook.initial?
|
48
|
+
hook.progress!
|
49
|
+
assert_equal hook.foo, 'last', 'should have "persisted" with custom adapter'
|
50
|
+
assert ChosenByHookAdapter.find(hook.id).initial?, 'should not have persisted via ActiveRecord'
|
51
|
+
end
|
52
|
+
end
|
@@ -28,13 +28,15 @@ class AdvanceExamplesTest < ActiveRecordTestCase
|
|
28
28
|
spec.state_names.each do |state_name|
|
29
29
|
state = spec.states[state_name]
|
30
30
|
|
31
|
-
(state.events.
|
31
|
+
(state.events.flat.reject {|e| e.name.to_s =~ /^revert_/ }).each do |event|
|
32
32
|
event_name = event.name
|
33
33
|
revert_event_name = "revert_" + event_name.to_s
|
34
34
|
|
35
35
|
# Add revert events
|
36
|
-
spec.states[event.transitions_to.to_sym].events
|
37
|
-
|
36
|
+
spec.states[event.transitions_to.to_sym].events.push(
|
37
|
+
revert_event_name,
|
38
|
+
Workflow::Event.new(revert_event_name, state)
|
39
|
+
)
|
38
40
|
|
39
41
|
# Add methods for revert events
|
40
42
|
Article.module_eval do
|
@@ -43,7 +43,7 @@ class Article < ActiveRecord::Base
|
|
43
43
|
singleton = class << self; self end
|
44
44
|
validations = Proc.new {}
|
45
45
|
|
46
|
-
meta = Article.workflow_spec.states[from].events[triggering_event].meta
|
46
|
+
meta = Article.workflow_spec.states[from].events[triggering_event].first.meta
|
47
47
|
fields_to_validate = meta[:validates_presence_of]
|
48
48
|
if fields_to_validate
|
49
49
|
validations = Proc.new {
|
data/test/main_test.rb
CHANGED
@@ -282,6 +282,7 @@ class MainTest < ActiveRecordTestCase
|
|
282
282
|
args = mock()
|
283
283
|
args.expects(:log).with('in private callback').once
|
284
284
|
args.expects(:log).with('in protected callback in the base class').once
|
285
|
+
args.expects(:log).with('in protected callback `on_assigned_entry`').once
|
285
286
|
|
286
287
|
b = Class.new # the base class with a protected callback
|
287
288
|
b.class_eval do
|
@@ -289,6 +290,7 @@ class MainTest < ActiveRecordTestCase
|
|
289
290
|
def assign_old(args)
|
290
291
|
args.log('in protected callback in the base class')
|
291
292
|
end
|
293
|
+
|
292
294
|
end
|
293
295
|
|
294
296
|
c = Class.new(b) # inheriting class with an additional protected callback
|
@@ -303,6 +305,11 @@ class MainTest < ActiveRecordTestCase
|
|
303
305
|
state :assigned_old
|
304
306
|
end
|
305
307
|
|
308
|
+
protected
|
309
|
+
def on_assigned_entry(prev_state, event, args)
|
310
|
+
args.log('in protected callback `on_assigned_entry`')
|
311
|
+
end
|
312
|
+
|
306
313
|
private
|
307
314
|
def assign(args)
|
308
315
|
args.log('in private callback')
|
@@ -509,6 +516,7 @@ class MainTest < ActiveRecordTestCase
|
|
509
516
|
event :go_to_college, :transitions_to => :student
|
510
517
|
end
|
511
518
|
state :student
|
519
|
+
state :street
|
512
520
|
end
|
513
521
|
end
|
514
522
|
|
@@ -517,6 +525,38 @@ class MainTest < ActiveRecordTestCase
|
|
517
525
|
assert_equal false, human.can_go_to_college?
|
518
526
|
end
|
519
527
|
|
528
|
+
test 'can_<fire_event>? with conditions' do
|
529
|
+
c = Class.new do
|
530
|
+
include Workflow
|
531
|
+
workflow do
|
532
|
+
state :off do
|
533
|
+
event :turn_on, :transitions_to => :on, :if => proc { |obj| obj.battery > 10 }
|
534
|
+
event :turn_on, :transitions_to => :low_battery, :if => proc { |obj| obj.battery > 0 }
|
535
|
+
end
|
536
|
+
state :on
|
537
|
+
state :low_battery
|
538
|
+
end
|
539
|
+
attr_reader :battery
|
540
|
+
def initialize(battery)
|
541
|
+
@battery = battery
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
device = c.new 0
|
546
|
+
assert_equal false, device.can_turn_on?
|
547
|
+
|
548
|
+
device = c.new 5
|
549
|
+
assert device.can_turn_on?
|
550
|
+
device.turn_on!
|
551
|
+
assert device.low_battery?
|
552
|
+
assert_equal false, device.on?
|
553
|
+
|
554
|
+
device = c.new 50
|
555
|
+
assert device.can_turn_on?
|
556
|
+
device.turn_on!
|
557
|
+
assert device.on?
|
558
|
+
end
|
559
|
+
|
520
560
|
test 'workflow graph generation' do
|
521
561
|
Dir.chdir('/tmp') do
|
522
562
|
capture_streams do
|
data/workflow.gemspec
CHANGED
@@ -27,6 +27,6 @@ Gem::Specification.new do |gem|
|
|
27
27
|
gem.add_development_dependency 'sqlite3'
|
28
28
|
gem.add_development_dependency 'mocha'
|
29
29
|
gem.add_development_dependency 'rake'
|
30
|
-
gem.add_development_dependency 'ruby-graphviz', ['
|
30
|
+
gem.add_development_dependency 'ruby-graphviz', ['~> 1.0.0']
|
31
31
|
end
|
32
32
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: workflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dobriakov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|
@@ -98,16 +98,16 @@ dependencies:
|
|
98
98
|
name: ruby-graphviz
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ~>
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 1.0.0
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ~>
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 1.0.0
|
111
111
|
description: |2
|
112
112
|
Workflow is a finite-state-machine-inspired API for modeling and interacting
|
113
113
|
with what we tend to refer to as 'workflow'.
|
@@ -132,6 +132,7 @@ files:
|
|
132
132
|
- Rakefile
|
133
133
|
- gemfiles/Gemfile.rails-2.3.x
|
134
134
|
- gemfiles/Gemfile.rails-3.x
|
135
|
+
- gemfiles/Gemfile.rails-4.0
|
135
136
|
- gemfiles/Gemfile.rails-edge
|
136
137
|
- lib/workflow.rb
|
137
138
|
- lib/workflow/adapters/active_record.rb
|
@@ -139,11 +140,13 @@ files:
|
|
139
140
|
- lib/workflow/draw.rb
|
140
141
|
- lib/workflow/errors.rb
|
141
142
|
- lib/workflow/event.rb
|
143
|
+
- lib/workflow/event_collection.rb
|
142
144
|
- lib/workflow/specification.rb
|
143
145
|
- lib/workflow/state.rb
|
144
146
|
- lib/workflow/version.rb
|
145
147
|
- orders_workflow.png
|
146
148
|
- test/active_record_scopes_test.rb
|
149
|
+
- test/adapter_hook_test.rb
|
147
150
|
- test/advanced_examples_test.rb
|
148
151
|
- test/advanced_hooks_and_validation_test.rb
|
149
152
|
- test/attr_protected_test.rb
|
@@ -178,12 +181,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
181
|
version: '0'
|
179
182
|
requirements: []
|
180
183
|
rubyforge_project:
|
181
|
-
rubygems_version: 2.0.
|
184
|
+
rubygems_version: 2.0.14
|
182
185
|
signing_key:
|
183
186
|
specification_version: 4
|
184
187
|
summary: A replacement for acts_as_state_machine.
|
185
188
|
test_files:
|
186
189
|
- test/active_record_scopes_test.rb
|
190
|
+
- test/adapter_hook_test.rb
|
187
191
|
- test/advanced_examples_test.rb
|
188
192
|
- test/advanced_hooks_and_validation_test.rb
|
189
193
|
- test/attr_protected_test.rb
|