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 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
- module On
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
- module Off
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 to do more things
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
- == Requirements
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
- facets
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.2.0
1
+ 1.0.0
data/lib/state_pattern.rb CHANGED
@@ -1,34 +1,29 @@
1
- require 'facets'
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 initial_state
19
- @initial_state
7
+ def state_classes
8
+ @state_classes ||= []
20
9
  end
21
10
 
22
- def set_initial_state(state_module)
23
- @initial_state = state_module
11
+ def initial_state_class
12
+ @initial_state_class
24
13
  end
25
14
 
26
- def add_states(*state_modules)
27
- @state_modules = state_modules
28
- @state_modules.each do |state_module|
29
- include state_module
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
- delegate_all_events
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 delegate_all_events
43
- state_methods.each do |method_name|
44
- define_method method_name do |*args|
45
- delegate_to_event(method_name, *args)
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
- @state_modules.map{|state_module| state_module.__send__(:instance_methods)}.flatten.uniq
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 :current_state_module, :current_event
51
+ attr_accessor :current_state, :current_event, :states
57
52
  def initialize(*args)
58
53
  super(*args)
59
- self.current_state_module = self.class.initial_state
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 current_state_instance
63
- as(current_state_module)
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 transition_to(state_module)
67
- raise InvalidTransitionException.new(self.current_state_module, state_module) unless self.valid_transition?(self.current_state_module, state_module)
68
- self.current_state_module = state_module
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.current_state_module.to_s
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
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{state_pattern}
5
- s.version = "0.2.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-07}
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",
@@ -1,19 +1,17 @@
1
1
  require 'test_helper'
2
2
 
3
3
  module Family
4
- module James
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
- module Lynn
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 << create_module(state_name) do
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.2.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-07 00:00:00 -07:00
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