newflow 1.0.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.
@@ -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
+