workflow 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|