state_design_pattern 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MzlmYTVlZmM1OTNmMjk4ODU4NTE2MmFlMzk1NDFhNTI1MjBhOTUzMg==
5
+ data.tar.gz: !binary |-
6
+ NGFmMWNkZTZiZjk4OGExOTMwYjlmMjQ2MmVhZmM0NGY5MjgwMGFlZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTlmZTQ2MjAwZmU0YTUxYTBlMWEwZGQ4NmU2ZjBiNzdiYmUzNzM1YmI1ZjFj
10
+ OGJlMWY5YjRkMGE3ZTJhZjMzZjk3NDUzZjNlOGU1ZjVlYmQ4MDZmNzAxYWU4
11
+ YTFhNGRkYjlkYTEwMTJmNzIxNDBkZGNkMjBlNDU5YWRjZGJiZDM=
12
+ data.tar.gz: !binary |-
13
+ MGM0OGNmYWQxNGUyNzUxMjk5MDI5NWEyYzkzNTRiMDNlZDljYjU2MTRiZWFh
14
+ OGRhMTNjN2ZhYzU4MTZhMmQ2MTM3YmEzYmMxNDBjNzY5NTU5YjhmZTc2NGQ4
15
+ YjllMTFlMDE3ODcwOWEwZTRjNjg2OGVlYjY5NjkwNmE1NWRhY2E=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .ruby-gemset
3
+ .ruby-version
4
+ /coverage
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ state_design_pattern (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coveralls (0.7.0)
10
+ multi_json (~> 1.3)
11
+ rest-client
12
+ simplecov (>= 0.7)
13
+ term-ansicolor
14
+ thor
15
+ docile (1.1.3)
16
+ mime-types (2.2)
17
+ minitest (5.3.4)
18
+ multi_json (1.10.1)
19
+ rake (10.3.2)
20
+ rest-client (1.6.7)
21
+ mime-types (>= 1.16)
22
+ simplecov (0.8.2)
23
+ docile (~> 1.1.0)
24
+ multi_json
25
+ simplecov-html (~> 0.8.0)
26
+ simplecov-html (0.8.0)
27
+ term-ansicolor (1.3.0)
28
+ tins (~> 1.0)
29
+ thor (0.19.1)
30
+ tins (1.3.0)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ coveralls (~> 0.7)
37
+ minitest (~> 5.3)
38
+ rake (~> 10.3)
39
+ state_design_pattern!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dwayne R. Crooks
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # state_design_pattern
2
+
3
+ [![Build Status](https://travis-ci.org/dwayne/state_design_pattern.svg?branch=master)](https://travis-ci.org/dwayne/state_design_pattern)
4
+ [![Coverage Status](https://coveralls.io/repos/dwayne/state_design_pattern/badge.png)](https://coveralls.io/r/dwayne/state_design_pattern)
5
+ [![Code Climate](https://codeclimate.com/github/dwayne/state_design_pattern.png)](https://codeclimate.com/github/dwayne/state_design_pattern)
6
+
7
+ An implementation of the
8
+ [State Design Pattern](http://sourcemaking.com/design_patterns/state) in Ruby.
9
+ The State Design Pattern allows an object to alter its behavior when its
10
+ internal state changes.
11
+
12
+ ## Example
13
+
14
+ Here's an example of the state design pattern in use. A light bulb is modeled.
15
+ It can be turned on and off. Every time it is turned on it uses 25% of its
16
+ energy. When it runs out of energy it cannot be turned on again.
17
+
18
+ ```ruby
19
+ class LightBulb < StateDesignPattern::StateMachine
20
+ def start_state
21
+ Off
22
+ end
23
+
24
+ def initial_context
25
+ Struct
26
+ .new(:energy, :times_on, :times_off)
27
+ .new(100, 0, 0)
28
+ end
29
+ end
30
+
31
+ class Switch < StateDesignPattern::BaseState
32
+ def_actions :turn_on, :turn_off
33
+ end
34
+
35
+ class On < Switch
36
+
37
+ def turn_on
38
+ state_machine.send_event(:already_turned_on)
39
+ end
40
+
41
+ def turn_off
42
+ state_machine.times_off += 1
43
+ state_machine.transition_to_state_and_send_event(Off, :turned_off)
44
+ end
45
+ end
46
+
47
+ class Off < Switch
48
+
49
+ def turn_on
50
+ if state_machine.energy >= 25
51
+ state_machine.energy -= 25
52
+ state_machine.times_on += 1
53
+ state_machine.transition_to_state_and_send_event(On, :turned_on)
54
+ else
55
+ state_machine.send_event(:out_of_energy)
56
+ end
57
+ end
58
+
59
+ def turn_off
60
+ state_machine.send_event(:already_turned_off)
61
+ end
62
+ end
63
+
64
+ class LightBulbObserver
65
+
66
+ def initialize
67
+ @events = []
68
+ end
69
+
70
+ def last_event
71
+ @events.last
72
+ end
73
+
74
+ def update(event)
75
+ @events << event
76
+ end
77
+ end
78
+
79
+ light_bulb = LightBulb.new
80
+ light_bulb_observer = LightBulbObserver.new
81
+
82
+ light_bulb.add_observer(light_bulb_observer)
83
+
84
+ light_bulb.current_state #=> Off
85
+
86
+ light_bulb.energy #=> 100
87
+ light_bulb.times_on #=> 0
88
+ light_bulb.times_off #=> 0
89
+
90
+ light_bulb.turn_on
91
+ light_bulb_observer.last_event[:name] #=> :turned_on
92
+
93
+ light_bulb.current_state #=> On
94
+
95
+ light_bulb.energy #=> 75
96
+ light_bulb.times_on #=> 1
97
+ light_bulb.times_off #=> 0
98
+
99
+ light_bulb.turn_off
100
+ light_bulb.turn_on
101
+
102
+ light_bulb.turn_off
103
+ light_bulb.turn_on
104
+
105
+ light_bulb.turn_off
106
+ light_bulb.turn_on
107
+
108
+ light_bulb.energy #=> 0
109
+ light_bulb.times_on #=> 4
110
+ light_bulb.times_off #=> 3
111
+
112
+ light_bulb.turn_off
113
+ light_bulb.turn_on
114
+
115
+ light_bulb_observer.last_event[:name] #=> :out_of_energy
116
+ ```
117
+
118
+ ## Testing
119
+
120
+ You can run:
121
+
122
+ - All specs: `bundle exec rake`, or
123
+ - A specific spec: `bundle exec ruby -Ilib -Ispec spec/path_to_spec_file.rb`
124
+
125
+ ## Contributing
126
+
127
+ If you'd like to contribute a feature or bugfix: Thanks! To make sure your
128
+ fix/feature has a high chance of being included, please read the following
129
+ guidelines:
130
+
131
+ 1. Post a [pull request](https://github.com/dwayne/state_design_pattern/compare/).
132
+ 2. Make sure there are tests! I will not accept any patch that is not tested.
133
+ It's a rare time when explicit tests aren't needed. If you have questions about
134
+ writing tests for state_pattern, please open a
135
+ [GitHub issue](https://github.com/dwayne/state_design_pattern/issues/new).
136
+
137
+ ## License
138
+
139
+ state_design_pattern is Copyright © 2014 Dwayne R. Crooks. It is free software,
140
+ and may be redistributed under the terms specified in the MIT-LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = 'spec/**/*_spec.rb'
6
+ t.libs.push 'spec'
7
+ end
8
+
9
+ task default: :test
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # See http://en.wikipedia.org/wiki/State_pattern#Java
4
+
5
+ require 'state_design_pattern'
6
+
7
+ class CaseChanger < StateDesignPattern::StateMachine
8
+ def start_state
9
+ Lowercase
10
+ end
11
+ end
12
+
13
+ class CaseChangerState < StateDesignPattern::BaseState
14
+ def_actions :write
15
+ end
16
+
17
+ class Lowercase < CaseChangerState
18
+ def write(name)
19
+ puts name.downcase
20
+ state_machine.transition_to_state(MultipleUppercase)
21
+ end
22
+ end
23
+
24
+ class MultipleUppercase < CaseChangerState
25
+ def write(name)
26
+ puts name.upcase
27
+ state_machine.transition_to_state(Lowercase) if incr_count > 1
28
+ end
29
+
30
+ private
31
+ def incr_count
32
+ @count = 0 unless @count
33
+ @count += 1
34
+ end
35
+ end
36
+
37
+ def main
38
+ changer = CaseChanger.new
39
+
40
+ ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'].each do |day|
41
+ changer.write(day)
42
+ end
43
+ end
44
+
45
+ main
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # See http://sourcemaking.com/design_patterns/state/java/1
4
+
5
+ require 'state_design_pattern'
6
+
7
+ class CeilingFanPullChain < StateDesignPattern::StateMachine
8
+ def start_state
9
+ Off
10
+ end
11
+ end
12
+
13
+ class CeilingFanState < StateDesignPattern::BaseState
14
+ def_actions :pull
15
+ end
16
+
17
+ class Off < CeilingFanState
18
+ def pull
19
+ puts "-> low speed"
20
+ state_machine.transition_to_state(Low)
21
+ end
22
+ end
23
+
24
+ class Low < CeilingFanState
25
+ def pull
26
+ puts "-> medium speed"
27
+ state_machine.transition_to_state(Medium)
28
+ end
29
+ end
30
+
31
+ class Medium < CeilingFanState
32
+ def pull
33
+ puts "-> high speed"
34
+ state_machine.transition_to_state(High)
35
+ end
36
+ end
37
+
38
+ class High < CeilingFanState
39
+ def pull
40
+ puts "-> turning off"
41
+ state_machine.transition_to_state(Off)
42
+ end
43
+ end
44
+
45
+ def main
46
+ chain = CeilingFanPullChain.new
47
+
48
+ loop do
49
+ print "Press "
50
+ gets.chomp
51
+ chain.pull
52
+ end
53
+ end
54
+
55
+ main
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # See http://en.wikipedia.org/wiki/State_pattern#Pseudocode
4
+
5
+ require 'state_design_pattern'
6
+
7
+ class Cursor < StateDesignPattern::StateMachine
8
+ def start_state
9
+ PenTool
10
+ end
11
+
12
+ def use_pen_tool
13
+ transition_to_state(PenTool)
14
+ end
15
+
16
+ def use_selection_tool
17
+ transition_to_state(SelectionTool)
18
+ end
19
+ end
20
+
21
+ class AbstractTool < StateDesignPattern::BaseState
22
+ def_actions :move_to, :mouse_down, :mouse_up
23
+ end
24
+
25
+ class PenTool < AbstractTool
26
+ def move_to(point)
27
+ puts "pen: moving to #{point}"
28
+ end
29
+
30
+ def mouse_down(point)
31
+ puts "pen: mouse down at #{point}"
32
+ end
33
+
34
+ def mouse_up(point)
35
+ puts "pen: mouse up at #{point}"
36
+ end
37
+ end
38
+
39
+ class SelectionTool < AbstractTool
40
+ def move_to(point)
41
+ if @mouse_button == :down
42
+ puts "selection: current selected rectangle is between #{@selection_start} and #{point}"
43
+ else
44
+ puts "selection: moving to #{point}"
45
+ end
46
+ end
47
+
48
+ def mouse_down(point)
49
+ @mouse_button = :down
50
+ @selection_start = point
51
+
52
+ puts "selection: mouse down at #{point}"
53
+ end
54
+
55
+ def mouse_up(point)
56
+ @mouse_button = :up
57
+
58
+ puts "selection: mouse up at #{point}"
59
+ end
60
+ end
61
+
62
+ class Point < Struct.new(:x, :y)
63
+ end
64
+
65
+ def main
66
+ cursor = Cursor.new
67
+
68
+ # Draw a line from (1, 1) to (5, 5)
69
+ cursor.use_pen_tool
70
+ cursor.move_to(Point.new(1, 1))
71
+ cursor.mouse_down(Point.new(1, 1))
72
+ cursor.move_to(Point.new(5, 5))
73
+ cursor.mouse_up(Point.new(5, 5))
74
+
75
+ # Select part of the line
76
+ cursor.use_selection_tool
77
+ cursor.move_to(Point.new(2, 2))
78
+ cursor.mouse_down(Point.new(2, 2))
79
+ cursor.move_to(Point.new(4, 4))
80
+ cursor.mouse_up(Point.new(4, 4))
81
+ end
82
+
83
+ main
data/examples/fsm.rb ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # See http://sourcemaking.com/design_patterns/state/java/6
4
+
5
+ require 'state_design_pattern'
6
+
7
+ class FSM < StateDesignPattern::StateMachine
8
+ def start_state
9
+ A
10
+ end
11
+ end
12
+
13
+ class FSMState < StateDesignPattern::BaseState
14
+ def_actions :on, :off, :ack
15
+ end
16
+
17
+ class A < FSMState
18
+ def on
19
+ puts "A + on = C"
20
+ state_machine.transition_to_state(C)
21
+ end
22
+
23
+ def off
24
+ puts "A + off = B"
25
+ state_machine.transition_to_state(B)
26
+ end
27
+
28
+ def ack
29
+ puts "A + ack = A"
30
+ state_machine.transition_to_state(A)
31
+ end
32
+ end
33
+
34
+ class B < FSMState
35
+ def on
36
+ puts "B + on = A"
37
+ state_machine.transition_to_state(A)
38
+ end
39
+
40
+ def off
41
+ puts "B + off = C"
42
+ state_machine.transition_to_state(C)
43
+ end
44
+ end
45
+
46
+ class C < FSMState
47
+ def on
48
+ puts "C + on = B"
49
+ state_machine.transition_to_state(B)
50
+ end
51
+ end
52
+
53
+ def main
54
+ fsm = FSM.new
55
+
56
+ [2, 1, 2, 1, 0, 2, 0, 0].each do |msg|
57
+ begin
58
+ fsm.on if msg == 0
59
+ fsm.off if msg == 1
60
+ fsm.ack if msg == 2
61
+ rescue StateDesignPattern::IllegalStateException
62
+ puts "error"
63
+ end
64
+ end
65
+ end
66
+
67
+ main
@@ -0,0 +1,29 @@
1
+ module StateDesignPattern
2
+
3
+ class BaseState
4
+
5
+ attr_reader :state_machine
6
+
7
+ def initialize(state_machine)
8
+ @state_machine = state_machine
9
+ end
10
+
11
+ def self.def_actions(*actions)
12
+ define_method(:actions) do
13
+ actions
14
+ end
15
+
16
+ actions.each do |action|
17
+ def_action(action)
18
+ end
19
+ end
20
+
21
+ def self.def_action(action_name)
22
+ define_method(action_name) do |*args|
23
+ raise IllegalStateException
24
+ end
25
+ end
26
+ end
27
+
28
+ class IllegalStateException < StandardError; end
29
+ end
@@ -0,0 +1,74 @@
1
+ require 'forwardable'
2
+ require 'observer'
3
+
4
+ module StateDesignPattern
5
+
6
+ class StateMachine
7
+ extend Forwardable
8
+ include Observable
9
+
10
+ def initialize
11
+ initialize_context
12
+ transition_to_start_state
13
+ setup_delegation
14
+ end
15
+
16
+ def initial_context
17
+ end
18
+
19
+ def start_state
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def current_state
24
+ @state.class
25
+ end
26
+
27
+ def transition_to_state_and_send_event(state_class, name, message = {})
28
+ transition_to_state(state_class)
29
+ send_event(name, message)
30
+ end
31
+
32
+ def transition_to_state(state_class)
33
+ @state = state_class.new(self)
34
+ end
35
+
36
+ def send_event(name, message = {})
37
+ event = { name: name, source: self }.merge(message)
38
+
39
+ changed
40
+ notify_observers(event)
41
+
42
+ self
43
+ end
44
+
45
+ private
46
+
47
+ def initialize_context
48
+ @ctx = initial_context
49
+ end
50
+
51
+ def transition_to_start_state
52
+ transition_to_state(start_state)
53
+ end
54
+
55
+ def setup_delegation
56
+ setup_context_delegation
57
+ setup_action_delegation
58
+ end
59
+
60
+ def setup_context_delegation
61
+ if @ctx
62
+ methods = @ctx.members.map { |reader| [reader, "#{reader}=".to_sym] }.flatten
63
+ self.class.def_delegators :@ctx, *methods
64
+ end
65
+ end
66
+
67
+ def setup_action_delegation
68
+ if @state.respond_to?(:actions)
69
+ methods = @state.actions
70
+ self.class.def_delegators :@state, *methods
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ module StateDesignPattern
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,2 @@
1
+ require 'state_design_pattern/state_machine'
2
+ require 'state_design_pattern/base_state'
@@ -0,0 +1,7 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'minitest/autorun'
5
+ require 'minitest/spec'
6
+
7
+ require 'state_design_pattern'
@@ -0,0 +1,314 @@
1
+ require 'spec_helper'
2
+
3
+ describe "the operation of the state design pattern with an example" do
4
+
5
+ TIMESTAMP = Time.now
6
+
7
+ class LightBulb < StateDesignPattern::StateMachine
8
+ def start_state
9
+ Off
10
+ end
11
+
12
+ def initial_context
13
+ Struct
14
+ .new(:energy, :times_on, :times_off)
15
+ .new(100, 0, 0)
16
+ end
17
+ end
18
+
19
+ class Switch < StateDesignPattern::BaseState
20
+ def_actions :turn_on, :turn_off, :toggle
21
+ end
22
+
23
+ class On < Switch
24
+
25
+ def turn_on
26
+ state_machine.send_event(:already_turned_on, when: TIMESTAMP)
27
+ end
28
+
29
+ def turn_off
30
+ state_machine.times_off += 1
31
+ state_machine.transition_to_state_and_send_event(Off, :turned_off, when: TIMESTAMP)
32
+ end
33
+ end
34
+
35
+ class Off < Switch
36
+
37
+ def turn_on
38
+ if state_machine.energy >= 25
39
+ state_machine.energy -= 25
40
+ state_machine.times_on += 1
41
+ state_machine.transition_to_state_and_send_event(On, :turned_on, when: TIMESTAMP)
42
+ else
43
+ state_machine.send_event(:out_of_energy, when: TIMESTAMP)
44
+ end
45
+ end
46
+
47
+ def turn_off
48
+ state_machine.send_event(:already_turned_off, when: TIMESTAMP)
49
+ end
50
+ end
51
+
52
+ class LightBulbObserver
53
+
54
+ def initialize
55
+ @events = []
56
+ end
57
+
58
+ def last_event
59
+ @events.last
60
+ end
61
+
62
+ def update(event)
63
+ @events << event
64
+ end
65
+ end
66
+
67
+ describe LightBulb do
68
+
69
+ let(:light_bulb) { LightBulb.new }
70
+ let(:light_bulb_observer) { LightBulbObserver.new }
71
+
72
+ before do
73
+ light_bulb.add_observer(light_bulb_observer)
74
+ end
75
+
76
+ describe "the initial state of the bulb" do
77
+ it "is off" do
78
+ light_bulb.current_state.must_equal Off
79
+ end
80
+
81
+ it "is full of energy" do
82
+ light_bulb.energy.must_equal 100
83
+ end
84
+
85
+ it "has been turned on 0 times" do
86
+ light_bulb.times_on.must_equal 0
87
+ end
88
+
89
+ it "has been turned off 0 times" do
90
+ light_bulb.times_off.must_equal 0
91
+ end
92
+ end
93
+
94
+ describe "the actions that can be performed on the light bulb" do
95
+
96
+ it "can be turned on" do
97
+ light_bulb.must_respond_to :turn_on
98
+ end
99
+
100
+ it "can be turned off" do
101
+ light_bulb.must_respond_to :turn_off
102
+ end
103
+
104
+ it "can be toggled" do
105
+ light_bulb.must_respond_to :toggle
106
+ end
107
+ end
108
+
109
+ describe "how the actions work" do
110
+
111
+ describe "in the off state" do
112
+
113
+ describe "#turn_on" do
114
+
115
+ before do
116
+ light_bulb.turn_on
117
+ end
118
+
119
+ it "turns on the bulb" do
120
+ light_bulb.current_state.must_equal On
121
+ end
122
+
123
+ it "causes the bulb to use 25% of its energy" do
124
+ light_bulb.energy.must_equal 75
125
+ end
126
+
127
+ it "has been turned on 1 time" do
128
+ light_bulb.times_on.must_equal 1
129
+ end
130
+
131
+ it "has been turned off 0 times" do
132
+ light_bulb.times_off.must_equal 0
133
+ end
134
+
135
+ it "sends a :turned_on event" do
136
+ event = light_bulb_observer.last_event
137
+
138
+ event[:name].must_equal :turned_on
139
+ event[:source].must_equal light_bulb
140
+ event[:when].must_equal TIMESTAMP
141
+ end
142
+ end
143
+
144
+ describe "#turn_off" do
145
+
146
+ before do
147
+ light_bulb.turn_off
148
+ end
149
+
150
+ it "keeps the bulb turned off" do
151
+ light_bulb.current_state.must_equal Off
152
+ end
153
+
154
+ it "doesn't use any energy" do
155
+ light_bulb.energy.must_equal 100
156
+ end
157
+
158
+ it "has been turned on 0 times" do
159
+ light_bulb.times_on.must_equal 0
160
+ end
161
+
162
+ it "has been turned off 0 times" do
163
+ light_bulb.times_off.must_equal 0
164
+ end
165
+
166
+ it "sends an :already_turned_off event" do
167
+ event = light_bulb_observer.last_event
168
+
169
+ event[:name].must_equal :already_turned_off
170
+ event[:source].must_equal light_bulb
171
+ event[:when].must_equal TIMESTAMP
172
+ end
173
+ end
174
+
175
+ describe "#toggle" do
176
+
177
+ it "doesn't work" do
178
+ proc { light_bulb.toggle }.must_raise StateDesignPattern::IllegalStateException
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "in the on state" do
184
+
185
+ before do
186
+ light_bulb.turn_on
187
+ end
188
+
189
+ describe "#turn_on" do
190
+
191
+ before do
192
+ light_bulb.turn_on
193
+ end
194
+
195
+ it "keeps the bulb turned on" do
196
+ light_bulb.current_state.must_equal On
197
+ end
198
+
199
+ it "doesn't use any energy" do
200
+ light_bulb.energy.must_equal 75
201
+ end
202
+
203
+ it "has been turned on 1 time" do
204
+ light_bulb.times_on.must_equal 1
205
+ end
206
+
207
+ it "has been turned off 0 times" do
208
+ light_bulb.times_off.must_equal 0
209
+ end
210
+
211
+ it "sends an :already_turned_on event" do
212
+ event = light_bulb_observer.last_event
213
+
214
+ event[:name].must_equal :already_turned_on
215
+ event[:source].must_equal light_bulb
216
+ event[:when].must_equal TIMESTAMP
217
+ end
218
+ end
219
+
220
+ describe "#turn_off" do
221
+
222
+ before do
223
+ light_bulb.turn_off
224
+ end
225
+
226
+ it "turns off the bulb" do
227
+ light_bulb.current_state.must_equal Off
228
+ end
229
+
230
+ it "doesn't use any energy" do
231
+ light_bulb.energy.must_equal 75
232
+ end
233
+
234
+ it "has been turned on 1 time" do
235
+ light_bulb.times_on.must_equal 1
236
+ end
237
+
238
+ it "has been turned off 1 time" do
239
+ light_bulb.times_off.must_equal 1
240
+ end
241
+
242
+ it "sends a :turned_off event" do
243
+ event = light_bulb_observer.last_event
244
+
245
+ event[:name].must_equal :turned_off
246
+ event[:source].must_equal light_bulb
247
+ event[:when].must_equal TIMESTAMP
248
+ end
249
+ end
250
+
251
+ describe "#toggle" do
252
+
253
+ it "doesn't work" do
254
+ proc { light_bulb.toggle }.must_raise StateDesignPattern::IllegalStateException
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ describe "how the bulb works when it is out of energy" do
261
+
262
+ before do
263
+ # at 100%
264
+ light_bulb.turn_on
265
+ light_bulb.turn_off
266
+ # at 75%
267
+ light_bulb.turn_on
268
+ light_bulb.turn_off
269
+ # at 50%
270
+ light_bulb.turn_on
271
+ light_bulb.turn_off
272
+ # at 25%
273
+ light_bulb.turn_on
274
+ light_bulb.turn_off
275
+ # at 0%
276
+ end
277
+
278
+ it "is off" do
279
+ light_bulb.current_state.must_equal Off
280
+ end
281
+
282
+ it "is out of energy" do
283
+ light_bulb.energy.must_equal 0
284
+ end
285
+
286
+ it "has been turned on 4 times" do
287
+ light_bulb.times_on.must_equal 4
288
+ end
289
+
290
+ it "has been turned off 4 times" do
291
+ light_bulb.times_off.must_equal 4
292
+ end
293
+
294
+ describe "when you try to turn it on" do
295
+
296
+ before do
297
+ light_bulb.turn_on
298
+ end
299
+
300
+ it "remains off" do
301
+ light_bulb.current_state.must_equal Off
302
+ end
303
+
304
+ it "sends an :out_of_energy energy event" do
305
+ event = light_bulb_observer.last_event
306
+
307
+ event[:name].must_equal :out_of_energy
308
+ event[:source].must_equal light_bulb
309
+ event[:when].must_equal TIMESTAMP
310
+ end
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'state_design_pattern/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'state_design_pattern'
8
+ spec.version = StateDesignPattern::VERSION
9
+ spec.author = 'Dwayne R. Crooks'
10
+ spec.email = ['me@dwaynecrooks.com']
11
+ spec.summary = %q{An implementation of the State Design Pattern in Ruby.}
12
+ spec.description = %q{An implementation of the State Design Pattern in Ruby. The State Design Pattern allows an object to alter its behavior when its
13
+ internal state changes.}
14
+ spec.homepage = 'https://github.com/dwayne/state_design_pattern'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.required_ruby_version = '>= 1.9.3'
23
+
24
+ spec.add_development_dependency 'rake', '~> 10.3'
25
+ spec.add_development_dependency 'minitest', '~> 5.3'
26
+ spec.add_development_dependency 'coveralls', '~> 0.7'
27
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: state_design_pattern
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dwayne R. Crooks
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '10.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '10.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '5.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '5.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.7'
55
+ description: ! "An implementation of the State Design Pattern in Ruby. The State Design
56
+ Pattern allows an object to alter its behavior when its\n internal state changes."
57
+ email:
58
+ - me@dwaynecrooks.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - .gitignore
64
+ - .travis.yml
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - examples/case_changer.rb
71
+ - examples/ceiling_fan.rb
72
+ - examples/cursor.rb
73
+ - examples/fsm.rb
74
+ - lib/state_design_pattern.rb
75
+ - lib/state_design_pattern/base_state.rb
76
+ - lib/state_design_pattern/state_machine.rb
77
+ - lib/state_design_pattern/version.rb
78
+ - spec/spec_helper.rb
79
+ - spec/state_design_pattern_spec.rb
80
+ - state_design_pattern.gemspec
81
+ homepage: https://github.com/dwayne/state_design_pattern
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.9.3
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.2.2
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: An implementation of the State Design Pattern in Ruby.
105
+ test_files:
106
+ - spec/spec_helper.rb
107
+ - spec/state_design_pattern_spec.rb