newflow 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ require 'logger'
2
+ require 'forwardable'
3
+
4
+ # Require all the files
5
+ base_dir = File.dirname(__FILE__) + "/newflow"
6
+ Dir["#{base_dir}/*.rb"].each { |f| require f }
7
+
8
+ # TODO: Allow workflows to identify themselves (for logging and stuffs)
9
+ module Newflow
10
+ class InvalidStateDefinitionError < ArgumentError; end
11
+ class InvalidWorkflowStateError < ArgumentError;
12
+ def initialize(state)
13
+ @state = state
14
+ end
15
+
16
+ def message
17
+ "'#{@state}' is not a valid state"
18
+ end
19
+ end
20
+
21
+ WITH_SIDE_EFFECTS = true
22
+ WITHOUT_SIDE_EFFECTS = false
23
+
24
+ def self.included(base)
25
+ base.send(:include, InstanceMethods)
26
+ if base.ancestors.map {|a| a.to_s }.include?("ActiveRecord::Base")
27
+ base.send(:include, ActiveRecordInstantiator)
28
+ else
29
+ base.send(:include, NonActiveRecordInstantiator)
30
+ end
31
+ base.send(:extend, ClassMethods, Forwardable)
32
+ base.def_delegators :workflow, :transition!, :transition_once!, :current_state, :current_state=,
33
+ :would_transition_to
34
+ end
35
+
36
+ module ActiveRecordInstantiator # TODO: TEST
37
+ def after_initialize_with_workflow
38
+ after_initialize_without_workflow if respond_to?(:after_initialize_without_workflow)
39
+ workflow # This will set the workflow_state
40
+ end
41
+ if respond_to?(:after_initialize)
42
+ alias_method :after_initialize_without_workflow, :after_initialize
43
+ end
44
+ alias_method :after_initialize, :after_initialize_with_workflow
45
+ end
46
+
47
+ module NonActiveRecordInstantiator
48
+ def initialize_with_workflow(*args, &block)
49
+ initialize_without_workflow(*args, &block)
50
+ workflow # This will set the workflow_state
51
+ end
52
+ alias_method :initialize_without_workflow, :initialize
53
+ alias_method :initialize, :initialize_with_workflow
54
+ end
55
+
56
+ module InstanceMethods
57
+ def workflow
58
+ @workflow ||= Workflow.new(self, self.class.__workflow_definition)
59
+ end
60
+ end
61
+
62
+ module ClassMethods
63
+ def define_workflow(&workflow_definition)
64
+ @__workflow_definition = workflow_definition
65
+ __define_query_methods(workflow_definition)
66
+ end
67
+
68
+ def __define_query_methods(workflow_definition)
69
+ @state_catcher = Object.new
70
+ @state_catcher.instance_variable_set("@states", [])
71
+ def @state_catcher.states; @states; end
72
+ def @state_catcher.state(name, *args); @states << name; end
73
+ @state_catcher.instance_eval &workflow_definition
74
+ @state_catcher.states.each do |state|
75
+ self.send(:define_method, "#{state}?") do
76
+ workflow.send("#{state}?")
77
+ end
78
+ end
79
+ end
80
+
81
+ def __workflow_definition
82
+ @__workflow_definition
83
+ end
84
+ end
85
+
86
+ def self.logger
87
+ return @logger if @logger
88
+ @logger = if defined?(Rails)
89
+ Rails.logger
90
+ else
91
+ Logger.new(File.open('/dev/null', 'w'))
92
+ end
93
+ end
94
+ end
95
+
@@ -0,0 +1,61 @@
1
+ module Newflow
2
+ class State
3
+ attr_reader :name, :transitions
4
+
5
+ def initialize(name, opts={}, &transitions_block)
6
+ logger.debug "State.initialize: name=#{name} opts=#{opts.inspect}"
7
+ @name = name
8
+ @opts = opts
9
+ @is_start = opts[:start]
10
+ @is_stop = opts[:stop]
11
+ @on_entry = Trigger.new(opts[:on_entry])
12
+ @transitions = []
13
+ check_validity
14
+ instance_eval &transitions_block if transitions_block
15
+ end
16
+
17
+ def transitions_to(target_state, opts={})
18
+ @transitions << Transition.new(target_state,opts)
19
+ end
20
+
21
+ def run(workflow, do_trigger=Newflow::WITH_SIDE_EFFECTS)
22
+ return @name unless @transitions
23
+ # We may want to consider looking at all transitions and letting user know
24
+ # that you can move in multiple directions
25
+ transition_to = @transitions.detect { |t| t.can_transition?(workflow) }
26
+ if transition_to
27
+ transition_to.trigger!(workflow) if do_trigger # TODO: TEST
28
+ transition_to.target_state
29
+ else
30
+ @name
31
+ end
32
+ end
33
+
34
+ # TODO: use convention of name == :start instead of a :start opt, not same for stop
35
+ def start?
36
+ @opts[:start]
37
+ end
38
+
39
+ def stop?
40
+ @opts[:stop]
41
+ end
42
+
43
+ def run_on_entry(workflow, do_trigger=Newflow::WITH_SIDE_EFFECTS)
44
+ @on_entry.run!(workflow) if do_trigger && @on_entry
45
+ end
46
+
47
+ def to_s
48
+ @name.to_s
49
+ end
50
+
51
+ def logger
52
+ Newflow.logger
53
+ end
54
+
55
+ private
56
+ def check_validity
57
+ raise "State #{name} cannot be both a start and a stop" if start? && stop?
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,46 @@
1
+ module Newflow
2
+ class Transition
3
+ attr_reader :target_state, :predicate, :predicate_name, :trigger
4
+
5
+ def initialize(target_state,opts)
6
+ @target_state = target_state
7
+ if_meth = opts[:if]
8
+ unless_meth = opts[:unless]
9
+ @trigger = Trigger.new(opts[:trigger])
10
+ logger.debug "State.transitions_to: target_state=#{target_state} if=#{if_meth.inspect} unless=#{unless_meth.inspect} trigger=#{@trigger}"
11
+ unless @target_state \
12
+ && (if_meth || unless_meth) \
13
+ && !(if_meth && unless_meth)
14
+ raise "You must specify a target state(#@target_state) and (if_method OR unless_method)"
15
+ end
16
+ @predicate_name = (if_meth || "!#{unless_meth}").to_s
17
+ @predicate = if if_meth
18
+ # TODO: be smart
19
+ if if_meth.respond_to?(:call)
20
+ lambda { |wf| if_meth.call }
21
+ else
22
+ lambda { |wf| wf.send(if_meth) }
23
+ end
24
+ else
25
+ if unless_meth.respond_to?(:call)
26
+ lambda { |wf| !unless_meth.call }
27
+ else
28
+ lambda { |wf| !wf.send(unless_meth) }
29
+ end
30
+ end
31
+ end
32
+
33
+ def can_transition?(workflow)
34
+ predicate.call(workflow)
35
+ end
36
+
37
+ def trigger!(workflow)
38
+ trigger.run!(workflow)
39
+ end
40
+
41
+ def logger
42
+ Newflow.logger
43
+ end
44
+ end
45
+ end
46
+
@@ -0,0 +1,20 @@
1
+ module Newflow
2
+ class Trigger
3
+ def initialize(trigger)
4
+ @trigger = trigger
5
+ end
6
+
7
+ def run!(workflow)
8
+ return false unless @trigger
9
+ case @trigger
10
+ when Symbol
11
+ workflow.send(@trigger)
12
+ when Array
13
+ @trigger.each {|t| Trigger.new(t).run!(workflow) }
14
+ else
15
+ @trigger.call
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,103 @@
1
+ module Newflow
2
+ class Workflow
3
+ def initialize(extendee, definition)
4
+ @extendee = extendee
5
+ construct_workflow!(definition)
6
+ end
7
+
8
+ def validate_workflow!
9
+ # TODO: Validate that all transitions reach a valid state
10
+ # TODO: Validate that there is at least one stop state
11
+ raise InvalidStateDefinitionError.new("#{@extendee.class} needs at least two states") if states.size < 2
12
+ raise InvalidStateDefinitionError.new("#{@extendee.class} needs a start of the workflow") unless current_state
13
+ end
14
+
15
+ def states
16
+ @states ||= {}
17
+ end
18
+
19
+ def state(name, opts={}, &block)
20
+ # TODO: Assert we're not overriding a state
21
+ states[name] = State.new(name, opts, &block)
22
+ end
23
+
24
+ def construct_workflow!(definition)
25
+ instance_eval &definition
26
+ start_state = states.values.detect { |s| s.start? }
27
+ @extendee.workflow_state ||= start_state.name.to_s if start_state
28
+ validate_workflow!
29
+ define_state_query_methods
30
+ raise InvalidWorkflowStateError.new(current_state) unless states[current_state]
31
+ end
32
+
33
+ def define_state_query_methods
34
+ states.keys.each do |key|
35
+ instance_eval <<-EOS
36
+ def #{key}?; current_state == :#{key}; end
37
+ EOS
38
+ end
39
+ end
40
+
41
+ def transition_once!(do_trigger=Newflow::WITH_SIDE_EFFECTS)
42
+ state = states[current_state]
43
+ raise InvalidWorkflowStateError.new(current_state) unless state # TODO: TEST
44
+ target_state = states[state.run(@extendee, do_trigger)]
45
+ if state != target_state
46
+ @extendee.workflow_state = target_state.to_s
47
+ target_state.run_on_entry(@extendee, do_trigger)
48
+ end
49
+ target_state
50
+ end
51
+
52
+ def transition!(do_trigger=Newflow::WITH_SIDE_EFFECTS)
53
+ # TODO: watch out for max # of transits
54
+ previous_state = current_state
55
+ previous_states = {}
56
+ num_transitions = 0
57
+ begin
58
+ if previous_states[current_state]
59
+ raise "Error: possible [infinite] loop in workflow, started in: #{previous_state}, currently in #{current_state}, been through all of (#{previous_states.keys.map(&:to_s).sort.join(", ")})" # TODO: TEST
60
+ end
61
+ previous_states[current_state] = true
62
+ the_state = current_state
63
+ transition_once!(do_trigger)
64
+ end while the_state != current_state && states[current_state]
65
+ previous_state == current_state ? nil : current_state
66
+ ensure
67
+ @extendee.workflow_state = previous_state unless do_trigger
68
+ end
69
+
70
+ def would_transition_to
71
+ transition!(Newflow::WITHOUT_SIDE_EFFECTS)
72
+ end
73
+
74
+ def current_state
75
+ @extendee.workflow_state.to_sym
76
+ end
77
+
78
+ def current_state=(state)
79
+ @extendee.workflow_state = state.to_s
80
+ end
81
+
82
+ def to_dotty
83
+ dot = ""
84
+ dot << "digraph {\n"
85
+ states.keys.each { |state_name|
86
+ state = states[state_name]
87
+ # it'd be nice to have the current state somehow shown visually
88
+ shape = "circle"
89
+ if state_name == current_state
90
+ puts "setting current shape to doublecircle #{state_name} vs #{current_state}"
91
+ shape = "doublecircle"
92
+ end
93
+ dot << %Q[ "#{state_name}" [ shape = #{shape} ]; \n]
94
+ state.transitions.each { |transition|
95
+ dot << " \"#{state_name}\" -> \"#{transition.target_state}\" [ label = \"#{transition.predicate_name}\" ];\n"
96
+ }
97
+ }
98
+ dot << "}\n"
99
+ return dot
100
+ end
101
+ end
102
+ end
103
+
@@ -0,0 +1,62 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "An object including Newflow" do
4
+ before do
5
+ klass = Class.new do
6
+ attr_accessor :workflow_state
7
+ include Newflow
8
+
9
+ define_workflow do
10
+ state :start, :start => true do
11
+ transitions_to :finish, :if => :go_to_finish?
12
+ end
13
+
14
+ state :finish, :stop => true
15
+ end
16
+
17
+ def go_to_finish?
18
+ true
19
+ end
20
+ end
21
+ @obj = klass.new
22
+ end
23
+
24
+ it "should begin in start state" do
25
+ @obj.should be_start
26
+ end
27
+
28
+ it "should stop in the finish state" do
29
+ @obj.transition!
30
+ @obj.should be_finish
31
+ end
32
+
33
+ it "should transition once" do
34
+ @obj.transition_once!
35
+ @obj.should be_finish
36
+ end
37
+
38
+ it "should have a current state" do
39
+ @obj.current_state.should == :start
40
+ end
41
+
42
+ it "should have a workflow state" do
43
+ @obj.workflow_state.should == "start"
44
+ end
45
+
46
+ it "should have a way to manually change the current state" do
47
+ @obj.current_state = :finish
48
+ @obj.workflow_state.should == "finish"
49
+ @obj.should be_finish
50
+ end
51
+
52
+ it "should not eat all missing methods" do
53
+ lambda { @obj.wammo! }.should raise_error(NoMethodError)
54
+ end
55
+
56
+ it "should keep the state even when the workflow is reset" do
57
+ @obj.workflow_state = "finish"
58
+ @obj.instance_variable_set("@workflow", nil)
59
+ @obj.should be_finish
60
+ end
61
+ end
62
+
@@ -0,0 +1,72 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "A valid start state" do
4
+ before do
5
+ @name = :start
6
+ @workflow = mock("workflow")
7
+ @state = Newflow::State.new(@name, :start => true) do
8
+ transitions_to :finish, :if => :go_to_finish?
9
+ end
10
+ end
11
+
12
+ it "should have a name" do
13
+ @state.name.should == @name
14
+ end
15
+
16
+ it "should be a start" do
17
+ @state.should be_start
18
+ end
19
+
20
+ it "should turn itself into a string" do
21
+ @state.to_s.should == @name.to_s
22
+ end
23
+
24
+ it "should should not bomb when running on entry" do
25
+ lambda { @state.run_on_entry(@extendee) }.should_not raise_error
26
+ end
27
+
28
+ it "should return the transition state when the predicate is true" do
29
+ @workflow.should_receive(:go_to_finish?).and_return true
30
+ @state.run(@workflow).should == :finish
31
+ end
32
+
33
+ it "should return the its own state when the predicate is false" do
34
+ @workflow.should_receive(:go_to_finish?).and_return false
35
+ @state.run(@workflow).should == :start
36
+ end
37
+ end
38
+
39
+ describe "A state with on_entry" do
40
+ before do
41
+ @name = :start
42
+ @extendee = mock("extendee")
43
+ @state = Newflow::State.new(@name, :on_entry => :make_pizza)
44
+ end
45
+
46
+ it "should run on entry when triggers are on (default)" do
47
+ @extendee.should_receive(:make_pizza)
48
+ @state.run_on_entry(@extendee)
49
+ end
50
+
51
+ it "should not run on entry when triggers are off" do
52
+ @extendee.should_not_receive(:make_pizza)
53
+ @state.run_on_entry(@extendee, Newflow::WITHOUT_SIDE_EFFECTS)
54
+ end
55
+ end
56
+
57
+ describe "A valid stop state" do
58
+ before do
59
+ @name = :stop
60
+ @state = Newflow::State.new(@name, :stop => true)
61
+ end
62
+
63
+ it "should be a stop" do
64
+ @state.should be_stop
65
+ end
66
+ end
67
+
68
+ describe "Invalid states" do
69
+ it "should not have both a start and a stop" do
70
+ lambda { @state = Newflow::State.new(:hi, :start => true, :stop => true) }.should raise_error
71
+ end
72
+ end
@@ -0,0 +1,109 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "An :if symbol transition with a symbol trigger" do
4
+ before do
5
+ @workflow = mock("workflow")
6
+ @transition = Newflow::Transition.new(:target_state, :if => :predicate?, :trigger => :some_action)
7
+ end
8
+
9
+ it "should not be able to transition if predicate is false" do
10
+ @workflow.should_receive(:predicate?).and_return false
11
+ @transition.can_transition?(@workflow).should be_false
12
+ end
13
+
14
+ it "should be able to transition if predicate is true" do
15
+ @workflow.should_receive(:predicate?).and_return true
16
+ @transition.can_transition?(@workflow).should be_true
17
+ end
18
+
19
+ it "should call the trigger if requested" do
20
+ @workflow.should_receive(:some_action)
21
+ @transition.trigger!(@workflow)
22
+ end
23
+
24
+ it "should have a predicate name" do
25
+ @transition.predicate_name.should == "predicate?"
26
+ end
27
+
28
+ it "should have a target state" do
29
+ @transition.target_state.should == :target_state
30
+ end
31
+ end
32
+
33
+ describe "An :unless symbol transition with a proc trigger" do
34
+ before do
35
+ @workflow = mock("workflow")
36
+ @trigger_ran = false
37
+ @trigger = lambda { @trigger_ran = true }
38
+ @transition = Newflow::Transition.new(:target_state, :unless => :predicate?, :trigger => @trigger)
39
+ end
40
+
41
+ it "should not be able to transition if predicate is true" do
42
+ @workflow.should_receive(:predicate?).and_return true
43
+ @transition.can_transition?(@workflow).should be_false
44
+ end
45
+
46
+ it "should be able to transition if predicate is false" do
47
+ @workflow.should_receive(:predicate?).and_return false
48
+ @transition.can_transition?(@workflow).should be_true
49
+ end
50
+
51
+ it "should call the trigger when requested" do
52
+ @transition.trigger!(@workflow)
53
+ @trigger_ran.should be_true
54
+ end
55
+
56
+ it "should have a predicate name" do
57
+ @transition.predicate_name.should == "!predicate?"
58
+ end
59
+ end
60
+
61
+ describe "An :if proc transition with no trigger" do
62
+ before do
63
+ @workflow = mock("workflow")
64
+ @if_proc = lambda { @predicate_value }
65
+ @transition = Newflow::Transition.new(:target_state, :if => @if_proc)
66
+ end
67
+
68
+ it "should not be able to transition if predicate is false" do
69
+ @predicate_value = false
70
+ @transition.can_transition?(@workflow).should be_false
71
+ end
72
+
73
+ it "should be able to transition if predicate is true" do
74
+ @predicate_value = true
75
+ @transition.can_transition?(@workflow).should be_true
76
+ end
77
+
78
+ it "should do nothing when triggered" do
79
+ lambda { @transition.trigger!(@workflow) }.should_not raise_error
80
+ end
81
+ end
82
+
83
+ describe "An :unless proc transition with no trigger" do
84
+ before do
85
+ @workflow = mock("workflow")
86
+ @unless_proc = lambda { @predicate_value }
87
+ @transition = Newflow::Transition.new(:target_state, :unless => @unless_proc)
88
+ end
89
+
90
+ it "should not be able to transition if predicate is true" do
91
+ @predicate_value = true
92
+ @transition.can_transition?(@workflow).should be_false
93
+ end
94
+
95
+ it "should be able to transition if predicate is false" do
96
+ @predicate_value = false
97
+ @transition.can_transition?(@workflow).should be_true
98
+ end
99
+ end
100
+
101
+ describe "Invalid triggers" do
102
+ it "should not be valid without an if or unless" do
103
+ lambda { @transition = Newflow::Transition.new(:target_state) }.should raise_error
104
+ end
105
+
106
+ it "should not be valid with an if and an unless" do
107
+ lambda { @transition = Newflow::Transition.new(:target_state, :unless => :unless, :if => :if) }.should raise_error
108
+ end
109
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "An empty trigger" do
4
+ before do
5
+ @extendee = mock("extendee")
6
+ @trigger = Newflow::Trigger.new(nil)
7
+ end
8
+
9
+ it "should do nothing when running" do
10
+ lambda { @trigger.run!(@extendee) }.should_not raise_error
11
+ end
12
+ end
13
+
14
+ describe "A symbol trigger" do
15
+ before do
16
+ @extendee = mock("extendee")
17
+ @trigger = Newflow::Trigger.new(:make_pizza)
18
+ end
19
+
20
+ it "should run the trigger on the workflow" do
21
+ @extendee.should_receive(:make_pizza)
22
+ @trigger.run!(@extendee)
23
+ end
24
+ end
25
+
26
+ describe "An array trigger" do
27
+ before do
28
+ @extendee = mock("extendee")
29
+ @trigger = Newflow::Trigger.new([:make_pizza, :make_cake])
30
+ end
31
+
32
+ it "should run the triggers on the workflow" do
33
+ @extendee.should_receive(:make_pizza)
34
+ @extendee.should_receive(:make_cake)
35
+ @trigger.run!(@extendee)
36
+ end
37
+ end
38
+
39
+ describe "A lambda trigger" do
40
+ before do
41
+ @extendee = mock("extendee")
42
+ @trigger = Newflow::Trigger.new(lambda { @extendee.make_pizza })
43
+ end
44
+
45
+ it "should run the triggers on the workflow" do
46
+ @extendee.should_receive(:make_pizza)
47
+ @trigger.run!(@extendee)
48
+ end
49
+ end
50
+
@@ -0,0 +1,97 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "A workflow" do
4
+ before do
5
+ @klass = Class.new do
6
+ attr_accessor :workflow_state
7
+ end
8
+ @obj = @klass.new
9
+ end
10
+
11
+ describe "A workflow with no states" do
12
+ before do
13
+ @definition = lambda {}
14
+ end
15
+
16
+ it "should raise an error on creation" do
17
+ lambda { Newflow::Workflow.new(@obj, @definition) }.should raise_error(Newflow::InvalidStateDefinitionError)
18
+ end
19
+ end
20
+
21
+ describe "A workflow with one state" do
22
+ before do
23
+ @definition = lambda {
24
+ state :start
25
+ }
26
+ end
27
+
28
+ it "should raise an error on creation" do
29
+ lambda { Newflow::Workflow.new(@obj, @definition) }.should raise_error(Newflow::InvalidStateDefinitionError)
30
+ end
31
+ end
32
+
33
+ describe "The minimal valid workflow" do
34
+ before do
35
+ @definition = lambda {
36
+ state :start, :start => true do
37
+ transitions_to :finish, :if => :go_to_finish?
38
+ end
39
+
40
+ state :finish, :on_entry => :make_pizza, :stop => true
41
+ }
42
+
43
+ @klass.send(:define_method, :go_to_finish?) do
44
+ true
45
+ end
46
+ @klass.send(:define_method, :make_pizza) do
47
+ "yum"
48
+ end
49
+ @workflow = Newflow::Workflow.new(@obj, @definition)
50
+ end
51
+
52
+ it "should begin in start state" do
53
+ @workflow.should be_start
54
+ @obj.workflow_state.should == "start"
55
+ end
56
+
57
+ it "should trigger the on_entry when going to finish" do
58
+ @obj.should_receive :make_pizza
59
+ @workflow.transition!
60
+ end
61
+
62
+ it "should be able to transition to the finish state" do
63
+ state = @workflow.would_transition_to
64
+ state.should == :finish
65
+ @workflow.should be_start
66
+ end
67
+
68
+ it "should stop in the finish state" do
69
+ @workflow.transition!
70
+ @workflow.should be_finish
71
+ @obj.workflow_state.should == "finish"
72
+ end
73
+ end
74
+
75
+ describe "A workflow with an invalid object" do
76
+ before do
77
+ @definition = lambda {
78
+ state :start, :start => true do
79
+ transitions_to :finish, :if => :go_to_finish?
80
+ end
81
+
82
+ state :finish, :stop => true
83
+ }
84
+
85
+ @klass.send(:define_method, :go_to_finish?) do
86
+ true
87
+ end
88
+ @obj.workflow_state = "invalid"
89
+ end
90
+
91
+ it "should raise an error on instantiation" do
92
+ lambda { Newflow::Workflow.new(@obj, @definition) }.should raise_error(Newflow::InvalidWorkflowStateError)
93
+ end
94
+ end
95
+ end
96
+
97
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'ruby-debug'
4
+ require File.dirname(__FILE__) + "/../lib/newflow"
5
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: newflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Trotter Cashion
8
+ - Kyle Burton
9
+ - Aaron Feng
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2009-10-02 00:00:00 -04:00
15
+ default_executable:
16
+ dependencies: []
17
+
18
+ description: |
19
+ Newflow provides a way to add workflows to existing objects. It uses
20
+ a simple dsl to add guards and triggers to states and their transitions.
21
+
22
+ email: cashion@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - lib/newflow/state.rb
31
+ - lib/newflow/transition.rb
32
+ - lib/newflow/trigger.rb
33
+ - lib/newflow/workflow.rb
34
+ - lib/newflow.rb
35
+ - spec/newflow_spec.rb
36
+ - spec/newflow_state_spec.rb
37
+ - spec/newflow_transition_spec.rb
38
+ - spec/newflow_trigger_spec.rb
39
+ - spec/newflow_workflow_spec.rb
40
+ - spec/spec_helper.rb
41
+ has_rdoc: true
42
+ homepage: http://trottercashion.com
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Add workflows (state transitions) to objects.
69
+ test_files: []
70
+