workflow 1.1.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c18ece78f869cf0939fd801a99b63a93b99eafd
4
- data.tar.gz: 949a3b5bff0e79ced88e7ba5a3050820f58efd40
3
+ metadata.gz: 9b3ca21163c2fe680f13487bd176530fa13e09b0
4
+ data.tar.gz: 3413ad93c300e668a778db3480a7ab11ea3910f1
5
5
  SHA512:
6
- metadata.gz: 801d65a4147f19643d429d38e794f9529ff760147cf63185ab19762ddf618deb8f8ff19deb0ec590b8a3cd7166c99bfa129fdf716dc8b261c7cb98478685a494
7
- data.tar.gz: 5cc6722b3029ab9a96a2247215706315561735f6f7fa5950727f22b969040c53bba907532e3fada31d485c9c9ac6a39a66b130ed956f61b8ef68a2f566018332
6
+ metadata.gz: 78329832051f3798e4fc5742eb1ea0f8d10e4f342bcd42e5cf6cb680a7355747e1e12dbcc9a13bc17537843e59e238cc98edd72e67aff106d9b087b2f53901b8
7
+ data.tar.gz: 992fe9887c6825b047fbdb28dcc16f10994705fad01aeaedb27266e92469ab4e28f91784d8cb8cb7901b82ce1843b51c03f28abca4b9f8b2550a95b8c88e6c14
data/.gitignore CHANGED
@@ -6,7 +6,7 @@ html/
6
6
  .bundle
7
7
  .config
8
8
  .yardoc
9
- Gemfile.lock
9
+ Gemfile*.lock
10
10
  InstalledFiles
11
11
  _yardoc
12
12
  coverage
@@ -19,3 +19,9 @@ matrix:
19
19
 
20
20
  - rvm: 2.0.0
21
21
  gemfile: gemfiles/Gemfile.rails-3.x
