stateflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Ryan Oberholzer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ LICENCE
2
+ Manifest
3
+ README.rdoc
4
+ Rakefile
5
+ examples/test.rb
6
+ init.rb
7
+ lib/stateflow.rb
8
+ lib/stateflow/event.rb
9
+ lib/stateflow/machine.rb
10
+ lib/stateflow/state.rb
11
+ lib/stateflow/transition.rb
@@ -0,0 +1,21 @@
1
+ = Stateflow
2
+
3
+ == TODO
4
+
5
+ * Persistence layers
6
+ * Tests
7
+
8
+ This is the basics of the gem. THIS IS NOT PRODUCTION READY UNTIL TESTS ARE DONE. Please check out the examples directory for usage until this readme gets fleshed out. Feel free to fork and modify as you please.
9
+
10
+ == Note on Patches/Pull Requests
11
+
12
+ * Fork the project.
13
+ * Make your feature addition or bug fix.
14
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
15
+ * Commit, do not mess with Rakefile, version, or history.
16
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
17
+ * Send me a pull request. Bonus points for topic branches.
18
+
19
+ == Copyright
20
+
21
+ Copyright (c) 2010 Ryan Oberholzer. See LICENSE for details.
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('stateflow', '0.0.1') do |p|
6
+ p.description = "State machine that allows dynamic transitions for business workflows"
7
+ p.url = "http://github.com/ryanza/stateflow"
8
+ p.author = "Ryan Oberholzer"
9
+ p.email = "ryan@platform45.com"
10
+ p.ignore_pattern = ["tmp/*", "script/*"]
11
+ p.development_dependencies = []
12
+ end
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'stateflow'
3
+
4
+ class Test
5
+ include Stateflow
6
+
7
+ stateflow do
8
+
9
+ initial :love
10
+
11
+ state :love do
12
+ enter lambda { |t| p "Entering love" }
13
+ exit :exit_love
14
+ end
15
+
16
+ state :hate do
17
+ enter lambda { |t| p "Entering hate" }
18
+ exit lambda { |t| p "Exiting hate" }
19
+ end
20
+
21
+ state :mixed do
22
+ enter lambda { |t| p "Entering mixed" }
23
+ exit lambda { |t| p "Exiting mixed" }
24
+ end
25
+
26
+ event :b do
27
+ transitions :from => :love, :to => :hate, :if => :no_ice_cream
28
+ transitions :from => :hate, :to => :love
29
+ end
30
+
31
+ event :a do
32
+ transitions :from => :love, :to => [:hate, :mixed], :decide => :likes_ice_cream?
33
+ transitions :from => [:hate, :mixed], :to => :love
34
+ end
35
+ end
36
+
37
+ def likes_ice_cream?
38
+ rand(10) > 5 ? :mixed : :hate
39
+ end
40
+
41
+ def exit_love
42
+ p "Exiting love"
43
+ end
44
+
45
+ def no_ice_cream
46
+ rand(4) > 2 ? true : false
47
+ end
48
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'stateflow'
@@ -0,0 +1,61 @@
1
+ module Stateflow
2
+ def self.included(base)
3
+ base.send :include, InstanceMethods
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ def self.persistence
8
+ @@persistence ||= "active_record"
9
+ end
10
+
11
+ def self.persistence=(persistence)
12
+ @@persistence = persistence
13
+ end
14
+
15
+ module ClassMethods
16
+ attr_reader :machine
17
+
18
+ def stateflow(&block)
19
+ @machine = Stateflow::Machine.new(&block)
20
+
21
+ @machine.states.values.each do |state|
22
+ state_name = state.name
23
+ define_method "#{state_name}?" do
24
+ state_name == current_state.name
25
+ end
26
+ end
27
+
28
+ @machine.events.keys.each do |key|
29
+ define_method "#{key}!" do
30
+ fire_event(key)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module InstanceMethods
37
+ def current_state
38
+ @current_state ||= machine.initial_state
39
+ end
40
+
41
+ def current_state=(new_state)
42
+ @current_state = new_state
43
+ end
44
+
45
+ def machine
46
+ self.class.machine
47
+ end
48
+
49
+ private
50
+ def fire_event(event)
51
+ event = machine.events[event.to_sym]
52
+ raise Exception.new("No event matches #{event}") if event.nil?
53
+ event.fire(current_state, self)
54
+ end
55
+ end
56
+
57
+ autoload :Machine, 'stateflow/machine'
58
+ autoload :State, 'stateflow/state'
59
+ autoload :Event, 'stateflow/event'
60
+ autoload :Transition, 'stateflow/transition'
61
+ end
@@ -0,0 +1,36 @@
1
+ module Stateflow
2
+ class NoTransitionFound < Exception; end
3
+ class NoStateFound < Exception; end
4
+
5
+ class Event
6
+ attr_accessor :name, :transitions
7
+
8
+ def initialize(name, &transitions)
9
+ @name = name
10
+ @transitions = Array.new
11
+
12
+ instance_eval(&transitions)
13
+ end
14
+
15
+ def fire(current_state, klass)
16
+ transition = @transitions.select{ |t| t.from.include? current_state.name }.first
17
+ raise NoTransitionFound.new("No transition found for event #{@name}") if transition.nil?
18
+
19
+ return false unless transition.can_transition?(klass)
20
+
21
+ new_state = klass.machine.states[transition.find_to_state(klass)]
22
+ raise NoStateFound.new("Invalid state #{transition.to.to_s} for transition.") if new_state.nil?
23
+
24
+ current_state.execute_action(:exit, klass)
25
+ klass.current_state = new_state
26
+ new_state.execute_action(:enter, klass)
27
+ true
28
+ end
29
+
30
+ private
31
+ def transitions(args = {})
32
+ transition = Stateflow::Transition.new(args)
33
+ @transitions << transition
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ module Stateflow
2
+ class Machine
3
+ attr_accessor :states, :initial_state, :events
4
+
5
+ def initialize(&machine)
6
+ @states, @events = Hash.new, Hash.new
7
+ instance_eval(&machine)
8
+ end
9
+
10
+ private
11
+ def initial(name)
12
+ @initial_state_name = name
13
+ end
14
+
15
+ def state(*names, &options)
16
+ names.each do |name|
17
+ state = Stateflow::State.new(name, &options)
18
+ @initial_state = state if @states.empty? || @initial_state_name == name
19
+ @states[name.to_sym] = state
20
+ end
21
+ end
22
+
23
+ def event(name, &transitions)
24
+ event = Stateflow::Event.new(name, &transitions)
25
+ @events[name.to_sym] = event
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Stateflow
2
+ class State
3
+ attr_accessor :name, :options
4
+
5
+ def initialize(name, &options)
6
+ @name = name
7
+ @options = Hash.new
8
+
9
+ instance_eval(&options) if options
10
+ end
11
+
12
+ def enter(method = nil, &block)
13
+ @options[:enter] = method.nil? ? block : method
14
+ end
15
+
16
+ def exit(method = nil, &block)
17
+ @options[:exit] = method.nil? ? block : method
18
+ end
19
+
20
+ def execute_action(action, base)
21
+ action = @options[action.to_sym]
22
+
23
+ case action
24
+ when Symbol, String
25
+ base.send(action)
26
+ when Proc
27
+ action.call(base)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ module Stateflow
2
+ class IncorrectTransition < Exception; end
3
+
4
+ class Transition
5
+ attr_reader :from, :to, :if, :decide
6
+
7
+ def initialize(args)
8
+ @from = [args[:from]].flatten
9
+ @to = args[:to]
10
+ @if = args[:if]
11
+ @decide = args[:decide]
12
+ end
13
+
14
+ def can_transition?(base)
15
+ return true unless @if
16
+ execute_action(@if, base)
17
+ end
18
+
19
+ def find_to_state(base)
20
+ raise IncorrectTransition.new("Array of destinations and no decision") if @to.is_a?(Array) && @decide.nil?
21
+ return @to unless @to.is_a?(Array)
22
+
23
+ to = execute_action(@decide, base)
24
+
25
+ @to.include?(to) ? to : (raise NoStateFound.new("Decision did not return a state that was set in the 'to' argument"))
26
+ end
27
+
28
+ private
29
+ def execute_action(action, base)
30
+ case action
31
+ when Symbol, String
32
+ base.send(action)
33
+ when Proc
34
+ action.call(base)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{stateflow}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ryan Oberholzer"]
9
+ s.date = %q{2010-03-22}
10
+ s.description = %q{State machine that allows dynamic transitions for business workflows}
11
+ s.email = %q{ryan@platform45.com}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/machine.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb"]
13
+ s.files = ["LICENCE", "Manifest", "README.rdoc", "Rakefile", "examples/test.rb", "init.rb", "lib/stateflow.rb", "lib/stateflow/event.rb", "lib/stateflow/machine.rb", "lib/stateflow/state.rb", "lib/stateflow/transition.rb", "stateflow.gemspec"]
14
+ s.homepage = %q{http://github.com/ryanza/stateflow}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Stateflow", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{stateflow}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{State machine that allows dynamic transitions for business workflows}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stateflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Oberholzer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-22 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: State machine that allows dynamic transitions for business workflows
17
+ email: ryan@platform45.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - lib/stateflow.rb
25
+ - lib/stateflow/event.rb
26
+ - lib/stateflow/machine.rb
27
+ - lib/stateflow/state.rb
28
+ - lib/stateflow/transition.rb
29
+ files:
30
+ - LICENCE
31
+ - Manifest
32
+ - README.rdoc
33
+ - Rakefile
34
+ - examples/test.rb
35
+ - init.rb
36
+ - lib/stateflow.rb
37
+ - lib/stateflow/event.rb
38
+ - lib/stateflow/machine.rb
39
+ - lib/stateflow/state.rb
40
+ - lib/stateflow/transition.rb
41
+ - stateflow.gemspec
42
+ has_rdoc: true
43
+ homepage: http://github.com/ryanza/stateflow
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --line-numbers
49
+ - --inline-source
50
+ - --title
51
+ - Stateflow
52
+ - --main
53
+ - README.rdoc
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "1.2"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project: stateflow
71
+ rubygems_version: 1.3.5
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: State machine that allows dynamic transitions for business workflows
75
+ test_files: []
76
+