dcadenas-state_pattern 0.2.0 → 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.
- data/README.rdoc +19 -12
- data/VERSION +1 -1
- data/lib/state_pattern.rb +49 -40
- data/lib/state_pattern/invalid_transition_exception.rb +14 -0
- data/lib/state_pattern/state.rb +16 -0
- data/state_pattern.gemspec +4 -2
- data/test/state_pattern_test.rb +4 -6
- data/test/test_class_creation_helper.rb +6 -5
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -1,26 +1,21 @@
|
|
1
1
|
= state_pattern
|
2
2
|
|
3
3
|
A Ruby state pattern implementation.
|
4
|
-
The idea comes from this nice Jay Field's post http://blog.jayfields.com/2007/08/ruby-state-pattern-using-modules-and.html
|
5
4
|
|
6
5
|
require 'rubygems'
|
7
6
|
require 'state_pattern'
|
8
7
|
|
9
|
-
|
10
|
-
protected
|
11
|
-
|
8
|
+
class On < StatePattern::State
|
12
9
|
def press
|
13
10
|
transition_to(Off)
|
14
|
-
"#{button_name} is off"
|
11
|
+
"#{stateable.button_name} is off"
|
15
12
|
end
|
16
13
|
end
|
17
14
|
|
18
|
-
|
19
|
-
protected
|
20
|
-
|
15
|
+
class Off < StatePattern::State
|
21
16
|
def press
|
22
17
|
transition_to(On)
|
23
|
-
"#{button_name} is on"
|
18
|
+
"#{stateable.button_name} is on"
|
24
19
|
end
|
25
20
|
end
|
26
21
|
|
@@ -31,7 +26,7 @@ The idea comes from this nice Jay Field's post http://blog.jayfields.com/2007/08
|
|
31
26
|
valid_transitions [On, :press] => Off, [Off, :press] => On
|
32
27
|
|
33
28
|
#this method can be removed as it will be mapped automatically anyways
|
34
|
-
#but is good to leave the option to do the delegation yourself in case you want
|
29
|
+
#but it is good to leave the option to do the delegation yourself in case you want more freedom
|
35
30
|
def press
|
36
31
|
delegate_to_event(:press)
|
37
32
|
end
|
@@ -46,9 +41,21 @@ The idea comes from this nice Jay Field's post http://blog.jayfields.com/2007/08
|
|
46
41
|
puts button.press # => "Light button is off"
|
47
42
|
puts button.press # => "Light button is on"
|
48
43
|
|
49
|
-
==
|
44
|
+
== Validations
|
45
|
+
|
46
|
+
One of the few drawbacks the state pattern has is that it can get difficult to see the global picture of your state machine when dealing with complex cases.
|
47
|
+
To deal with this problem you have the option of using the valid_transitions statement to "draw" your state diagram in code. Whenever a state transition is performed, the valid_transitions hash is checked and if the transition is not valid a StatePattern::InvalidTransitionException is thrown.
|
48
|
+
|
49
|
+
Examples:
|
50
|
+
|
51
|
+
The most basic notation
|
52
|
+
valid_transitions On => Off, Off => On
|
53
|
+
|
54
|
+
With more than one target state
|
55
|
+
valid_transitions Up => [Middle, Down], Down => Middle, Middle => Up
|
50
56
|
|
51
|
-
|
57
|
+
Using event names to gain more detail
|
58
|
+
valid_transitions [Up, :switch] => [Middle, Down], [Down, :switch] => Middle, [Middle, :switch] => Up
|
52
59
|
|
53
60
|
== Copyright
|
54
61
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/lib/state_pattern.rb
CHANGED
@@ -1,34 +1,29 @@
|
|
1
|
-
require '
|
1
|
+
require 'state_pattern/state'
|
2
|
+
require 'state_pattern/invalid_transition_exception'
|
2
3
|
|
3
4
|
module StatePattern
|
4
|
-
class InvalidTransitionException < RuntimeError
|
5
|
-
attr_reader :from_module, :to_module
|
6
|
-
def initialize(from_module, to_module)
|
7
|
-
@from_module = from_module
|
8
|
-
@to_module = to_module
|
9
|
-
end
|
10
|
-
|
11
|
-
def message
|
12
|
-
"Cannot transition from #{@from_module} to #{@to_module}"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
5
|
def self.included(base)
|
17
6
|
base.instance_eval do
|
18
|
-
def
|
19
|
-
@
|
7
|
+
def state_classes
|
8
|
+
@state_classes ||= []
|
20
9
|
end
|
21
10
|
|
22
|
-
def
|
23
|
-
@
|
11
|
+
def initial_state_class
|
12
|
+
@initial_state_class
|
24
13
|
end
|
25
14
|
|
26
|
-
def
|
27
|
-
@
|
28
|
-
|
29
|
-
|
15
|
+
def set_initial_state(state_class)
|
16
|
+
@initial_state_class = state_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_states(*state_classes)
|
20
|
+
state_classes.each do |state_class|
|
21
|
+
add_state_class(state_class)
|
30
22
|
end
|
31
|
-
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_state_class(state_class)
|
26
|
+
state_classes << state_class
|
32
27
|
end
|
33
28
|
|
34
29
|
def valid_transitions(transitions_hash)
|
@@ -39,33 +34,52 @@ module StatePattern
|
|
39
34
|
@transitions_hash
|
40
35
|
end
|
41
36
|
|
42
|
-
def
|
43
|
-
state_methods.each do |
|
44
|
-
define_method
|
45
|
-
delegate_to_event(
|
37
|
+
def delegate_all_state_events
|
38
|
+
state_methods.each do |state_method|
|
39
|
+
define_method state_method do |*args|
|
40
|
+
delegate_to_event(state_method)
|
46
41
|
end
|
47
42
|
end
|
48
43
|
end
|
49
44
|
|
50
45
|
def state_methods
|
51
|
-
|
46
|
+
state_classes.map{|state_class| state_class.public_instance_methods(false)}.flatten.uniq
|
52
47
|
end
|
53
48
|
end
|
54
49
|
end
|
55
50
|
|
56
|
-
attr_accessor :
|
51
|
+
attr_accessor :current_state, :current_event, :states
|
57
52
|
def initialize(*args)
|
58
53
|
super(*args)
|
59
|
-
self.
|
54
|
+
self.states = {}
|
55
|
+
add_state_instances
|
56
|
+
set_state(self.class.initial_state_class)
|
57
|
+
self.class.delegate_all_state_events
|
60
58
|
end
|
61
59
|
|
62
|
-
def
|
63
|
-
|
60
|
+
def set_state(state_class)
|
61
|
+
add_state_instance(state_class)
|
62
|
+
self.current_state = self.states[state_class]
|
64
63
|
end
|
65
64
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
65
|
+
def add_state_instances
|
66
|
+
self.class.state_classes.map do |state_class|
|
67
|
+
add_state_instance(state_class)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def add_state_instance(state_class)
|
72
|
+
self.states[state_class] = state_class.new(self) if !self.states.has_key?(state_class) || self.states[state_class].nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
def delegate_to_event(method_name, *args)
|
76
|
+
self.current_event = method_name.to_sym
|
77
|
+
self.current_state.send(current_event, *args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def transition_to(state_class)
|
81
|
+
raise InvalidTransitionException.new(self.current_state.class, state_class, self.current_event) unless self.valid_transition?(self.current_state.class, state_class)
|
82
|
+
set_state(state_class)
|
69
83
|
end
|
70
84
|
|
71
85
|
def valid_transition?(from_module, to_module)
|
@@ -81,12 +95,7 @@ module StatePattern
|
|
81
95
|
end
|
82
96
|
|
83
97
|
def state
|
84
|
-
self.
|
85
|
-
end
|
86
|
-
|
87
|
-
def delegate_to_event(method_name, *args)
|
88
|
-
self.current_event = method_name.to_sym
|
89
|
-
self.current_state_instance.__send__(current_event, *args)
|
98
|
+
self.current_state.state
|
90
99
|
end
|
91
100
|
end
|
92
101
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module StatePattern
|
2
|
+
class InvalidTransitionException < RuntimeError
|
3
|
+
attr_reader :from_module, :to_module, :event
|
4
|
+
def initialize(from_module, to_module, event)
|
5
|
+
@from_module = from_module
|
6
|
+
@to_module = to_module
|
7
|
+
@event = event
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Event #@event cannot transition from #@from_module to #@to_module"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module StatePattern
|
2
|
+
class State
|
3
|
+
attr_reader :stateable
|
4
|
+
def initialize(stateable)
|
5
|
+
@stateable = stateable
|
6
|
+
end
|
7
|
+
|
8
|
+
def transition_to(state_class)
|
9
|
+
@stateable.transition_to(state_class)
|
10
|
+
end
|
11
|
+
|
12
|
+
def state
|
13
|
+
self.class.to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/state_pattern.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{state_pattern}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Daniel Cadenas"]
|
9
|
-
s.date = %q{2009-06-
|
9
|
+
s.date = %q{2009-06-08}
|
10
10
|
s.email = %q{dcadenas@gmail.com}
|
11
11
|
s.extra_rdoc_files = [
|
12
12
|
"LICENSE",
|
@@ -20,6 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
"Rakefile",
|
21
21
|
"VERSION",
|
22
22
|
"lib/state_pattern.rb",
|
23
|
+
"lib/state_pattern/invalid_transition_exception.rb",
|
24
|
+
"lib/state_pattern/state.rb",
|
23
25
|
"state_pattern.gemspec",
|
24
26
|
"test/state_pattern_test.rb",
|
25
27
|
"test/test_class_creation_helper.rb",
|
data/test/state_pattern_test.rb
CHANGED
@@ -1,19 +1,17 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
module Family
|
4
|
-
|
5
|
-
protected
|
4
|
+
class James < StatePattern::State
|
6
5
|
def name
|
7
6
|
transition_to(Lynn)
|
8
|
-
"James #{last_name}"
|
7
|
+
"James #{stateable.last_name}"
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
protected
|
11
|
+
class Lynn < StatePattern::State
|
14
12
|
def name
|
15
13
|
transition_to(James)
|
16
|
-
"Lynn #{last_name}"
|
14
|
+
"Lynn #{stateable.last_name}"
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module TestClassCreationHelper
|
2
|
+
#TODO: ugly
|
2
3
|
def with_test_class(main_state_module_name, options = {})
|
3
4
|
created_consts = []
|
4
5
|
transitions = options[:transitions] || {}
|
@@ -6,7 +7,7 @@ module TestClassCreationHelper
|
|
6
7
|
|
7
8
|
if options.has_key?(:states)
|
8
9
|
options[:states].each do |state_name|
|
9
|
-
created_consts <<
|
10
|
+
created_consts << create_class(state_name, StatePattern::State) do
|
10
11
|
state_methods.each do |method_name|
|
11
12
|
define_method method_name do
|
12
13
|
next_state_name = transitions[[state_name, method_name]]
|
@@ -47,12 +48,12 @@ module TestClassCreationHelper
|
|
47
48
|
|
48
49
|
private
|
49
50
|
|
50
|
-
def create_module(module_name, module_or_class = Module, &block)
|
51
|
-
new_module = module_or_class.new &block
|
51
|
+
def create_module(module_name, superklass = Object, module_or_class = Module, &block)
|
52
|
+
new_module = module_or_class.new(superklass, &block)
|
52
53
|
Object.const_set(module_name, new_module) unless Object.const_defined? module_name
|
53
54
|
end
|
54
55
|
|
55
|
-
def create_class(class_name, &block)
|
56
|
-
create_module(class_name, Class, &block)
|
56
|
+
def create_class(class_name, superklass = Object, &block)
|
57
|
+
create_module(class_name, superklass, Class, &block)
|
57
58
|
end
|
58
59
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dcadenas-state_pattern
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Cadenas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-08 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -30,6 +30,8 @@ files:
|
|
30
30
|
- Rakefile
|
31
31
|
- VERSION
|
32
32
|
- lib/state_pattern.rb
|
33
|
+
- lib/state_pattern/invalid_transition_exception.rb
|
34
|
+
- lib/state_pattern/state.rb
|
33
35
|
- state_pattern.gemspec
|
34
36
|
- test/state_pattern_test.rb
|
35
37
|
- test/test_class_creation_helper.rb
|