workflow_on_mongoid 0.8.0.3 → 0.8.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ .DS_STORE
2
+ coverage/*
3
+ pkg/*
4
+ scratch_directory/*
5
+ tmp/*
6
+ *.gem
7
+ RAILS
8
+ .bundle
9
+ Gemfile.lock
10
+ .rvmrc
11
+ rdoc/*
12
+ doc/*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/README.rdoc CHANGED
@@ -19,7 +19,7 @@ That is all. Now you can use {Workflow}[http://github.com/geekq/workflow] with y
19
19
 
20
20
  The version numbers of workflow_on_mongoid track the latest supported version of {Workflow}[http://github.com/geekq/workflow] . The major version number (x.x.x) matches the latest supported version of the Workflow gem, while the minor version number (0.7.0.x) tracks changes to workflow_on_mongoid itself.
21
21
 
22
- The current version is 0.8.0.2 .
22
+ The current version is 0.8.0.3 .
23
23
 
24
24
  NOTE: workflow_on_mongoid 0.8.0 uses Mongoid 2.0.0.beta.20 . 0.8.0.1+ uses the latest Mongoid 2.0.0.rc . Please choose the appropriate version of workflow_on_mongoid based on the version of Mongoid you are using (the changes from Mongoid 2.0.0.beta to 2.0.0.rc are significant!)
25
25
 
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'bundler'
2
+ Bundler.setup(:default, :development)
3
+
4
+ require "rake"
5
+ require "rake/rdoctask"
6
+ require 'rake/testtask'
7
+
8
+ # require "rspec"
9
+ # require "rspec/core/rake_task"
10
+
11
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
12
+ require "workflow_on_mongoid/version"
13
+
14
+ desc 'Default: run all tests.'
15
+ task :default => :test
16
+
17
+ desc "Test the workflow_on_mongoid plugin."
18
+ Rake::TestTask.new(:test) do |t|
19
+ t.libs << 'lib'
20
+ t.test_files = Dir["test/*_test.rb"]
21
+ t.verbose = true
22
+ end
@@ -2,5 +2,5 @@
2
2
  module WorkflowOnMongoid #:nodoc
3
3
  # major version number matches the latest supported version of the Workflow gem
4
4
  # minor version number for tracking changes to workflow_on_mongoid itself
5
- VERSION = "0.8.0.3"
5
+ VERSION = "0.8.0.4"
6
6
  end
@@ -0,0 +1,49 @@
1
+ # require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ require 'test_helper'
4
+
5
+ require 'couchtiny'
6
+ require 'couchtiny/document'
7
+ require 'workflow'
8
+
9
+ class User < CouchTiny::Document
10
+ include Workflow
11
+ workflow do
12
+ state :submitted do
13
+ event :activate_via_link, :transitions_to => :proved_email
14
+ end
15
+ state :proved_email
16
+ end
17
+
18
+ def load_workflow_state
19
+ self[:workflow_state]
20
+ end
21
+
22
+ def persist_workflow_state(new_value)
23
+ self[:workflow_state] = new_value
24
+ save!
25
+ end
26
+ end
27
+
28
+
29
+ class CouchtinyExample < Test::Unit::TestCase
30
+
31
+ def setup
32
+ db = CouchTiny::Database.url("http://127.0.0.1:5984/test-workflow")
33
+ db.delete_database! rescue nil
34
+ db.create_database!
35
+ User.use_database db
36
+ end
37
+
38
+ test 'CouchDB persistence' do
39
+ user = User.new :email => 'manya@example.com'
40
+ user.save!
41
+ assert user.submitted?
42
+ user.activate_via_link!
43
+ assert user.proved_email?
44
+
45
+ reloaded_user = User.get user.id
46
+ puts reloaded_user.inspect
47
+ assert reloaded_user.proved_email?, 'Reloaded user should have the desired workflow state'
48
+ end
49
+ end
data/test/main_test.rb ADDED
@@ -0,0 +1,486 @@
1
+ $LOAD_PATH.unshift File.expand_path( File.dirname(__FILE__) )
2
+
3
+ require 'test_helper'
4
+
5
+ $VERBOSE = false
6
+ require 'active_record/base'
7
+ require 'sqlite3'
8
+ require 'workflow'
9
+ require 'mocha'
10
+ require 'stringio'
11
+ require 'ruby-debug'
12
+
13
+ ActiveRecord::Migration.verbose = false
14
+
15
+ class Order < ActiveRecord::Base
16
+ include Workflow
17
+ workflow do
18
+ state :submitted do
19
+ event :accept, :transitions_to => :accepted, :meta => {:doc_weight => 8} do |reviewer, args|
20
+ end
21
+ end
22
+ state :accepted do
23
+ event :ship, :transitions_to => :shipped
24
+ end
25
+ state :shipped
26
+ end
27
+ end
28
+
29
+ class LegacyOrder < ActiveRecord::Base
30
+ include Workflow
31
+
32
+ workflow_column :foo_bar # use this legacy database column for persistence
33
+
34
+ workflow do
35
+ state :submitted do
36
+ event :accept, :transitions_to => :accepted, :meta => {:doc_weight => 8} do |reviewer, args|
37
+ end
38
+ end
39
+ state :accepted do
40
+ event :ship, :transitions_to => :shipped
41
+ end
42
+ state :shipped
43
+ end
44
+ end
45
+
46
+ class Image < ActiveRecord::Base
47
+ include Workflow
48
+
49
+ workflow_column :status
50
+
51
+ workflow do
52
+ state :unconverted do
53
+ event :convert, :transitions_to => :converted
54
+ end
55
+ state :converted
56
+ end
57
+ end
58
+
59
+ class SmallImage < Image
60
+ end
61
+
62
+ class SpecialSmallImage < SmallImage
63
+ end
64
+
65
+ class MainTest < ActiveRecordTestCase
66
+
67
+ def setup
68
+ super
69
+
70
+ ActiveRecord::Schema.define do
71
+ create_table :orders do |t|
72
+ t.string :title, :null => false
73
+ t.string :workflow_state
74
+ end
75
+ end
76
+
77
+ exec "INSERT INTO orders(title, workflow_state) VALUES('some order', 'accepted')"
78
+
79
+ ActiveRecord::Schema.define do
80
+ create_table :legacy_orders do |t|
81
+ t.string :title, :null => false
82
+ t.string :foo_bar
83
+ end
84
+ end
85
+
86
+ exec "INSERT INTO legacy_orders(title, foo_bar) VALUES('some order', 'accepted')"
87
+
88
+ ActiveRecord::Schema.define do
89
+ create_table :images do |t|
90
+ t.string :title, :null => false
91
+ t.string :state
92
+ t.string :type
93
+ end
94
+ end
95
+ end
96
+
97
+ def assert_state(title, expected_state, klass = Order)
98
+ o = klass.find_by_title(title)
99
+ assert_equal expected_state, o.read_attribute(klass.workflow_column)
100
+ o
101
+ end
102
+
103
+ test 'immediately save the new workflow_state on state machine transition' do
104
+ o = assert_state 'some order', 'accepted'
105
+ assert o.ship!
106
+ assert_state 'some order', 'shipped'
107
+ end
108
+
109
+ test 'immediately save the new workflow_state on state machine transition with custom column name' do
110
+ o = assert_state 'some order', 'accepted', LegacyOrder
111
+ assert o.ship!
112
+ assert_state 'some order', 'shipped', LegacyOrder
113
+ end
114
+
115
+ test 'persist workflow_state in the db and reload' do
116
+ o = assert_state 'some order', 'accepted'
117
+ assert_equal :accepted, o.current_state.name
118
+ o.ship!
119
+ o.save!
120
+
121
+ assert_state 'some order', 'shipped'
122
+
123
+ o.reload
124
+ assert_equal 'shipped', o.read_attribute(:workflow_state)
125
+ end
126
+
127
+ test 'persist workflow_state in the db with_custom_name and reload' do
128
+ o = assert_state 'some order', 'accepted', LegacyOrder
129
+ assert_equal :accepted, o.current_state.name
130
+ o.ship!
131
+ o.save!
132
+
133
+ assert_state 'some order', 'shipped', LegacyOrder
134
+
135
+ o.reload
136
+ assert_equal 'shipped', o.read_attribute(:foo_bar)
137
+ end
138
+
139
+ test 'default workflow column should be workflow_state' do
140
+ o = assert_state 'some order', 'accepted'
141
+ assert_equal :workflow_state, o.class.workflow_column
142
+ end
143
+
144
+ test 'custom workflow column should be foo_bar' do
145
+ o = assert_state 'some order', 'accepted', LegacyOrder
146
+ assert_equal :foo_bar, o.class.workflow_column
147
+ end
148
+
149
+ test 'access workflow specification' do
150
+ assert_equal 3, Order.workflow_spec.states.length
151
+ assert_equal ['submitted', 'accepted', 'shipped'].sort,
152
+ Order.workflow_spec.state_names.map{|n| n.to_s}.sort
153
+ end
154
+
155
+ test 'current state object' do
156
+ o = assert_state 'some order', 'accepted'
157
+ assert_equal 'accepted', o.current_state.to_s
158
+ assert_equal 1, o.current_state.events.length
159
+ end
160
+
161
+ test 'on_entry and on_exit invoked' do
162
+ c = Class.new
163
+ callbacks = mock()
164
+ callbacks.expects(:my_on_exit_new).once
165
+ callbacks.expects(:my_on_entry_old).once
166
+ c.class_eval do
167
+ include Workflow
168
+ workflow do
169
+ state :new do
170
+ event :age, :transitions_to => :old
171
+ end
172
+ on_exit do
173
+ callbacks.my_on_exit_new
174
+ end
175
+ state :old
176
+ on_entry do
177
+ callbacks.my_on_entry_old
178
+ end
179
+ on_exit do
180
+ fail "wrong on_exit executed"
181
+ end
182
+ end
183
+ end
184
+
185
+ o = c.new
186
+ assert_equal 'new', o.current_state.to_s
187
+ o.age!
188
+ end
189
+
190
+ test 'on_transition invoked' do
191
+ callbacks = mock()
192
+ callbacks.expects(:on_tran).once # this is validated at the end
193
+ c = Class.new
194
+ c.class_eval do
195
+ include Workflow
196
+ workflow do
197
+ state :one do
198
+ event :increment, :transitions_to => :two
199
+ end
200
+ state :two
201
+ on_transition do |from, to, triggering_event, *event_args|
202
+ callbacks.on_tran
203
+ end
204
+ end
205
+ end
206
+ assert_not_nil c.workflow_spec.on_transition_proc
207
+ c.new.increment!
208
+ end
209
+
210
+ test 'access event meta information' do
211
+ c = Class.new
212
+ c.class_eval do
213
+ include Workflow
214
+ workflow do
215
+ state :main, :meta => {:importance => 8}
216
+ state :supplemental, :meta => {:importance => 1}
217
+ end
218
+ end
219
+ assert_equal 1, c.workflow_spec.states[:supplemental].meta[:importance]
220
+ end
221
+
222
+ test 'initial state' do
223
+ c = Class.new
224
+ c.class_eval do
225
+ include Workflow
226
+ workflow { state :one; state :two }
227
+ end
228
+ assert_equal 'one', c.new.current_state.to_s
229
+ end
230
+
231
+ test 'nil as initial state' do
232
+ exec "INSERT INTO orders(title, workflow_state) VALUES('nil state', NULL)"
233
+ o = Order.find_by_title('nil state')
234
+ assert o.submitted?, 'if workflow_state is nil, the initial state should be assumed'
235
+ assert !o.shipped?
236
+ end
237
+
238
+ test 'initial state immediately set as ActiveRecord attribute for new objects' do
239
+ o = Order.create(:title => 'new object')
240
+ assert_equal 'submitted', o.read_attribute(:workflow_state)
241
+ end
242
+
243
+ test 'question methods for state' do
244
+ o = assert_state 'some order', 'accepted'
245
+ assert o.accepted?
246
+ assert !o.shipped?
247
+ end
248
+
249
+ test 'correct exception for event, that is not allowed in current state' do
250
+ o = assert_state 'some order', 'accepted'
251
+ assert_raise Workflow::NoTransitionAllowed do
252
+ o.accept!
253
+ end
254
+ end
255
+
256
+ test 'multiple events with the same name and different arguments lists from different states'
257
+
258
+ test 'implicit transition callback' do
259
+ args = mock()
260
+ args.expects(:my_tran).once # this is validated at the end
261
+ c = Class.new
262
+ c.class_eval do
263
+ include Workflow
264
+ def my_transition(args)
265
+ args.my_tran
266
+ end
267
+ workflow do
268
+ state :one do
269
+ event :my_transition, :transitions_to => :two
270
+ end
271
+ state :two
272
+ end
273
+ end
274
+ c.new.my_transition!(args)
275
+ end
276
+
277
+ test 'Single table inheritance (STI)' do
278
+ class BigOrder < Order
279
+ end
280
+
281
+ bo = BigOrder.new
282
+ assert bo.submitted?
283
+ assert !bo.accepted?
284
+ end
285
+
286
+ test 'STI when parent changed the workflow_state column' do
287
+ assert_equal 'status', Image.workflow_column.to_s
288
+ assert_equal 'status', SmallImage.workflow_column.to_s
289
+ assert_equal 'status', SpecialSmallImage.workflow_column.to_s
290
+ end
291
+
292
+ test 'Two-level inheritance' do
293
+ class BigOrder < Order
294
+ end
295
+
296
+ class EvenBiggerOrder < BigOrder
297
+ end
298
+
299
+ assert EvenBiggerOrder.new.submitted?
300
+ end
301
+
302
+ test 'Iheritance with workflow definition override' do
303
+ class BigOrder < Order
304
+ end
305
+
306
+ class SpecialBigOrder < BigOrder
307
+ workflow do
308
+ state :start_big
309
+ end
310
+ end
311
+
312
+ special = SpecialBigOrder.new
313
+ assert_equal 'start_big', special.current_state.to_s
314
+ end
315
+
316
+ test 'Better error message for missing target state' do
317
+ class Problem
318
+ include Workflow
319
+ workflow do
320
+ state :initial do
321
+ event :solve, :transitions_to => :solved
322
+ end
323
+ end
324
+ end
325
+ assert_raise Workflow::WorkflowError do
326
+ Problem.new.solve!
327
+ end
328
+ end
329
+
330
+ # Intermixing of transition graph definition (states, transitions)
331
+ # on the one side and implementation of the actions on the other side
332
+ # for a bigger state machine can introduce clutter.
333
+ #
334
+ # To reduce this clutter it is now possible to use state entry- and
335
+ # exit- hooks defined through a naming convention. For example, if there
336
+ # is a state :pending, then you can hook in by defining method
337
+ # `def on_pending_exit(new_state, event, *args)` instead of using a
338
+ # block:
339
+ #
340
+ # state :pending do
341
+ # on_entry do
342
+ # # your implementation here
343
+ # end
344
+ # end
345
+ #
346
+ # If both a function with a name according to naming convention and the
347
+ # on_entry/on_exit block are given, then only on_entry/on_exit block is used.
348
+ test 'on_entry and on_exit hooks in separate methods' do
349
+ c = Class.new
350
+ c.class_eval do
351
+ include Workflow
352
+ attr_reader :history
353
+ def initialize
354
+ @history = []
355
+ end
356
+ workflow do
357
+ state :new do
358
+ event :next, :transitions_to => :next_state
359
+ end
360
+ state :next_state
361
+ end
362
+
363
+ def on_next_state_entry(prior_state, event, *args)
364
+ @history << "on_next_state_entry #{event} #{prior_state} ->"
365
+ end
366
+
367
+ def on_new_exit(new_state, event, *args)
368
+ @history << "on_new_exit #{event} -> #{new_state}"
369
+ end
370
+ end
371
+
372
+ o = c.new
373
+ assert_equal 'new', o.current_state.to_s
374
+ assert_equal [], o.history
375
+ o.next!
376
+ assert_equal ['on_new_exit next -> next_state', 'on_next_state_entry next new ->'], o.history
377
+
378
+ end
379
+
380
+ test 'diagram generation' do
381
+ begin
382
+ $stdout = StringIO.new('', 'w')
383
+ Workflow::create_workflow_diagram(Order, 'doc')
384
+ assert_match(/open.+\.pdf/, $stdout.string,
385
+ 'PDF should be generate and a hint be given to the user.')
386
+ ensure
387
+ $stdout = STDOUT
388
+ end
389
+ end
390
+
391
+ test 'halt stops the transition' do
392
+ c = Class.new do
393
+ include Workflow
394
+ workflow do
395
+ state :young do
396
+ event :age, :transitions_to => :old
397
+ end
398
+ state :old
399
+ end
400
+
401
+ def age(by=1)
402
+ halt 'too fast' if by > 100
403
+ end
404
+ end
405
+
406
+ joe = c.new
407
+ assert joe.young?
408
+ joe.age! 120
409
+ assert joe.young?, 'Transition should have been halted'
410
+ assert_equal 'too fast', joe.halted_because
411
+ end
412
+
413
+ test 'halt! raises exception immediately' do
414
+ article_class = Class.new do
415
+ include Workflow
416
+ attr_accessor :too_far
417
+ workflow do
418
+ state :new do
419
+ event :reject, :transitions_to => :rejected
420
+ end
421
+ state :rejected
422
+ end
423
+
424
+ def reject(reason)
425
+ halt! 'We do not reject articles unless the reason is important' \
426
+ unless reason =~ /important/i
427
+ self.too_far = "This line should not be executed"
428
+ end
429
+ end
430
+
431
+ article = article_class.new
432
+ assert article.new?
433
+ assert_raise Workflow::TransitionHalted do
434
+ article.reject! 'Too funny'
435
+ end
436
+ assert_nil article.too_far
437
+ assert article.new?, 'Transition should have been halted'
438
+ article.reject! 'Important: too short'
439
+ assert article.rejected?, 'Transition should happen now'
440
+ end
441
+
442
+ # published gem doesn't have the `can_?` methods yet!
443
+ # test 'can fire event?' do
444
+ # c = Class.new do
445
+ # include Workflow
446
+ # workflow do
447
+ # state :newborn do
448
+ # event :go_to_school, :transitions_to => :schoolboy
449
+ # end
450
+ # state :schoolboy do
451
+ # event :go_to_college, :transitions_to => :student
452
+ # end
453
+ # state :student
454
+ # end
455
+ # end
456
+ #
457
+ # human = c.new
458
+ # assert human.can_go_to_school?
459
+ # assert_equal false, human.can_go_to_college?
460
+ # end
461
+
462
+ test 'workflow graph generation' do
463
+ Dir.chdir('tmp') do
464
+ capture_streams do
465
+ Workflow::create_workflow_diagram(Order)
466
+ end
467
+ end
468
+ end
469
+
470
+ test 'workflow graph generation in path with spaces' do
471
+ `mkdir -p '/tmp/Workflow test'`
472
+ capture_streams do
473
+ Workflow::create_workflow_diagram(Order, '/tmp/Workflow test')
474
+ end
475
+ end
476
+
477
+ def capture_streams
478
+ old_stdout = $stdout
479
+ $stdout = captured_stdout = StringIO.new
480
+ yield
481
+ $stdout = old_stdout
482
+ captured_stdout
483
+ end
484
+
485
+ end
486
+