workflow-orchestrator 1.3.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +36 -0
  4. data/CHANGELOG.md +133 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +22 -0
  7. data/README.md +707 -0
  8. data/Rakefile +30 -0
  9. data/gemfiles/Gemfile.rails-3.x +12 -0
  10. data/gemfiles/Gemfile.rails-4.0 +14 -0
  11. data/gemfiles/Gemfile.rails-4.1 +14 -0
  12. data/gemfiles/Gemfile.rails-4.2 +14 -0
  13. data/gemfiles/Gemfile.rails-edge +14 -0
  14. data/lib/workflow/adapters/active_record.rb +75 -0
  15. data/lib/workflow/adapters/remodel.rb +15 -0
  16. data/lib/workflow/draw.rb +79 -0
  17. data/lib/workflow/errors.rb +20 -0
  18. data/lib/workflow/event.rb +38 -0
  19. data/lib/workflow/event_collection.rb +36 -0
  20. data/lib/workflow/specification.rb +83 -0
  21. data/lib/workflow/state.rb +44 -0
  22. data/lib/workflow/version.rb +3 -0
  23. data/lib/workflow.rb +307 -0
  24. data/orders_workflow.png +0 -0
  25. data/test/active_record_scopes_test.rb +56 -0
  26. data/test/active_record_scopes_with_values_test.rb +79 -0
  27. data/test/adapter_hook_test.rb +52 -0
  28. data/test/advanced_examples_test.rb +84 -0
  29. data/test/advanced_hooks_and_validation_test.rb +119 -0
  30. data/test/attr_protected_test.rb +107 -0
  31. data/test/before_transition_test.rb +36 -0
  32. data/test/couchtiny_example.rb +46 -0
  33. data/test/enum_values_in_memory_test.rb +23 -0
  34. data/test/enum_values_test.rb +30 -0
  35. data/test/incline_column_test.rb +54 -0
  36. data/test/inheritance_test.rb +56 -0
  37. data/test/main_test.rb +588 -0
  38. data/test/multiple_workflows_test.rb +84 -0
  39. data/test/new_versions/compare_states_test.rb +32 -0
  40. data/test/new_versions/persistence_test.rb +62 -0
  41. data/test/on_error_test.rb +52 -0
  42. data/test/on_unavailable_transition_test.rb +85 -0
  43. data/test/readme_example.rb +37 -0
  44. data/test/test_helper.rb +39 -0
  45. data/test/without_active_record_test.rb +54 -0
  46. data/workflow-orchestrator.gemspec +42 -0
  47. metadata +267 -0
