workflow_on_mongoid 0.8.0.3 → 0.8.0.4

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/.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
+