22
+
23
+ - rvm: 2.0.0
24
+ gemfile: gemfiles/Gemfile.rails-4.0
25
+
26
+ - rvm: 2.1.0
27
+ gemfile: gemfiles/Gemfile.rails-3.x
@@ -1,5 +1,8 @@
1
- [![Build Status](https://travis-ci.org/geekq/workflow.png?branch=master)](https://travis-ci.org/geekq/workflow)
1
+ [![Build Status](https://travis-ci.org/geekq/workflow.png?branch=master)](https://travis-ci.org/geekq/workflow) Tested with [different Ruby and Rails versions](https://travis-ci.org/geekq/workflow)
2
2
 
3
+ Note: you can find documentation for specific workflow rubygem versions at
4
+ http://rubygems.org/gems/workflow: select a version (optional, default
5
+ is latest release), click "Documentation" link.
3
6
 
4
7
  What is workflow?
5
8
  -----------------
@@ -79,7 +82,7 @@ after another state (by the order they were defined):
79
82
  => true
80
83
  article.current_state >= :accepted
81
84
  => false
82
- article.between? :awaiting_review, :rejected
85
+ article.current_state.between? :awaiting_review, :rejected
83
86
  => true
84
87
 
85
88
  Now we can call the submit event, which transitions to the
@@ -147,9 +150,10 @@ be:
147
150
  end
148
151
  end
149
152
 
150
- `article.review!; article.reject!` will cause a state transition, persist the new state
151
- (if integrated with ActiveRecord) and invoke this user defined reject
152
- method.
153
+ `article.review!; article.reject!` will cause state transition to
154
+ `being_reviewed` state, persist the new state (if integrated with
155
+ ActiveRecord), invoke this user defined `reject` method and finally
156
+ persist the `rejected` state.
153
157
 
154
158
  Note: on successful transition from one state to another the workflow
155
159
  gem immediately persists the new workflow state with `update_column()`,
@@ -300,11 +304,87 @@ couchrest library.
300
304
  Please also have a look at
301
305
  [the full source code](http://github.com/geekq/workflow/blob/master/test/couchtiny_example.rb).
302
306
 
303
- Integration with Mongoid
304
- ------------------------
305
307
 
306
- You can integrate with Mongoid following the example above for CouchDB, but there is a gem that does that for you (and includes extensive tests):
307
- [workflow_on_mongoid](http://github.com/bowsersenior/workflow_on_mongoid)
308
+ Adapters to support other databases
309
+ -----------------------------------
310
+
311
+ I get a lot of requests to integrate persistence support for different
312
+ databases, object-relational adapters, column stores, document
313
+ databases.
314
+
315
+ To enable highest possible quality, avoid too many dependencies and to
316
+ avoid unneeded maintenance burden on the `workflow` core it is best to
317
+ implement such support as a separate gem.
318
+
319
+ Only support for the ActiveRecord will remain for the foreseeable
320
+ future. So Rails beginners can expect `workflow` to work with Rails out
321
+ of the box. Other already included adapters stay for a while but should
322
+ be extracted to separate gems.
323
+
324
+ If you want to implement support for your favorite ORM mapper or your
325
+ favorite NoSQL database, you just need to implement a module which
326
+ overrides the persistence methods `load_workflow_state` and
327
+ `persist_workflow_state`. Example:
328
+
329
+ module Workflow
330
+ module SuperCoolDb
331
+ module InstanceMethods
332
+ def load_workflow_state
333
+ # Load and return the workflow_state from some storage.
334
+ # You can use self.class.workflow_column configuration.
335
+ end
336
+
337
+ def persist_workflow_state(new_value)
338
+ # save the new_value workflow state
339
+ end
340
+ end
341
+
342
+ module ClassMethods
343
+ # class methods of your adapter go here
344
+ end
345
+
346
+ def self.included(klass)
347
+ klass.send :include, InstanceMethods
348
+ klass.extend ClassMethods
349
+ end
350
+ end
351
+ end
352
+
353
+ The user of the adapter can use it then as:
354
+
355
+ class Article
356
+ include Workflow
357
+ include Workflow:SuperCoolDb
358
+ workflow do
359
+ state :submitted
360
+ # ...
361
+ end
362
+ end
363
+
364
+ I can then link to your implementation from this README. Please let me
365
+ also know, if you need any interface beyond `load_workflow_state` and
366
+ `persist_workflow_state` methods to implement an adapter for your
367
+ favorite database.
368
+
369
+
370
+ Custom Versions of Existing Adapters
371
+ ------------------------------------
372
+
373
+ Other adapters (such as a custom ActiveRecord plugin) can be selected by adding a `workflow_adapter` class method, eg.
374
+
375
+ ```ruby
376
+ class Example < ActiveRecord::Base
377
+ def self.workflow_adapter
378
+ MyCustomAdapter
379
+ end
380
+ include Workflow
381
+
382
+ # ...
383
+ end
384
+ ```
385
+
386
+ (The above will include `MyCustomAdapter` *instead* of `Workflow::Adapter::ActiveRecord`.)
387
+
308
388
 
309
389
  Accessing your workflow specification
310
390
  -------------------------------------
@@ -341,6 +421,25 @@ The workflow library itself uses this feature to tweak the graphical
341
421
  representation of the workflow. See below.
342
422
 
343
423
 
424
+ Conditional event transitions
425
+ -----------------------------
426
+
427
+ Conditions are procs or lambdas added to events, like so:
428
+
429
+ state :off
430
+ event :turn_on, :transition_to => :on,
431
+ :if => proc { |device| device.battery_level > 0 }
432
+ event :turn_on, :transition_to => :low_battery,
433
+ :if => proc { |device| device.battery_level > 10 }
434
+ end
435
+
436
+ When calling a `device.can_<fire_event>?` check, or attempting a `device.<event>!`, each event is checked in turn:
437
+
438
+ * With no :if check, proceed as usual.
439
+ * If an :if check is present, proceed if it evaluates to true, or drop to the next event.
440
+ * If you've run out of events to check (eg. battery_level == 0), then the transition isn't possible.
441
+
442
+
344
443
  Advanced transition hooks
345
444
  -------------------------
346
445
 
@@ -541,6 +640,16 @@ when using both a block and a callback method for an event, the block executes p
541
640
  Changelog
542
641
  ---------
543
642
 
643
+ ### New in the version 1.2.0
644
+
645
+ * Fix issue #98 protected on\_\* callbacks in Ruby 2
646
+ * #106 Inherit exceptions from StandardError instead of Exception
647
+ * #109 Conditional event transitions, contributed by [damncabbage](http://robhoward.id.au/)
648
+ * New policy for supporting other databases - extract to separate
649
+ gems. See the README section above.
650
+ * #111 Custom Versions of Existing Adapters by [damncabbage](http://robhoward.id.au/)
651
+
652
+
544
653
  ### New in the version 1.1.0
545
654
 
546
655
  * Tested with ActiveRecord 4.0 (Rails 4.0)
@@ -651,7 +760,9 @@ Support
651
760
  About
652
761
  -----
653
762
 
654
- Author: Vladimir Dobriakov, <http://www.innoq.com/blog/vd>, <http://blog.geekq.net/>
763
+ Author: Vladimir Dobriakov, <http://www.mobile-web-consulting.de>, <http://blog.geekq.net/>
764
+
765
+ Copyright (c) 2010-2014 Vladimir Dobriakov, www.mobile-web-consulting.de
655
766
 
656
767
  Copyright (c) 2008-2009 Vodafone
657
768
 
@@ -7,5 +7,5 @@ group :development do
7
7
  gem "sqlite3"
8
8
  gem "mocha"
9
9
  gem "rake"
10
- gem "ruby-graphviz", ">= 1.0"
10
+ gem "ruby-graphviz", "~> 1.0.0"
11
11
  end
@@ -7,5 +7,5 @@ group :development do
7
7
  gem "sqlite3"
8
8
  gem "mocha"
9
9
  gem "rake"
10
- gem "ruby-graphviz", ">= 1.0"
10
+ gem "ruby-graphviz", "~> 1.0.0"
11
11
  end
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "minitest", "< 5.0.0" # 5.0.0 introduced incompatible changes renaming all the classes
5
+ gem "rdoc", ">= 3.12"
6
+ gem "bundler", ">= 1.0.0"
7
+ gem "activerecord", "~>4.0"
8
+ gem 'protected_attributes'
9
+ gem "sqlite3"
10
+ gem "mocha"
11
+ gem "rake"
12
+ gem "ruby-graphviz", "~> 1.0.0"
13
+ end
@@ -1,12 +1,13 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  group :development do
4
+ gem "minitest", "< 5.0.0" # 5.0.0 introduced incompatible changes renaming all the classes
4
5
  gem "rdoc", ">= 3.12"
5
6
  gem "bundler", ">= 1.0.0"
6
7
  gem "activerecord"
7
8
  gem "sqlite3"
8
9
  gem "mocha"
9
10
  gem "rake"
10
- gem "ruby-graphviz", ">= 1.0"
11
+ gem "ruby-graphviz", "~> 1.0.0"
11
12
  gem 'protected_attributes'
12
13
  end
@@ -38,7 +38,7 @@ module Workflow
38
38
  undef_method "#{state_name}?"
39
39
  end
40
40
 
41
- state.events.values.each do |event|
41
+ state.events.flat.each do |event|
42
42
  event_name = event.name
43
43
  module_eval do
44
44
  undef_method "#{event_name}!".to_sym
@@ -57,7 +57,7 @@ module Workflow
57
57
  end
58
58
  end
59
59
 
60
- state.events.values.each do |event|
60
+ state.events.flat.each do |event|
61
61
  event_name = event.name
62
62
  module_eval do
63
63
  define_method "#{event_name}!".to_sym do |*args|
@@ -65,7 +65,7 @@ module Workflow
65
65
  end
66
66
 
67
67
  define_method "can_#{event_name}?" do
68
- return self.current_state.events.include?(event_name)
68
+ return !!current_state.events.first_applicable(event_name, self)
69
69
  end
70
70
  end
71
71
  end
@@ -94,7 +94,7 @@ module Workflow
94
94
  end
95
95
 
96
96
  def process_event!(name, *args)
97
- event = current_state.events[name.to_sym]
97
+ event = current_state.events.first_applicable(name, self)
98
98
  raise NoTransitionAllowed.new(
99
99
  "There is no event #{name.to_sym} defined for the #{current_state} state") \
100
100
  if event.nil?
@@ -111,7 +111,7 @@ module Workflow
111
111
 
112
112
  begin
113
113
  return_value = run_action(event.action, *args) || run_action_callback(event.name, *args)
114
- rescue Exception => e
114
+ rescue StandardError => e
115
115
  run_on_error(e, from, to, name, *args)
116
116
  end
117
117
 
@@ -214,7 +214,7 @@ module Workflow
214
214
  instance_exec(prior_state.name, triggering_event, *args, &state.on_entry)
215
215
  else
216
216
  hook_name = "on_#{state}_entry"
217
- self.send hook_name, prior_state, triggering_event, *args if self.respond_to? hook_name
217
+ self.send hook_name, prior_state, triggering_event, *args if has_callback?(hook_name)
218
218
  end
219
219
  end
220
220
 
@@ -224,7 +224,7 @@ module Workflow
224
224
  instance_exec(new_state.name, triggering_event, *args, &state.on_exit)
225
225
  else
226
226
  hook_name = "on_#{state}_exit"
227
- self.send hook_name, new_state, triggering_event, *args if self.respond_to? hook_name
227
+ self.send hook_name, new_state, triggering_event, *args if has_callback?(hook_name)
228
228
  end
229
229
  end
230
230
  end
@@ -262,15 +262,15 @@ module Workflow
262
262
 
263
263
  klass.extend ClassMethods
264
264
 
265
- if Object.const_defined?(:ActiveRecord)
266
- if klass < ActiveRecord::Base
267
- klass.send :include, Adapter::ActiveRecord::InstanceMethods
268
- klass.send :extend, Adapter::ActiveRecord::Scopes
269
- klass.before_validation :write_initial_state
265
+ # Look for a hook; otherwise detect based on ancestor class.
266
+ if klass.respond_to?(:workflow_adapter)
267
+ klass.send :include, klass.workflow_adapter
268
+ else
269
+ if Object.const_defined?(:ActiveRecord) && klass < ActiveRecord::Base
270
+ klass.send :include, Adapter::ActiveRecord
270
271
  end
271
- elsif Object.const_defined?(:Remodel)
272
- if klass < Adapter::Remodel::Entity
273
- klass.send :include, Remodel::InstanceMethods
272
+ if Object.const_defined?(:Remodel) && klass < Adapter::Remodel::Entity
273
+ klass.send :include, Adapter::Remodel::InstanceMethods
274
274
  end
275
275
  end
276
276
  end
@@ -1,6 +1,12 @@
1
1
  module Workflow
2
2
  module Adapter
3
3
  module ActiveRecord
4
+ def self.included(klass)
5
+ klass.send :include, Adapter::ActiveRecord::InstanceMethods
6
+ klass.send :extend, Adapter::ActiveRecord::Scopes
7
+ klass.before_validation :write_initial_state
8
+ end
9
+
4
10
  module InstanceMethods
5
11
  def load_workflow_state
6
12
  read_attribute(self.class.workflow_column)
@@ -1,7 +1,7 @@
1
1
  begin
2
2
  require 'rubygems'
3
3
 
4
- gem 'ruby-graphviz', '>=1.0'
4
+ gem 'ruby-graphviz', '~> 1.0.0'
5
5
  gem 'activesupport'
6
6
 
7
7
  require 'graphviz'
@@ -57,7 +57,7 @@ module Workflow
57
57
  node = state.draw(graph)
58
58
  node.fontname = options[:font]
59
59
 
60
- state.events.each do |_, event|
60
+ state.events.flat.each do |event|
61
61
  edge = event.draw(graph, state)
62
62
  edge.fontname = options[:font]
63
63
  end
@@ -1,5 +1,5 @@
1
1
  module Workflow
2
- class TransitionHalted < Exception
2
+ class TransitionHalted < StandardError
3
3
 
4
4
  attr_reader :halted_because
5
5
 
@@ -10,9 +10,9 @@ module Workflow
10
10
 
11
11
  end
12
12
 
13
- class NoTransitionAllowed < Exception; end
13
+ class NoTransitionAllowed < StandardError; end
14
14
 
15
- class WorkflowError < Exception; end
15
+ class WorkflowError < StandardError; end
16
16
 
17
- class WorkflowDefinitionError < Exception; end
18
- end
17
+ class WorkflowDefinitionError < StandardError; end
18
+ end
@@ -1,10 +1,22 @@
1
1
  module Workflow
2
2
  class Event
3
3
 
4
- attr_accessor :name, :transitions_to, :meta, :action
4
+ attr_accessor :name, :transitions_to, :meta, :action, :condition
5
5
 
6
- def initialize(name, transitions_to, meta = {}, &action)
7
- @name, @transitions_to, @meta, @action = name, transitions_to.to_sym, meta, action
6
+ def initialize(name, transitions_to, condition = nil, meta = {}, &action)
7
+ @name = name
8
+ @transitions_to = transitions_to.to_sym
9
+ @meta = meta
10
+ @action = action
11
+ @condition = if condition.nil? || condition.respond_to?(:call)
12
+ condition
13
+ else
14
+ raise TypeError, 'condition must be nil or callable (eg. a proc or lambda)'
15
+ end
16
+ end
17
+
18
+ def condition_applicable?(object)
19
+ condition ? condition.call(object) : true
8
20
  end
9
21
 
10
22
  def draw(graph, from_state)
@@ -15,4 +27,4 @@ module Workflow
15
27
  @name.to_s
16
28
  end
17
29
  end
18
- end
30
+ end
@@ -0,0 +1,36 @@
1
+ module Workflow
2
+ class EventCollection < Hash
3
+
4
+ def [](name)
5
+ super name.to_sym # Normalize to symbol
6
+ end
7
+
8
+ def push(name, event)
9
+ key = name.to_sym
10
+ self[key] ||= []
11
+ self[key] << event
12
+ end
13
+
14
+ def flat
15
+ self.values.flatten.uniq do |event|
16
+ [:name, :transitions_to, :meta, :action].map { |m| event.send(m) }
17
+ end
18
+ end
19
+
20
+ def include?(name_or_obj)
21
+ case name_or_obj
22
+ when Event
23
+ flat.include? name_or_obj
24
+ else
25
+ !(self[name_or_obj].nil?)
26
+ end
27
+ end
28
+
29
+ def first_applicable(name, object_context)
30
+ (self[name] || []).detect do |event|
31
+ event.condition_applicable?(object_context) && event
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -1,5 +1,6 @@
1
1
  require 'workflow/state'
2
2
  require 'workflow/event'
3
+ require 'workflow/event_collection'
3
4
  require 'workflow/errors'
4
5
 
5
6
  module Workflow
@@ -30,11 +31,13 @@ module Workflow
30
31
 
31
32
  def event(name, args = {}, &action)
32
33
  target = args[:transitions_to] || args[:transition_to]
34
+ condition = args[:if]
33
35
  raise WorkflowDefinitionError.new(
34
36
  "missing ':transitions_to' in workflow event definition for '#{name}'") \
35
37
  if target.nil?
36
- @scoped_state.events[name.to_sym] =
37
- Workflow::Event.new(name, target, (args[:meta] or {}), &action)
38
+ @scoped_state.events.push(
39
+ name, Workflow::Event.new(name, target, condition, (args[:meta] or {}), &action)
40
+ )
38
41
  end
39
42
 
40
43
  def on_entry(&proc)
@@ -61,4 +64,4 @@ module Workflow
61
64
  @on_error_proc = proc
62
65
  end
63
66
  end
64
- end
67
+ end
@@ -4,7 +4,7 @@ module Workflow
4
4
  attr_reader :spec
5
5
 
6
6
  def initialize(name, spec, meta = {})
7
- @name, @spec, @events, @meta = name, spec, Hash.new, meta
7
+ @name, @spec, @events, @meta = name, spec, EventCollection.new, meta
8
8
  end
9
9
 
10
10
  def draw(graph)
@@ -1,3 +1,3 @@
1
1
  module Workflow
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -0,0 +1,52 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+ require 'workflow'
3
+ class AdapterHookTest < ActiveRecordTestCase
4
+ test 'hook to choose adapter' do
5
+
6
+ ActiveRecord::Schema.define do
7
+ create_table(:examples) { |t| t.string :workflow_state }
8
+ end
9
+
10
+ class DefaultAdapter < ActiveRecord::Base
11
+ self.table_name = :examples
12
+ include Workflow
13
+ workflow do
14
+ state(:initial) { event :progress, :transitions_to => :last }
15
+ state(:last)
16
+ end
17
+ end
18
+
19
+ class ChosenByHookAdapter < ActiveRecord::Base
20
+ self.table_name = :examples
21
+ attr_reader :foo
22
+ def self.workflow_adapter
23
+ Module.new do
24
+ def load_workflow_state
25
+ @foo if defined?(@foo)
26
+ end
27
+ def persist_workflow_state(new_value)
28
+ @foo = new_value
29
+ end
30
+ end
31
+ end
32
+
33
+ include Workflow
34
+ workflow do
35
+ state(:initial) { event :progress, :transitions_to => :last }
36
+ state(:last)
37
+ end
38
+ end
39
+
40
+ default = DefaultAdapter.create
41
+ assert default.initial?
42
+ default.progress!
43
+ assert default.last?
44
+ assert DefaultAdapter.find(default.id).last?, 'should have persisted via ActiveRecord'
45
+
46
+ hook = ChosenByHookAdapter.create
47
+ assert hook.initial?
48
+ hook.progress!
49
+ assert_equal hook.foo, 'last', 'should have "persisted" with custom adapter'
50
+ assert ChosenByHookAdapter.find(hook.id).initial?, 'should not have persisted via ActiveRecord'
51
+ end
52
+ end
@@ -28,13 +28,15 @@ class AdvanceExamplesTest < ActiveRecordTestCase
28
28
  spec.state_names.each do |state_name|
29
29
  state = spec.states[state_name]
30
30
 
31
- (state.events.values.reject {|e| e.name.to_s =~ /^revert_/ }).each do |event|
31
+ (state.events.flat.reject {|e| e.name.to_s =~ /^revert_/ }).each do |event|
32
32
  event_name = event.name
33
33
  revert_event_name = "revert_" + event_name.to_s
34
34
 
35
35
  # Add revert events
36
- spec.states[event.transitions_to.to_sym].events[revert_event_name.to_sym] =
37
- Workflow::Event.new(revert_event_name, state, {})
36
+ spec.states[event.transitions_to.to_sym].events.push(
37
+ revert_event_name,
38
+ Workflow::Event.new(revert_event_name, state)
39
+ )
38
40
 
39
41
  # Add methods for revert events
40
42
  Article.module_eval do
@@ -43,7 +43,7 @@ class Article < ActiveRecord::Base
43
43
  singleton = class << self; self end
44
44
  validations = Proc.new {}
45
45
 
46
- meta = Article.workflow_spec.states[from].events[triggering_event].meta
46
+ meta = Article.workflow_spec.states[from].events[triggering_event].first.meta
47
47
  fields_to_validate = meta[:validates_presence_of]
48
48
  if fields_to_validate
49
49
  validations = Proc.new {
@@ -282,6 +282,7 @@ class MainTest < ActiveRecordTestCase
282
282
  args = mock()
283
283
  args.expects(:log).with('in private callback').once
284
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
285
286
 
286
287
  b = Class.new # the base class with a protected callback
287
288
  b.class_eval do
@@ -289,6 +290,7 @@ class MainTest < ActiveRecordTestCase
289
290
  def assign_old(args)
290
291
  args.log('in protected callback in the base class')
291
292
  end
293
+
292
294
  end
293
295
 
294
296
  c = Class.new(b) # inheriting class with an additional protected callback
@@ -303,6 +305,11 @@ class MainTest < ActiveRecordTestCase
303
305
  state :assigned_old
304
306
  end
305
307
 
308
+ protected
309
+ def on_assigned_entry(prev_state, event, args)
310
+ args.log('in protected callback `on_assigned_entry`')
311
+ end
312
+
306
313
  private
307
314
  def assign(args)
308
315
  args.log('in private callback')
@@ -509,6 +516,7 @@ class MainTest < ActiveRecordTestCase
509
516
  event :go_to_college, :transitions_to => :student
510
517
  end
511
518
  state :student
519
+ state :street
512
520
  end
513
521
  end
514
522
 
@@ -517,6 +525,38 @@ class MainTest < ActiveRecordTestCase
517
525
  assert_equal false, human.can_go_to_college?
518
526
  end
519
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 => proc { |obj| obj.battery > 10 }
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
+ end
544
+
545
+ device = c.new 0
546
+ assert_equal false, device.can_turn_on?
547
+
548
+ device = c.new 5
549
+ assert device.can_turn_on?
550
+ device.turn_on!
551
+ assert device.low_battery?
552
+ assert_equal false, device.on?
553
+
554
+ device = c.new 50
555
+ assert device.can_turn_on?
556
+ device.turn_on!
557
+ assert device.on?
558
+ end
559
+
520
560
  test 'workflow graph generation' do
521
561
  Dir.chdir('/tmp') do
522
562
  capture_streams do
@@ -27,6 +27,6 @@ Gem::Specification.new do |gem|
27
27
  gem.add_development_dependency 'sqlite3'
28
28
  gem.add_development_dependency 'mocha'
29
29
  gem.add_development_dependency 'rake'
30
- gem.add_development_dependency 'ruby-graphviz', ['>= 1.0']
30
+ gem.add_development_dependency 'ruby-graphviz', ['~> 1.0.0']
31
31
  end
32
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dobriakov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-14 00:00:00.000000000 Z
11
+ date: 2014-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -98,16 +98,16 @@ dependencies:
98
98
  name: ruby-graphviz
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ~>
102
102
  - !ruby/object:Gem::Version
103
- version: '1.0'
103
+ version: 1.0.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ~>
109
109
  - !ruby/object:Gem::Version
110
- version: '1.0'
110
+ version: 1.0.0
111
111
  description: |2
112
112
  Workflow is a finite-state-machine-inspired API for modeling and interacting
113
113
  with what we tend to refer to as 'workflow'.
@@ -132,6 +132,7 @@ files:
132
132
  - Rakefile
133
133
  - gemfiles/Gemfile.rails-2.3.x
134
134
  - gemfiles/Gemfile.rails-3.x
135
+ - gemfiles/Gemfile.rails-4.0
135
136
  - gemfiles/Gemfile.rails-edge
136
137
  - lib/workflow.rb
137
138
  - lib/workflow/adapters/active_record.rb
@@ -139,11 +140,13 @@ files:
139
140
  - lib/workflow/draw.rb
140
141
  - lib/workflow/errors.rb
141
142
  - lib/workflow/event.rb
143
+ - lib/workflow/event_collection.rb
142
144
  - lib/workflow/specification.rb
143
145
  - lib/workflow/state.rb
144
146
  - lib/workflow/version.rb
145
147
  - orders_workflow.png
146
148
  - test/active_record_scopes_test.rb
149
+ - test/adapter_hook_test.rb
147
150
  - test/advanced_examples_test.rb
148
151
  - test/advanced_hooks_and_validation_test.rb
149
152
  - test/attr_protected_test.rb
@@ -178,12 +181,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
181
  version: '0'
179
182
  requirements: []
180
183
  rubyforge_project:
181
- rubygems_version: 2.0.0
184
+ rubygems_version: 2.0.14
182
185
  signing_key:
183
186
  specification_version: 4
184
187
  summary: A replacement for acts_as_state_machine.
185
188
  test_files:
186
189
  - test/active_record_scopes_test.rb
190
+ - test/adapter_hook_test.rb
187
191
  - test/advanced_examples_test.rb
188
192
  - test/advanced_hooks_and_validation_test.rb
189
193
  - test/attr_protected_test.rb