workflow 0.5.0 → 0.6.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.
- data/README.markdown +63 -0
- data/VERSION +1 -1
- data/lib/workflow.rb +5 -0
- data/test/main_test.rb +2 -14
- data/test/multiple_workflows_test.rb +61 -0
- data/test/test_helper.rb +26 -3
- metadata +5 -3
data/README.markdown
CHANGED
@@ -331,6 +331,63 @@ The whole event sequence is as follows:
|
|
331
331
|
* on_entry
|
332
332
|
|
333
333
|
|
334
|
+
Multiple Workflows
|
335
|
+
------------------
|
336
|
+
|
337
|
+
I am frequently asked if it's possible to represent multiple "workflows"
|
338
|
+
in an ActiveRecord class.
|
339
|
+
|
340
|
+
The solution depends on your business logic and how you want to
|
341
|
+
structure your implementation.
|
342
|
+
|
343
|
+
### Use Single Table Inheritance
|
344
|
+
|
345
|
+
One solution can be to do it on the class level and use a class
|
346
|
+
hierarchy. You can use [single table inheritance][STI] so there is only
|
347
|
+
single `orders` table in the database. Read more in the chapter "Single
|
348
|
+
Table Inheritance" of the [ActiveRecord documentation][ActiveRecord].
|
349
|
+
Then you define your different classes:
|
350
|
+
|
351
|
+
class Order < ActiveRecord::Base
|
352
|
+
include Workflow
|
353
|
+
end
|
354
|
+
|
355
|
+
class SmallOrder < Order
|
356
|
+
workflow do
|
357
|
+
# workflow definition for small orders goes here
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class BigOrder < Order
|
362
|
+
workflow do
|
363
|
+
# workflow for big orders, probably with a longer approval chain
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
### Individual workflows for objects
|
369
|
+
|
370
|
+
Another solution would be to connect different workflows to object
|
371
|
+
instances via metaclass, e.g.
|
372
|
+
|
373
|
+
booking = Booking.find(1234)
|
374
|
+
if # some condition
|
375
|
+
class << booking
|
376
|
+
include Workflow
|
377
|
+
workflow do
|
378
|
+
state :state1
|
379
|
+
state :state2
|
380
|
+
end
|
381
|
+
end
|
382
|
+
# if some other condition, use a different workflow
|
383
|
+
|
384
|
+
Please also have a look at [the full working example][multiple_workflow_test]!
|
385
|
+
|
386
|
+
[STI]: http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
387
|
+
[ActiveRecord]: http://api.rubyonrails.org/classes/ActiveRecord/Base.html
|
388
|
+
[multiple_workflow_test]: http://github.com/geekq/workflow/blob/master/test/multiple_workflows_test.rb
|
389
|
+
|
390
|
+
|
334
391
|
Documenting with diagrams
|
335
392
|
-------------------------
|
336
393
|
|
@@ -375,6 +432,12 @@ when using both a block and a callback method for an event, the block executes p
|
|
375
432
|
Changelog
|
376
433
|
---------
|
377
434
|
|
435
|
+
### New in the version 0.6.0
|
436
|
+
|
437
|
+
* enable multiple workflows by connecting workflow to object instances
|
438
|
+
(using metaclass) instead of connecting to a class, s. "Multiple
|
439
|
+
Workflows" section
|
440
|
+
|
378
441
|
### New in the version 0.5.0
|
379
442
|
|
380
443
|
* change the behaviour of halt! to immediately raise an exception. See
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/workflow.rb
CHANGED
@@ -177,6 +177,11 @@ module Workflow
|
|
177
177
|
end
|
178
178
|
|
179
179
|
def spec
|
180
|
+
# check the singleton class first
|
181
|
+
class << self
|
182
|
+
return workflow_spec if workflow_spec
|
183
|
+
end
|
184
|
+
|
180
185
|
c = self.class
|
181
186
|
# using a simple loop instead of class_inheritable_accessor to avoid
|
182
187
|
# dependency on Rails' ActiveSupport
|
data/test/main_test.rb
CHANGED
@@ -43,18 +43,10 @@ class LegacyOrder < ActiveRecord::Base
|
|
43
43
|
end
|
44
44
|
|
45
45
|
|
46
|
-
class MainTest <
|
47
|
-
|
48
|
-
def exec(sql)
|
49
|
-
ActiveRecord::Base.connection.execute sql
|
50
|
-
end
|
46
|
+
class MainTest < ActiveRecordTestCase
|
51
47
|
|
52
48
|
def setup
|
53
|
-
|
54
|
-
:adapter => "sqlite3",
|
55
|
-
:database => ":memory:" #"tmp/test"
|
56
|
-
)
|
57
|
-
ActiveRecord::Base.connection.reconnect! # eliminate ActiveRecord warning. TODO: delete as soon as ActiveRecord is fixed
|
49
|
+
super
|
58
50
|
|
59
51
|
ActiveRecord::Schema.define do
|
60
52
|
create_table :orders do |t|
|
@@ -76,10 +68,6 @@ class MainTest < Test::Unit::TestCase
|
|
76
68
|
|
77
69
|
end
|
78
70
|
|
79
|
-
def teardown
|
80
|
-
ActiveRecord::Base.connection.disconnect!
|
81
|
-
end
|
82
|
-
|
83
71
|
def assert_state(title, expected_state, klass = Order)
|
84
72
|
o = klass.find_by_title(title)
|
85
73
|
assert_equal expected_state, o.read_attribute(klass.workflow_column)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
class MultipleWorkflowsTest < ActiveRecordTestCase
|
3
|
+
|
4
|
+
test 'multiple workflows' do
|
5
|
+
|
6
|
+
ActiveRecord::Schema.define do
|
7
|
+
create_table :bookings do |t|
|
8
|
+
t.string :title, :null => false
|
9
|
+
t.string :workflow_state
|
10
|
+
t.string :workflow_type
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
exec "INSERT INTO bookings(title, workflow_state, workflow_type) VALUES('booking1', 'initial', 'workflow_1')"
|
15
|
+
exec "INSERT INTO bookings(title, workflow_state, workflow_type) VALUES('booking2', 'initial', 'workflow_2')"
|
16
|
+
|
17
|
+
class Booking < ActiveRecord::Base
|
18
|
+
def initialize_workflow
|
19
|
+
# define workflow per object instead of per class
|
20
|
+
case workflow_type
|
21
|
+
when 'workflow_1'
|
22
|
+
class << self
|
23
|
+
include Workflow
|
24
|
+
workflow do
|
25
|
+
state :initial do
|
26
|
+
event :progress, :transitions_to => :last
|
27
|
+
end
|
28
|
+
state :last
|
29
|
+
end
|
30
|
+
end
|
31
|
+
when 'workflow_2'
|
32
|
+
class << self
|
33
|
+
include Workflow
|
34
|
+
workflow do
|
35
|
+
state :initial do
|
36
|
+
event :progress, :transitions_to => :intermediate
|
37
|
+
end
|
38
|
+
state :intermediate
|
39
|
+
state :last
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
booking1 = Booking.find_by_title('booking1')
|
47
|
+
booking1.initialize_workflow
|
48
|
+
|
49
|
+
booking2 = Booking.find_by_title('booking2')
|
50
|
+
booking2.initialize_workflow
|
51
|
+
|
52
|
+
assert booking1.initial?
|
53
|
+
booking1.progress!
|
54
|
+
assert booking1.last?, 'booking1 should transition to the "last" state'
|
55
|
+
|
56
|
+
assert booking2.initial?
|
57
|
+
booking2.progress!
|
58
|
+
assert booking2.intermediate?, 'booking2 should transition to the "intermediate" state'
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
|
+
require 'active_record'
|
3
4
|
|
4
5
|
class << Test::Unit::TestCase
|
5
6
|
def test(name, &block)
|
@@ -7,10 +8,32 @@ class << Test::Unit::TestCase
|
|
7
8
|
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name.to_s
|
8
9
|
if block
|
9
10
|
define_method test_name, &block
|
10
|
-
else
|
11
|
+
else
|
11
12
|
puts "PENDING: #{name}"
|
12
|
-
end
|
13
|
-
end
|
13
|
+
end
|
14
|
+
end
|
14
15
|
end
|
15
16
|
|
17
|
+
class ActiveRecordTestCase < Test::Unit::TestCase
|
18
|
+
def exec(sql)
|
19
|
+
ActiveRecord::Base.connection.execute sql
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
ActiveRecord::Base.establish_connection(
|
24
|
+
:adapter => "sqlite3",
|
25
|
+
:database => ":memory:" #"tmp/test"
|
26
|
+
)
|
27
|
+
|
28
|
+
# eliminate ActiveRecord warning. TODO: delete as soon as ActiveRecord is fixed
|
29
|
+
ActiveRecord::Base.connection.reconnect!
|
30
|
+
end
|
31
|
+
|
32
|
+
def teardown
|
33
|
+
ActiveRecord::Base.connection.disconnect!
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_test
|
37
|
+
end
|
38
|
+
end
|
16
39
|
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 6
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.6.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Vladimir Dobriakov
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-07-
|
17
|
+
date: 2010-07-08 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -35,6 +35,7 @@ files:
|
|
35
35
|
- lib/workflow.rb
|
36
36
|
- test/couchtiny_example.rb
|
37
37
|
- test/main_test.rb
|
38
|
+
- test/multiple_workflows_test.rb
|
38
39
|
- test/readme_example.rb
|
39
40
|
- test/test_helper.rb
|
40
41
|
- test/without_active_record_test.rb
|
@@ -74,4 +75,5 @@ test_files:
|
|
74
75
|
- test/main_test.rb
|
75
76
|
- test/test_helper.rb
|
76
77
|
- test/without_active_record_test.rb
|
78
|
+
- test/multiple_workflows_test.rb
|
77
79
|
- test/readme_example.rb
|