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 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