webflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,324 @@
1
+ #--
2
+ # Copyright (c) 2007 The World in General
3
+ #
4
+ # Released under the Creative Commons Attribution-Share Alike 3.0 License.
5
+ # Licence details available at : http://creativecommons.org/licenses/by-sa/3.0/
6
+ #
7
+ # Created by Luc Boudreau ( lucboudreau at gmail )
8
+ #
9
+ # The above copyright notice and this permission notice shall be
10
+ # included in all copies or substantial portions of the Software.
11
+ #
12
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+ #++
20
+
21
+ #require 'event'
22
+ #require 'base'
23
+
24
+ module WebFlow
25
+
26
+ # The FlowStep is a superclass from which every step type inherits.
27
+ # It's methods are therefore available to any step type present in the
28
+ # WebFlow framework.
29
+ #
30
+ # == Mapping instructions
31
+ #
32
+ # The mapping instructions common to every step type are the following.
33
+ #
34
+ # === on
35
+ #
36
+ # The <tt>on</tt> instruction is used to map a returned event name to a
37
+ # subsequent step. Here's a simple example of it's usage.
38
+ #
39
+ # action_step :example_step do
40
+ #
41
+ # (...)
42
+ #
43
+ # on :success => :next_step_name
44
+ #
45
+ # on :back => :previous_step_name
46
+ #
47
+ # end
48
+ #
49
+ # In the previous example, the example step will route the flow to the
50
+ # step named <tt>next_step_name</tt> if the step definition returns an
51
+ # event which is named <tt>success</tt>. If the step definition
52
+ # returns a <tt>back</tt> event, the flow will then call the
53
+ # <tt>previous_step_name</tt> step.
54
+ #
55
+ # === upon
56
+ #
57
+ # The <tt>upon</tt> instruction is used to map a raised error class to a
58
+ # subsequent step. Here's a simple example of it's usage.
59
+ #
60
+ # action_step :example_step do
61
+ #
62
+ # (...)
63
+ #
64
+ # upon :StandardError => :next_step_name
65
+ #
66
+ # end
67
+ #
68
+ # In the previous example, the example step will route the flow to the
69
+ # step named <tt>next_step_name</tt> if the step definition raises an
70
+ # error which is a kind of <tt>StandardError</tt>. If the upon instruction
71
+ # is used more than once, the first declaration has priority. Also note
72
+ # that the <tt>kind_of?</tt> method is used to validate the correspondence,
73
+ # so subclasses of mapped error classes will be included as well.
74
+ #
75
+ # === method
76
+ #
77
+ # The <tt>method</tt> instruction is meant to override the default step
78
+ # definition name. By default, the definition of a given step has to be declared
79
+ # with the same name as the step name in the mapping. If a step is named
80
+ # <tt>my_jolly_step</tt>, the controller has to implement the step logic
81
+ # with a <tt>def</tt> block which is named <tt>my_jolly_step</tt>.
82
+ #
83
+ # The <tt>method</tt> instruction will tell the WebFlow framework to look for
84
+ # a definition of the given value instead of looking for the same name.
85
+ # This allows to reuse business logic and decouple the mapping from the
86
+ # step implementation. One could then do something like :
87
+ #
88
+ # class MyController < WebFlow::Base
89
+ #
90
+ # def initialize
91
+ #
92
+ # (...)
93
+ #
94
+ # action_step :step_1 do
95
+ # method :shared_implementation
96
+ # (...)
97
+ # end
98
+ #
99
+ # action_step :step_2 do
100
+ # method :shared_implementation
101
+ # (...)
102
+ # end
103
+ # end
104
+ #
105
+ # def shared_implementation
106
+ # (...)
107
+ # end
108
+ #
109
+ # end
110
+ #
111
+ #
112
+ # == Developer infos
113
+ #
114
+ # This class defines a basic skeleton for different step types
115
+ # used by the WebFlow framework. All new step types must inherit
116
+ # of this superclass to be usable in the WebFlow controller.
117
+ #
118
+ # It is also the responsibility of any subclass to add it's declaration
119
+ # method in the WebFlow::Base class. See view_step.rb for an example.
120
+ #
121
+ # Also, subclasses can change the @definition_required instance variable value
122
+ # to tell the WebFlow framework that it can handle the execution
123
+ # without the user controller defining explicitly a step implementation.
124
+ # See view_step.rb(initialize) for an example.
125
+ #
126
+ # Event outcomes are defined in the @outcomes instance variable while
127
+ # the error handlers are defined in the @handlers instance variable.
128
+ # Those variables are protected, so any subclasses of FlowStep can access
129
+ # them and hack the mechanism if required.
130
+ #
131
+ class FlowStep
132
+
133
+
134
+ # Method signature to override in implementing classes.
135
+ # Does the 'dirty job' once it is required.
136
+ #
137
+ # Each implementing step HAS TO VERIFY THE VALUE OF THE lookup_method_to_call
138
+ # method. Or else, the 'method' instruction won't be respected
139
+ # if the user has used the 'method' instruction.
140
+ #
141
+ def execute(*args)
142
+
143
+ raise(WebFlowError.new, "You can't directly use FlowStep as a step. As a matter of fact, I don't even know how you were able in the first place anyways...")
144
+
145
+ end
146
+
147
+
148
+ # Used to know if the step needs an explicitly declared step definition or if it can
149
+ # manage the task with a default behavior.
150
+ def definition_required?
151
+ # We have to distinguish the false value from the non
152
+ # existence of the variable. Therefore the ||= operator
153
+ # can't help us.
154
+ if @definition_required.class.kind_of?(NilClass)
155
+ @definition_required = true
156
+ else
157
+ @definition_required
158
+ end
159
+ end
160
+
161
+
162
+
163
+ # Maps an event to a method call. Use in a mapping like this :
164
+ #
165
+ # implemented_step_type :id_of_step do
166
+ # on :event => :method
167
+ # end
168
+ #
169
+ # This is also possible :
170
+ #
171
+ # implemented_step_type :id_of_step do
172
+ # on { :event => :method,
173
+ # :event2 => :method2 }
174
+ # end
175
+ #
176
+ def on(options)
177
+
178
+ # Make sure we received a hash
179
+ raise(WebFlowError.new, "The 'on' method takes a Hash object as a parameter.") unless options.kind_of? Hash
180
+
181
+ # Make sure the key is not used twice in a definition
182
+ # to enforce coherence of the mapping.
183
+ options.each_key do |key|
184
+ raise(WebFlowError.new, "An event in this step scope already uses the name '#{key}'.") if outcomes.has_key?(key.to_s)
185
+ raise(WebFlowError.new, "#{key} is a reserved event keyword.") if WebFlow::Base.reserved_event?(key.to_s)
186
+ end
187
+
188
+ # Store the values in the possible outcomes hash
189
+ options.each { |key,value| outcomes[key.to_s] = value.to_s }
190
+
191
+ end
192
+
193
+
194
+ #def on(*args, &block)
195
+ #
196
+ # if block_given?
197
+ # event = args.first
198
+ # outcomes[event.to_s] = block
199
+ # else
200
+ # options = args.first || {}
201
+ #
202
+ # options.each_key do |key|
203
+ # raise(WebFlowError.new, "An event already uses the name '#{key}'. Names must be unique within a step scope.") if outcomes.has_key?(key.to_s)
204
+ # raise(WebFlowError.new, "#{key} is a reserved event keyword.") if WebFlow::Base.reserved_event?(key.to_s)
205
+ # end
206
+ #
207
+ # # Store the values in the possible outcomes hash
208
+ # options.each { |key, value| outcomes[key.to_s] = value.to_s }
209
+ #
210
+ # end
211
+ #
212
+ #end
213
+
214
+
215
+ # Maps an error class to a step name to execute.
216
+ # Use in a mapping like this :
217
+ #
218
+ # implemented_step_type :id_of_step do
219
+ # upon :WebFlowError => :step_name
220
+ # end
221
+ #
222
+ # This is also possible :
223
+ #
224
+ # implemented_step_type :id_of_step do
225
+ # upon { :WebFlowError => :step_name,
226
+ # :WhateverError => :step_name }
227
+ # end
228
+ #
229
+ # Only subclasses of StandardError will be rescued. This means that RuntimeError
230
+ # cannot be handled.
231
+ #
232
+ def upon(hash)
233
+
234
+ # Make sure we received a hash
235
+ raise(WebFlowError.new, "The 'upon' method takes a Hash object as a parameter. Go back to the API documents since you've obviously didn't read them well enough...") unless hash.kind_of? Hash
236
+
237
+ # Make sure the key is not used twice in a definition
238
+ # to enforce coherence of the mapping.
239
+ hash.each_key do |key|
240
+ raise(WebFlowError.new, "An error of the class '#{key}' is already mapped. They must be unique within a step scope.") if handlers.has_key?(key.to_s)
241
+ end
242
+
243
+ # Store the values in the handlers hash
244
+ hash.each { |key,value| handlers.store key.to_s, value.to_s }
245
+
246
+ end
247
+
248
+
249
+ # Maps a method name to execute instead of searching
250
+ # the controller for a method name who is the same as
251
+ # the step name.
252
+ #
253
+ # Use it as :
254
+ #
255
+ # action_step :my_step do
256
+ # method :call_this_instead
257
+ # end
258
+ #
259
+ def method(method_name)
260
+
261
+ @_method = method_name.to_s
262
+
263
+ end
264
+
265
+
266
+ # Tells if the error class passed as an argument is handled
267
+ def handles?(error_class)
268
+ handlers.has_key?(error_class)
269
+ end
270
+
271
+
272
+
273
+ # Returns the appropriate step name to execute upon
274
+ # the given error class
275
+ def handler(error_class)
276
+ error_class.kind_of?(String) ? key = error_class : key = error_class.to_s
277
+ handlers.has_key?(key) ? handlers.fetch(key) : raise(WebFlowError.new, "There's no handler defined for the error class '#{key}'.")
278
+ end
279
+
280
+
281
+ # Tells if the given event name is a possibe outcome of this step
282
+ def has_an_outcome_for?(event_name)
283
+ outcomes.has_key? event_name.to_s
284
+ end
285
+
286
+
287
+ # Returns the step name associated to the given event.
288
+ def outcome(event_name)
289
+ outcomes.has_key?(event_name.to_s) ? outcomes.fetch(event_name.to_s) : raise(WebFlowError.new, "There's no outcome defined for the event name '#{event_name}'.")
290
+ end
291
+
292
+
293
+ protected
294
+
295
+ # Tells the framework if the step needs a definition in the declaring controller. For example, the
296
+ # view step doesn't need it. It can act without any step definition.
297
+ @definition_required = true
298
+
299
+
300
+ # Holds the different outcomes possible, which are
301
+ # defined via the 'on' method
302
+ def outcomes
303
+ @outcomes ||= {}
304
+ end
305
+
306
+
307
+ # Holds the methods to call upon errors
308
+ def handlers
309
+ @handlers ||= {}
310
+ end
311
+
312
+
313
+ # Call this method from within a subclass to get the correct
314
+ # method name to call. Pass a default value to return if nothing
315
+ # is defined for this step. Don't pass a value and it will return
316
+ # either nil or a method name.
317
+ def lookup_method_to_call( default_value=nil )
318
+ @_method ||= nil
319
+ @_method.nil? ? default_value.to_s : @_method
320
+ end
321
+
322
+
323
+ end
324
+ end
@@ -0,0 +1,77 @@
1
+ #--
2
+ # Copyright (c) 2007 The World in General
3
+ #
4
+ # Released under the Creative Commons Attribution-Share Alike 3.0 License.
5
+ # Licence details available at : http://creativecommons.org/licenses/by-sa/3.0/
6
+ #
7
+ # Created by Luc Boudreau ( lucboudreau at gmail )
8
+ #
9
+ # The above copyright notice and this permission notice shall be
10
+ # included in all copies or substantial portions of the Software.
11
+ #
12
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+ #++
20
+
21
+
22
+
23
+ module WebFlow
24
+
25
+ # This is a skeleton class to create plugins to the WebFlow framework.
26
+ # Any plugin MUST me a subclass of this superclass.
27
+ #
28
+ # Plugins are still under development so they are not activated yet.
29
+ class Plugin
30
+
31
+ # Method called by a controller when a system event for which this
32
+ # plugin is registered is reached.
33
+ def self::call(system_event)
34
+
35
+ # Execute the plugin
36
+ return_value = self.notify
37
+
38
+ # Make sure we got a system event back
39
+ raise(WebFlowError.new, "One of your plugin didn't return a system event, as required. The culprit is : " + self.inspect ) unless return_value.kind_of? WebFlow::SystemEvent
40
+
41
+ # Act accordingly
42
+ return return_value
43
+
44
+
45
+ end
46
+
47
+
48
+ protected
49
+
50
+ # Method to override in a plugin subclass. This method will be called
51
+ # when a controller encounters one of it's internal events
52
+ def self::notify( internal_event_name, controller, flow_data, request, response, session)
53
+
54
+ raise(WebFlowError.new, "One of your plugins didn't override the notify method. The culprit is : " + self.inspect)
55
+
56
+ end
57
+
58
+
59
+
60
+ # Allows a plugin to register itself as a listener of the WebFlow::Base class
61
+ # internal events.
62
+ def self::listen(*event_names)
63
+
64
+ event_names.each do |name|
65
+ WebFlow::Base.listen name.to_s, self
66
+ end
67
+
68
+ end
69
+
70
+ # Tells the notifier that this filter has done it's job and other filters
71
+ # may do whatever the see fit.
72
+ def self::resume
73
+ WebFlow::SystemEvent
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,48 @@
1
+ #--
2
+ # Copyright (c) 2007 The World in General
3
+ #
4
+ # Released under the Creative Commons Attribution-Share Alike 3.0 License.
5
+ # Licence details available at : http://creativecommons.org/licenses/by-sa/3.0/
6
+ #
7
+ # Created by Luc Boudreau ( lucboudreau at gmail )
8
+ #
9
+ # The above copyright notice and this permission notice shall be
10
+ # included in all copies or substantial portions of the Software.
11
+ #
12
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+ #++
20
+
21
+ module WebFlow
22
+
23
+ # This class is used by the WebFlow framework to generate the
24
+ # FlowExecutionKey which uniquely identifies a user flow execution.
25
+ #
26
+ # It creates random alphanumeric strings with upper-case characters.
27
+ class RandomGenerator
28
+
29
+ # Defines which characters are used to generate random strings
30
+ Chars = ("A".."Z").to_a + ("0".."9").to_a
31
+
32
+ # Generates a random string of the given length. By default, it's
33
+ # 64 characters long.
34
+ def self::random_string(len = 64)
35
+
36
+ # Initial string
37
+ string = ""
38
+
39
+ # Pick characters at random
40
+ 1.upto(len) { |i| string << Chars[rand(Chars.size-1)] }
41
+
42
+ # Return the resulting string
43
+ return string.to_s
44
+
45
+ end
46
+
47
+ end
48
+ end