workflow 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/geekq/workflow.png?branch=master)](https://travis-ci.org/geekq/workflow)
|
1
|
+
[![Build Status](https://travis-ci.org/geekq/workflow.png?branch=master)](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
|