state_pattern 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,276 @@
1
+ [![Build Status](https://secure.travis-ci.org/dcadenas/state_pattern.png?branch=master)](http://travis-ci.org/dcadenas/state_pattern)
2
+
3
+ # state_pattern
4
+
5
+ A Ruby state pattern implementation.
6
+
7
+ This library intentionally follows the classic state pattern implementation (no mixins, classical delegation to simple state classes, etc.) believing that it increases flexibility (internal DSL constraints vs plain object oriented Ruby power), simplicity and clarity.
8
+
9
+ The gem is ready for Rails active record integration (see below and the examples folder).
10
+
11
+ ## Usage and functionality summary
12
+
13
+ * Define the set of states you want your stateful object to have by creating a class for each state and inheriting from `StatePattern:State`.
14
+ * All public methods defined in this state classes, except `enter` and `exit` (see below), are then available to the stateful object and their behaviour will depend on the current state .
15
+ * If this automatic delegation to the current state public methods is not enough for your stateful object then you can just reopen the method and use super whenever you want to call the state implementation.
16
+ * Inside each state instance you can access the stateful object through the +stateful+ method.
17
+ * Inside each state instance you can access the previous state through the +previous_state+ method.
18
+ * Define `enter` or `exit` methods to hook any behaviour you want to execute whenever the stateful object enters or exits the state.
19
+ * An event is just a method that calls `transition_to` at some point.
20
+ * If you want guards for some event just use plain old ifs before your `transition_to`.
21
+ * In the stateful object you must `set_initial_state`.
22
+
23
+ ## Examples
24
+
25
+ So here's a simple example that mimics a traffic semaphore
26
+
27
+ ```ruby
28
+ require 'state_pattern'
29
+
30
+ class Stop < StatePattern::State
31
+ def next
32
+ sleep 3
33
+ transition_to(Go)
34
+ end
35
+
36
+ def color
37
+ "Red"
38
+ end
39
+ end
40
+
41
+ class Go < StatePattern::State
42
+ def next
43
+ sleep 2
44
+ transition_to(Caution)
45
+ end
46
+
47
+ def color
48
+ "Green"
49
+ end
50
+ end
51
+
52
+ class Caution < StatePattern::State
53
+ def next
54
+ sleep 1
55
+ transition_to(Stop)
56
+ end
57
+
58
+ def color
59
+ "Amber"
60
+ end
61
+ end
62
+
63
+ class TrafficSemaphore
64
+ include StatePattern
65
+ set_initial_state Stop
66
+ end
67
+
68
+ semaphore = TrafficSemaphore.new
69
+
70
+ loop do
71
+ puts semaphore.color
72
+ semaphore.next
73
+ end
74
+ ```
75
+
76
+ Let's now use one nice example from the AASM documentation and translate it to state_pattern.
77
+
78
+ ```ruby
79
+ require 'state_pattern'
80
+
81
+ class Dating < StatePattern::State
82
+ def get_intimate
83
+ transition_to(Intimate) if stateful.drunk?
84
+ end
85
+
86
+ def get_married
87
+ transition_to(Married) if stateful.willing_to_give_up_manhood?
88
+ end
89
+
90
+ def enter
91
+ stateful.make_happy
92
+ end
93
+
94
+ def exit
95
+ stateful.make_depressed
96
+ end
97
+ end
98
+
99
+ class Intimate < StatePattern::State
100
+ def get_married
101
+ transition_to(Married) if stateful.willing_to_give_up_manhood?
102
+ end
103
+
104
+ def enter
105
+ stateful.make_very_happy
106
+ end
107
+
108
+ def exit
109
+ stateful.never_speak_again
110
+ end
111
+ end
112
+
113
+ class Married < StatePattern::State
114
+ def enter
115
+ stateful.give_up_intimacy
116
+ end
117
+
118
+ def exit
119
+ stateful.buy_exotic_car_and_wear_a_combover
120
+ end
121
+ end
122
+
123
+ class Relationship
124
+ include StatePattern
125
+ set_initial_state Dating
126
+
127
+ def drunk?; @drunk; end
128
+ def willing_to_give_up_manhood?; @give_up_manhood; end
129
+ def make_happy; end
130
+ def make_depressed; end
131
+ def make_very_happy; end
132
+ def never_speak_again; end
133
+ def give_up_intimacy; end
134
+ def buy_exotic_car_and_wear_a_combover; end
135
+ end
136
+ ```
137
+
138
+ ## Enter and exit hooks
139
+
140
+ Inside your state classes, any code that you put inside the enter method will be executed when the state is instantiated.
141
+ You can also use the exit hook which is triggered when a successful transition to another state takes place.
142
+
143
+ ## Overriding automatic delegation
144
+
145
+ If the automatic delegation to the current state public methods is not enough for your stateful object then you can just reopen the method and use super whenever you want to call the state implementation.
146
+
147
+ ```ruby
148
+ class TrafficSemaphore
149
+ include StatePattern
150
+ set_initial_state Stop
151
+
152
+ def color
153
+ # some great code here
154
+ # now we call the current state implementation
155
+ super
156
+ # more cool hacking here
157
+ end
158
+ end
159
+ ```
160
+
161
+ ## Rails
162
+
163
+ To use the state pattern in your Rails models you need to:
164
+
165
+ * Add a state column for your model table of type string
166
+ * Include StatePattern::ActiveRecord in your model file
167
+ * Use the state pattern as you would do in a plain Ruby class as shown above
168
+
169
+ Please see the examples folder for a Rails 3 example.
170
+
171
+ ### Example
172
+
173
+ Remember to put each class in its correct file following Rails naming conventions.
174
+
175
+ ```ruby
176
+ module BlogStates
177
+ #we can put common state behaviour into a base state class or we could have implemented it inside the model with methods that call super, your choice
178
+ class StateBase < StatePattern::State
179
+ def submit!
180
+ end
181
+
182
+ def publish!
183
+ end
184
+
185
+ def reject!
186
+ transition_to(Rejected)
187
+ stateful.save!
188
+ end
189
+
190
+ def verify!
191
+ end
192
+ end
193
+
194
+ class Published < StateBase
195
+ end
196
+
197
+ class Pending < StateBase
198
+ def publish!
199
+ transition_to(Published) if stateful.valid?
200
+ stateful.save!
201
+ end
202
+ end
203
+
204
+ class Unverified < StateBase
205
+ def submit!
206
+ if stateful.submitter.manager?
207
+ if stateful.profile_complete?
208
+ transition_to(Published)
209
+ else
210
+ transition_to(Pending)
211
+ end
212
+
213
+ stateful.save!
214
+ end
215
+ end
216
+
217
+ def verify!
218
+ transition_to(Pending)
219
+ stateful.save!
220
+ end
221
+ end
222
+
223
+ class Rejected < StateBase
224
+ def publish!
225
+ transition_to(Published) if stateful.valid?
226
+ stateful.save!
227
+ end
228
+
229
+ def enter
230
+ Notifier.notify_blog_owner(stateful)
231
+ end
232
+ end
233
+ end
234
+
235
+ class Blog < ActiveRecord::Base
236
+ include StatePattern::ActiveRecord
237
+ set_initial_state Unverified
238
+
239
+ .
240
+ .
241
+ .
242
+
243
+ end
244
+ ```
245
+
246
+ ### The state attribute
247
+
248
+ By default `StatePattern::ActiveRecord` expects a column named `state` in the model. If you prefer to use another attribute do:
249
+
250
+ ```ruby
251
+ set_state_attribute :state_column
252
+ ```
253
+
254
+ ### How do I decide? state_pattern or [AASM](http://github.com/rubyist/aasm)?
255
+
256
+ * Lot of state dependent behavior? Lot of conditional logic depending on the state? => state_pattern
257
+ * Not much state dependent behavior? => AASM
258
+
259
+ ## Thanks
260
+
261
+ * [Alvaro Gil](http://github.com/zevarito) for being the first using this gem in a real Rails project.
262
+ * [Nicolás Sanguinetti](http://github.com/foca) for his great feedback.
263
+
264
+ ## Installation
265
+
266
+ ```bash
267
+ gem install state_pattern
268
+ ```
269
+
270
+ ## Collaborate
271
+
272
+ [http://github.com/dcadenas/state_pattern](http://github.com/dcadenas/state_pattern)
273
+
274
+ ## Copyright
275
+
276
+ Copyright (c) 2009 Daniel Cadenas. See LICENSE for details.
@@ -15,7 +15,7 @@ Rails::Initializer.run do |config|
15
15
  # config.load_paths += %W( #{RAILS_ROOT}/extras )
16
16
 
17
17
  # Specify gems that this application depends on and have them installed with rake gems:install
18
- config.gem "state_pattern"
18
+ config.gem "state_pattern", :version => "~>2.0.0"
19
19
  # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
20
20
  # config.gem "sqlite3-ruby", :lib => "sqlite3"
21
21
  # config.gem "aws-s3", :lib => "aws/s3"
@@ -1,7 +1,7 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
3
  gem 'rails', '3.0.0'
4
- gem 'state_pattern', '1.3.0'
4
+ gem 'state_pattern', '~>2.0.0'
5
5
 
6
6
  # Bundle edge Rails instead:
7
7
  # gem 'rails', :git => 'git://github.com/rails/rails.git'
@@ -60,7 +60,7 @@ GEM
60
60
  thor (~> 0.14.0)
61
61
  rake (0.8.7)
62
62
  sqlite3-ruby (1.3.1)
63
- state_pattern (1.3.0)
63
+ state_pattern (2.0.0)
64
64
  thor (0.14.3)
65
65
  treetop (1.4.8)
66
66
  polyglot (>= 0.3.1)
@@ -72,4 +72,4 @@ PLATFORMS
72
72
  DEPENDENCIES
73
73
  rails (= 3.0.0)
74
74
  sqlite3-ruby
75
- state_pattern (= 1.3.0)
75
+ state_pattern (~> 2.0.0)