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 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.5.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 < Test::Unit::TestCase
47
-
48
- def exec(sql)
49
- ActiveRecord::Base.connection.execute sql
50
- end
46
+ class MainTest < ActiveRecordTestCase
51
47
 
52
48
  def setup
53
- ActiveRecord::Base.establish_connection(
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
- - 5
7
+ - 6
8
8
  - 0
9
- version: 0.5.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-01 00:00:00 +02:00
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