data/test/main_test.rb ADDED
@@ -0,0 +1,588 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ $VERBOSE = false
4
+ require 'active_record'
5
+ require 'sqlite3'
6
+ require 'workflow'
7
+ require 'mocha/setup'
8
+ require 'stringio'
9
+ #require 'ruby-debug'
10
+
11
+ ActiveRecord::Migration.verbose = false
12
+
13
+ class Order < ActiveRecord::Base
14
+ include Workflow
15
+ workflow do
16
+ state :submitted, :meta => {refundable: true} do
17
+ event :accept, :transitions_to => :accepted, :meta => {:weight => 8} do |reviewer, args|
18
+ end
19
+ end
20
+ state :accepted, :meta => {refundable: true} do
21
+ event :ship, :transitions_to => :shipped
22
+ end
23
+ state :shipped, :meta => {refundable: false}
24
+ end
25
+ end
26
+
27
+ class LegacyOrder < ActiveRecord::Base
28
+ include Workflow
29
+
30
+ workflow_column :foo_bar # use this legacy database column for persistence
31
+
32
+ workflow do
33
+ state :submitted do
34
+ event :accept, :transitions_to => :accepted, :meta => {:weight => 8} do |reviewer, args|
35
+ end
36
+ end
37
+ state :accepted do
38
+ event :ship, :transitions_to => :shipped
39
+ end
40
+ state :shipped
41
+ end
42
+ end
43
+
44
+ class Image < ActiveRecord::Base
45
+ include Workflow
46
+
47
+ workflow_column :status
48
+
49
+ workflow do
50
+ state :unconverted do
51
+ event :convert, :transitions_to => :converted
52
+ end
53
+ state :converted
54
+ end
55
+ end
56
+
57
+ class SmallImage < Image
58
+ end
59
+
60
+ class SpecialSmallImage < SmallImage
61
+ end
62
+
63
+ class MainTest < ActiveRecordTestCase
64
+
65
+ def setup
66
+ super
67
+
68
+ ActiveRecord::Schema.define do
69
+ create_table :orders do |t|
70
+ t.string :title, :null => false
71
+ t.string :workflow_state
72
+ end
73
+ end
74
+
75
+ exec "INSERT INTO orders(title, workflow_state) VALUES('some order', 'accepted')"
76
+
77
+ ActiveRecord::Schema.define do
78
+ create_table :legacy_orders do |t|
79
+ t.string :title, :null => false
80
+ t.string :foo_bar
81
+ end
82
+ end
83
+
84
+ exec "INSERT INTO legacy_orders(title, foo_bar) VALUES('some order', 'accepted')"
85
+
86
+ ActiveRecord::Schema.define do
87
+ create_table :images do |t|
88
+ t.string :title, :null => false
89
+ t.string :state
90
+ t.string :type
91
+ end
92
+ end
93
+ end
94
+
95
+ def assert_state(title, expected_state, klass = Order)
96
+ o = klass.find_by_title(title)
97
+ assert_equal expected_state, o.read_attribute(klass.workflow_column)
98
+ o
99
+ end
100
+
101
+ test 'immediately save the new workflow_state on state machine transition' do
102
+ o = assert_state 'some order', 'accepted'
103
+ assert o.ship!
104
+ assert_state 'some order', 'shipped'
105
+ end
106
+
107
+ test 'immediately save the new workflow_state on state machine transition with custom column name' do
108
+ o = assert_state 'some order', 'accepted', LegacyOrder
109
+ assert o.ship!
110
+ assert_state 'some order', 'shipped', LegacyOrder
111
+ end
112
+
113
+ test 'persist workflow_state in the db and reload' do
114
+ o = assert_state 'some order', 'accepted'
115
+ assert_equal :accepted, o.current_state.name
116
+ o.ship!
117
+ o.save!
118
+
119
+ assert_state 'some order', 'shipped'
120
+
121
+ o.reload
122
+ assert_equal 'shipped', o.read_attribute(:workflow_state)
123
+ end
124
+
125
+ test 'persist workflow_state in the db with_custom_name and reload' do
126
+ o = assert_state 'some order', 'accepted', LegacyOrder
127
+ assert_equal :accepted, o.current_state.name
128
+ o.ship!
129
+ o.save!
130
+
131
+ assert_state 'some order', 'shipped', LegacyOrder
132
+
133
+ o.reload
134
+ assert_equal 'shipped', o.read_attribute(:foo_bar)
135
+ end
136
+
137
+ test 'default workflow column should be workflow_state' do
138
+ o = assert_state 'some order', 'accepted'
139
+ assert_equal :workflow_state, o.class.workflow_column
140
+ end
141
+
142
+ test 'custom workflow column should be foo_bar' do
143
+ o = assert_state 'some order', 'accepted', LegacyOrder
144
+ assert_equal :foo_bar, o.class.workflow_column
145
+ end
146
+
147
+ test 'access workflow specification' do
148
+ assert_equal 3, Order.workflow_spec.states.length
149
+ assert_equal ['submitted', 'accepted', 'shipped'].sort,
150
+ Order.workflow_spec.state_names.map{|n| n.to_s}.sort
151
+ end
152
+
153
+ test 'current state object' do
154
+ o = assert_state 'some order', 'accepted'
155
+ assert_equal 'accepted', o.current_state.to_s
156
+ assert_equal 1, o.current_state.events.length
157
+ end
158
+
159
+ test 'on_entry and on_exit invoked' do
160
+ c = Class.new
161
+ callbacks = mock()
162
+ callbacks.expects(:my_on_exit_new).once
163
+ callbacks.expects(:my_on_entry_old).once
164
+ c.class_eval do
165
+ include Workflow
166
+ workflow do
167
+ state :new do
168
+ event :age, :transitions_to => :old
169
+ end
170
+ on_exit do
171
+ callbacks.my_on_exit_new
172
+ end
173
+ state :old
174
+ on_entry do
175
+ callbacks.my_on_entry_old
176
+ end
177
+ on_exit do
178
+ fail "wrong on_exit executed"
179
+ end
180
+ end
181
+ end
182
+
183
+ o = c.new
184
+ assert_equal 'new', o.current_state.to_s
185
+ o.age!
186
+ end
187
+
188
+ test 'on_transition invoked' do
189
+ callbacks = mock()
190
+ callbacks.expects(:on_tran).once # this is validated at the end
191
+ c = Class.new
192
+ c.class_eval do
193
+ include Workflow
194
+ workflow do
195
+ state :one do
196
+ event :increment, :transitions_to => :two
197
+ end
198
+ state :two
199
+ on_transition do |from, to, triggering_event, *event_args|
200
+ callbacks.on_tran
201
+ end
202
+ end
203
+ end
204
+ assert_not_nil c.workflow_spec.on_transition_proc
205
+ c.new.increment!
206
+ end
207
+
208
+ test 'access event meta information' do
209
+ c = Class.new
210
+ c.class_eval do
211
+ include Workflow
212
+ workflow do
213
+ state :main, 1, :meta => {:importance => 8}
214
+ state :supplemental, 2, :meta => {:importance => 1}
215
+ end
216
+ end
217
+ assert_equal 1, c.workflow_spec.states[:supplemental].meta[:importance]
218
+ end
219
+
220
+ test 'initial state' do
221
+ c = Class.new
222
+ c.class_eval do
223
+ include Workflow
224
+ workflow { state :one; state :two }
225
+ end
226
+ assert_equal 'one', c.new.current_state.to_s
227
+ end
228
+
229
+ test 'nil as initial state' do
230
+ exec "INSERT INTO orders(title, workflow_state) VALUES('nil state', NULL)"
231
+ o = Order.find_by_title('nil state')
232
+ assert o.submitted?, 'if workflow_state is nil, the initial state should be assumed'
233
+ assert !o.shipped?
234
+ end
235
+
236
+ test 'initial state immediately set as ActiveRecord attribute for new objects' do
237
+ o = Order.create(:title => 'new object')
238
+ assert_equal 'submitted', o.read_attribute(:workflow_state).to_s
239
+ end
240
+
241
+ test 'question methods for state' do
242
+ o = assert_state 'some order', 'accepted'
243
+ assert o.accepted?
244
+ assert !o.shipped?
245
+ end
246
+
247
+ test 'correct exception for event, that is not allowed in current state' do
248
+ o = assert_state 'some order', 'accepted'
249
+ assert_raise Workflow::NoTransitionAllowed do
250
+ o.accept!
251
+ end
252
+ end
253
+
254
+ test 'multiple events with the same name and different arguments lists from different states'
255
+
256
+ test 'implicit transition callback' do
257
+ args = mock()
258
+ args.expects(:my_tran).once # this is validated at the end
259
+ c = Class.new
260
+ c.class_eval do
261
+ include Workflow
262
+ def my_transition(args)
263
+ args.my_tran
264
+ end
265
+ workflow do
266
+ state :one do
267
+ event :my_transition, :transitions_to => :two
268
+ end
269
+ state :two
270
+ end
271
+
272
+ private
273
+ def another_transition(args)
274
+ args.another_tran
275
+ end
276
+ end
277
+ a = c.new
278
+ a.my_transition!(args)
279
+ end
280
+
281
+ test '#53 Support for non public transition callbacks' do
282
+ args = mock()
283
+ args.expects(:log).with('in private callback').once
284
+ args.expects(:log).with('in protected callback in the base class').once
285
+ args.expects(:log).with('in protected callback `on_assigned_entry`').once
286
+
287
+ b = Class.new # the base class with a protected callback
288
+ b.class_eval do
289
+ protected
290
+ def assign_old(args)
291
+ args.log('in protected callback in the base class')
292
+ end
293
+
294
+ end
295
+
296
+ c = Class.new(b) # inheriting class with an additional protected callback
297
+ c.class_eval do
298
+ include Workflow
299
+ workflow do
300
+ state :new do
301
+ event :assign, :transitions_to => :assigned
302
+ event :assign_old, :transitions_to => :assigned_old
303
+ end
304
+ state :assigned
305
+ state :assigned_old
306
+ end
307
+
308
+ protected
309
+ def on_assigned_entry(prev_state, event, args)
310
+ args.log('in protected callback `on_assigned_entry`')
311
+ end
312
+
313
+ private
314
+ def assign(args)
315
+ args.log('in private callback')
316
+ end
317
+ end
318
+
319
+ a = c.new
320
+ a.assign!(args)
321
+
322
+ a2 = c.new
323
+ a2.assign_old!(args)
324
+ end
325
+
326
+ test '#58 Limited private transition callback lookup' do
327
+ args = mock()
328
+ c = Class.new
329
+ c.class_eval do
330
+ include Workflow
331
+ workflow do
332
+ state :new do
333
+ event :fail, :transitions_to => :failed
334
+ end
335
+ state :failed
336
+ end
337
+ end
338
+ a = c.new
339
+ a.fail!(args)
340
+ end
341
+
342
+ test 'Single table inheritance (STI)' do
343
+ class BigOrder < Order
344
+ end
345
+
346
+ bo = BigOrder.new
347
+ assert bo.submitted?
348
+ assert !bo.accepted?
349
+ end
350
+
351
+ test 'STI when parent changed the workflow_state column' do
352
+ assert_equal 'status', Image.workflow_column.to_s
353
+ assert_equal 'status', SmallImage.workflow_column.to_s
354
+ assert_equal 'status', SpecialSmallImage.workflow_column.to_s
355
+ end
356
+
357
+ test 'Two-level inheritance' do
358
+ class BigOrder < Order
359
+ end
360
+
361
+ class EvenBiggerOrder < BigOrder
362
+ end
363
+
364
+ assert EvenBiggerOrder.new.submitted?
365
+ end
366
+
367
+ test 'Iheritance with workflow definition override' do
368
+ class BigOrder < Order
369
+ end
370
+
371
+ class SpecialBigOrder < BigOrder
372
+ workflow do
373
+ state :start_big
374
+ end
375
+ end
376
+
377
+ special = SpecialBigOrder.new
378
+ assert_equal 'start_big', special.current_state.to_s
379
+ end
380
+
381
+ test 'Better error message for missing target state' do
382
+ class Problem
383
+ include Workflow
384
+ workflow do
385
+ state :initial do
386
+ event :solve, :transitions_to => :solved
387
+ end
388
+ end
389
+ end
390
+ assert_raise Workflow::WorkflowError do
391
+ Problem.new.solve!
392
+ end
393
+ end
394
+
395
+ # Intermixing of transition graph definition (states, transitions)
396
+ # on the one side and implementation of the actions on the other side
397
+ # for a bigger state machine can introduce clutter.
398
+ #
399
+ # To reduce this clutter it is now possible to use state entry- and
400
+ # exit- hooks defined through a naming convention. For example, if there
401
+ # is a state :pending, then you can hook in by defining method
402
+ # `def on_pending_exit(new_state, event, *args)` instead of using a
403
+ # block:
404
+ #
405
+ # state :pending do
406
+ # on_entry do
407
+ # # your implementation here
408
+ # end
409
+ # end
410
+ #
411
+ # If both a function with a name according to naming convention and the
412
+ # on_entry/on_exit block are given, then only on_entry/on_exit block is used.
413
+ test 'on_entry and on_exit hooks in separate methods' do
414
+ c = Class.new
415
+ c.class_eval do
416
+ include Workflow
417
+ attr_reader :history
418
+ def initialize
419
+ @history = []
420
+ end
421
+ workflow do
422
+ state :new do
423
+ event :next, :transitions_to => :next_state
424
+ end
425
+ state :next_state
426
+ end
427
+
428
+ def on_next_state_entry(prior_state, event, *args)
429
+ @history << "on_next_state_entry #{event} #{prior_state} ->"
430
+ end
431
+
432
+ def on_new_exit(new_state, event, *args)
433
+ @history << "on_new_exit #{event} -> #{new_state}"
434
+ end
435
+ end
436
+
437
+ o = c.new
438
+ assert_equal 'new', o.current_state.to_s
439
+ assert_equal [], o.history
440
+ o.next!
441
+ assert_equal ['on_new_exit next -> next_state', 'on_next_state_entry next new ->'], o.history
442
+
443
+ end
444
+
445
+ test 'diagram generation' do
446
+ begin
447
+ $stdout = StringIO.new('', 'w')
448
+ require 'workflow/draw'
449
+ Workflow::Draw::workflow_diagram(Order, :path => '/tmp')
450
+ assert_match(/run the following/, $stdout.string,
451
+ 'PDF should be generate and a hint be given to the user.')
452
+ ensure
453
+ $stdout = STDOUT
454
+ end
455
+ end
456
+
457
+ test 'halt stops the transition' do
458
+ c = Class.new do
459
+ include Workflow
460
+ workflow do
461
+ state :young do
462
+ event :age, :transitions_to => :old
463
+ end
464
+ state :old
465
+ end
466
+
467
+ def age(by=1)
468
+ halt 'too fast' if by > 100
469
+ end
470
+ end
471
+
472
+ joe = c.new
473
+ assert joe.young?
474
+ joe.age! 120
475
+ assert joe.young?, 'Transition should have been halted'
476
+ assert_equal 'too fast', joe.halted_because
477
+ end
478
+
479
+ test 'halt! raises exception immediately' do
480
+ article_class = Class.new do
481
+ include Workflow
482
+ attr_accessor :too_far
483
+ workflow do
484
+ state :new do
485
+ event :reject, :transitions_to => :rejected
486
+ end
487
+ state :rejected
488
+ end
489
+
490
+ def reject(reason)
491
+ halt! 'We do not reject articles unless the reason is important' \
492
+ unless reason =~ /important/i
493
+ self.too_far = "This line should not be executed"
494
+ end
495
+ end
496
+
497
+ article = article_class.new
498
+ assert article.new?
499
+ assert_raise Workflow::TransitionHalted do
500
+ article.reject! 'Too funny'
501
+ end
502
+ assert_nil article.too_far
503
+ assert article.new?, 'Transition should have been halted'
504
+ article.reject! 'Important: too short'
505
+ assert article.rejected?, 'Transition should happen now'
506
+ end
507
+
508
+ test 'can fire event?' do
509
+ c = Class.new do
510
+ include Workflow
511
+ workflow do
512
+ state :newborn do
513
+ event :go_to_school, :transitions_to => :schoolboy
514
+ end
515
+ state :schoolboy do
516
+ event :go_to_college, :transitions_to => :student
517
+ end
518
+ state :student
519
+ state :street
520
+ end
521
+ end
522
+
523
+ human = c.new
524
+ assert human.can_go_to_school?
525
+ assert_equal false, human.can_go_to_college?
526
+ end
527
+
528
+ test 'can_<fire_event>? with conditions' do
529
+ c = Class.new do
530
+ include Workflow
531
+ workflow do
532
+ state :off do
533
+ event :turn_on, :transitions_to => :on, :if => :sufficient_battery_level?
534
+ event :turn_on, :transitions_to => :low_battery, :if => proc { |obj| obj.battery > 0 }
535
+ end
536
+ state :on
537
+ state :low_battery
538
+ end
539
+ attr_reader :battery
540
+ def initialize(battery)
541
+ @battery = battery
542
+ end
543
+
544
+ def sufficient_battery_level?
545
+ @battery > 10
546
+ end
547
+ end
548
+
549
+ device = c.new 0
550
+ assert_equal false, device.can_turn_on?
551
+
552
+ device = c.new 5
553
+ assert device.can_turn_on?
554
+ device.turn_on!
555
+ assert device.low_battery?
556
+ assert_equal false, device.on?
557
+
558
+ device = c.new 50
559
+ assert device.can_turn_on?
560
+ device.turn_on!
561
+ assert device.on?
562
+ end
563
+
564
+ test 'workflow graph generation' do
565
+ Dir.chdir('/tmp') do
566
+ capture_streams do
567
+ Workflow::Draw::workflow_diagram(Order, :path => '/tmp')
568
+ end
569
+ end
570
+ end
571
+
572
+ test 'workflow graph generation in a path with spaces' do
573
+ `mkdir -p '/tmp/Workflow test'`
574
+ capture_streams do
575
+ Workflow::Draw::workflow_diagram(Order, :path => '/tmp/Workflow test')
576
+ end
577
+ end
578
+
579
+ def capture_streams
580
+ old_stdout = $stdout
581
+ $stdout = captured_stdout = StringIO.new
582
+ yield
583
+ $stdout = old_stdout
584
+ captured_stdout
585
+ end
586
+
587
+ end
588
+
@@ -0,0 +1,84 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'workflow'
3
+ class MultipleWorkflowsTest < ActiveRecordTestCase
4
+
5
+ test 'multiple workflows' do
6
+
7
+ ActiveRecord::Schema.define do
8
+ create_table :bookings do |t|
9
+ t.string :title, :null => false
10
+ t.string :workflow_state
11
+ t.string :workflow_type
12
+ end
13
+ end
14
+
15
+ exec "INSERT INTO bookings(title, workflow_state, workflow_type) VALUES('booking1', 'initial', 'workflow_1')"
16
+ exec "INSERT INTO bookings(title, workflow_state, workflow_type) VALUES('booking2', 'initial', 'workflow_2')"
17
+
18
+ class Booking < ActiveRecord::Base
19
+
20
+ include Workflow
21
+
22
+ def initialize_workflow
23
+ # define workflow per object instead of per class
24
+ case workflow_type
25
+ when 'workflow_1'
26
+ class << self
27
+ workflow do
28
+ state :initial do
29
+ event :progress, :transitions_to => :last
30
+ end
31
+ state :last
32
+ end
33
+ end
34
+ when 'workflow_2'
35
+ class << self
36
+ workflow do
37
+ state :initial do
38
+ event :progress, :transitions_to => :intermediate
39
+ end
40
+ state :intermediate
41
+ state :last
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def metaclass; class << self; self; end; end
48
+
49
+ def workflow_spec
50
+ metaclass.workflow_spec
51
+ end
52
+
53
+ end
54
+
55
+ booking1 = Booking.find_by_title('booking1')
56
+ booking1.initialize_workflow
57
+
58
+ booking2 = Booking.find_by_title('booking2')
59
+ booking2.initialize_workflow
60
+
61
+ assert booking1.initial?
62
+ booking1.progress!
63
+ assert booking1.last?, 'booking1 should transition to the "last" state'
64
+
65
+ assert booking2.initial?
66
+ booking2.progress!
67
+ assert booking2.intermediate?, 'booking2 should transition to the "intermediate" state'
68
+
69
+ assert booking1.workflow_spec, 'can access the individual workflow specification'
70
+ assert_equal 2, booking1.workflow_spec.states.length
71
+ assert_equal 3, booking2.workflow_spec.states.length
72
+
73
+ # check persistence
74
+ booking2reloaded = Booking.find_by_title('booking2')
75
+ booking2reloaded.initialize_workflow
76
+ assert booking2reloaded.intermediate?, 'persistence of workflow state does not work'
77
+ end
78
+
79
+ class Object
80
+ # The hidden singleton lurks behind everyone
81
+ def metaclass; class << self; self; end; end
82
+ end
83
+
84
+ end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+ require 'workflow'
3
+
4
+ class ComparableStatesOrder
5
+ include Workflow
6
+ workflow do
7
+ state :submitted do
8
+ event :accept, :transitions_to => :accepted, :meta => {:weight => 8} do |reviewer, args|
9
+ end
10
+ end
11
+ state :accepted do
12
+ event :ship, :transitions_to => :shipped
13
+ end
14
+ state :shipped
15
+ end
16
+ end
17
+
18
+ class CompareStatesTest < Test::Unit::TestCase
19
+
20
+ test 'compare states' do
21
+ o = ComparableStatesOrder.new
22
+ o.accept!
23
+ assert_equal :accepted, o.current_state.name
24
+ assert o.current_state == :accepted
25
+ assert o.current_state < :shipped
26
+ assert o.current_state > :submitted
27
+ assert_raise ArgumentError do
28
+ o.current_state > :unknown
29
+ end
30
+ end
31
+
32
+ end