-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathstate.rb
108 lines (88 loc) · 2.77 KB
/
state.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# Allow an object to alter its behavior when its internal state changes.
# The object will appear to change its class.
# Each call to state defines a new subclass of Connection that is stored in a
# hash. Then, a call to transition_to instantiates one of these subclasses and
# sets it to the be the active state. Method calls to Connection are delegated
# to the active state object via method_missing
module StatePattern
class UnknownStateException < RuntimeError
end
def self.included(base)
base.extend StatePattern::ClassMethods
end
module ClassMethods
attr_reader :state_classes
def state(state_name, &block)
@state_classes ||= {}
new_klass = Class.new(self, &block)
new_klass.class_eval do
alias_method :__old_init, :initialize
def initialize(context, *args, &block)
@context = context
__old_init(*args, &block)
end
end
@state_classes[state_name] = new_klass
end
end
attr_accessor :current_state, :current_state_obj
def transition_to(state_name, *args, &block)
new_context = @context || self
klass = new_context.class.state_classes[state_name]
if klass
new_context.current_state = state_name
new_context.current_state_obj = klass.new(new_context, *args, &block)
else
raise UnknownStateException, "tried to transition to unknown state,#{state_name}"
end
end
def method_missing(method, *args, &block)
transition_to :initial unless @current_state_obj
if @current_state_obj
@current_state_obj.send(method, *args, &block)
else
super
end
end
end
class Connection
include StatePattern
# you always need a state named initial
state :initial do
def connect
# move to state :connected. all other args to transition_to# are passed
# to the new state's constructor transition_to:connected, "hello from
# initial state"
puts 'connected'
end
def disconnect
puts 'not connected yet'
end
end
state :connected do
def initialize(msg)
puts "initialize got msg:#{msg}"
end
def connect
puts 'already connected'
end
def disconnect
puts 'disconnecting'
transition_to :initial
end
end
def reset
# you can also change the state from outside of the state objects
# transition_to:initial
puts 'resetting outside a state'
end
end
# Usage
c = Connection.new
c.disconnect # => not connected yet
c.connect # => connected, initialize got msg: hello from initial state
c.connect # => already connected
c.disconnect # => disconnecting
c.connect # => connected, initialize got msg: hello from initial state
c.reset # => reseting outside a state
c.disconnect # => not connected yet