webflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
6
+ *.iml
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create 1.9.2@webflow
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in webflow.gemspec
4
+ gemspec
data/README ADDED
File without changes
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,79 @@
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
+
26
+ # This class adds a couple validations before creating a new flow.
27
+ class Controller < WebFlow::Plugin
28
+
29
+ # Listen to the 'before_new_flow' internal event
30
+ self.listen :before_new_flow
31
+
32
+
33
+
34
+
35
+ # Method to override in a plugin subclass. This method will be called
36
+ # when a controller encounters one of it's internal events
37
+ def self::notify( system_event)
38
+
39
+ # Make sure there's a start_step defined
40
+ raise (WebFlowError.new, "Your controller must declare a start step name. Use 'start_step :step_name' and define this step in the mapping.") if controller.instance_variable_get("@_start_step").nil?
41
+
42
+ # Make sure there's an end_step defined
43
+ raise (WebFlowError.new, "Your controller must declare an end step name. Use 'end_step :step_name' and define this step in the mapping. I suggest using a view step which could redirect if you don't want to create a 'thank you' screen.") if controller.instance_variable_get("@_end_step").nil?
44
+
45
+ # Resume filter chain
46
+ resume
47
+
48
+ end
49
+
50
+
51
+
52
+ end
53
+
54
+
55
+
56
+ WebFlow::Base.class_eval do
57
+
58
+ protected
59
+
60
+ # Allows the controller to handle errors which were not handled by
61
+ # the steps themselves.
62
+ def upon
63
+ end
64
+
65
+
66
+
67
+
68
+
69
+
70
+ # Holds the methods to call upon errors
71
+ def handlers
72
+ @handlers ||= {}
73
+ end
74
+
75
+
76
+ end
77
+
78
+
79
+ 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
+
22
+
23
+
24
+ module WebFlow
25
+
26
+ # This class adds a couple validations before creating a new flow.
27
+ class FlowResumeValidations < WebFlow::Plugin
28
+
29
+ # Listen to the 'before_new_flow' internal event
30
+ #self.listen :before_flow_resume
31
+
32
+
33
+
34
+
35
+ # Method to override in a plugin subclass. This method will be called
36
+ # when a controller encounters one of it's internal events
37
+ def self::notify( internal_event_name, controller, flow_data)
38
+
39
+ # Nothing... deactivated.
40
+
41
+ end
42
+
43
+ end
44
+
45
+
46
+ end
47
+
48
+
@@ -0,0 +1,52 @@
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
+
24
+ module WebFlow
25
+
26
+ # This class adds a couple validations before creating a new flow.
27
+ class NewFlowValidations < WebFlow::Plugin
28
+
29
+ # Listen to the 'before_new_flow' internal event
30
+ self.listen :before_new_flow
31
+
32
+
33
+
34
+
35
+ # Method to override in a plugin subclass. This method will be called
36
+ # when a controller encounters one of it's internal events
37
+ def self::notify( internal_event_name, controller, flow_data)
38
+
39
+ # Make sure there's a start_step defined
40
+ raise (WebFlowError.new, "Your controller must declare a start step name. Use 'start_step :step_name' and define this step in the mapping.") if controller.instance_variable_get("@_start_step").nil?
41
+
42
+ # Make sure there's an end_step defined
43
+ raise (WebFlowError.new, "Your controller must declare an end step name. Use 'end_step :step_name' and define this step in the mapping. I suggest using a view step which could redirect if you don't want to create a 'thank you' screen.") if controller.instance_variable_get("@_end_step").nil?
44
+
45
+ end
46
+
47
+ end
48
+
49
+
50
+ end
51
+
52
+
@@ -0,0 +1,18 @@
1
+ require "webflow/version"
2
+
3
+ # Load the basic stuff
4
+ require 'webflow/exceptions'
5
+ require 'webflow/base'
6
+ require 'webflow/session_handler'
7
+ require 'webflow/state'
8
+ require 'webflow/event'
9
+ require 'webflow/random_generator'
10
+ require 'webflow/flow_step'
11
+ require 'webflow/action_step'
12
+ require 'webflow/view_step'
13
+ #require 'webflow/plugin'
14
+
15
+ module WebFlow
16
+ # Reserve the render user event for the Base class.
17
+ WebFlow::Base.reserve_event :render
18
+ end
@@ -0,0 +1,114 @@
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
+ module WebFlow
23
+
24
+ # This class allows the controllers to create action steps. Action steps
25
+ # do nothing but call a method defined in the controller and then route
26
+ # the flow according to it's events mapping.
27
+ #
28
+ # == Simple usage
29
+ #
30
+ # action_step :action_name do
31
+ # on :success => :another_step
32
+ # on :back => :yet_another_step
33
+ # end
34
+ #
35
+ # The method identified by :action_name will simply be called and then the
36
+ # flow will be routed to either another_step or yet_another_step steps.
37
+ #
38
+ # One could also use the action_step instruction to batch define action
39
+ # steps. Simply call the action_step instruction and give it a comma separated
40
+ # list of step names.
41
+ #
42
+ # action_step :first_step, :second_step do
43
+ # (...)
44
+ # end
45
+ #
46
+ # == Inherited instructions
47
+ #
48
+ # The ViewStep class inherits all the standard step methods defined in FlowStep class.
49
+ #
50
+ #
51
+ class ActionStep < WebFlow::FlowStep
52
+
53
+ # Holds the view name to render once the execution is complete
54
+ @action_name
55
+
56
+
57
+ # Initializes the instance
58
+ def initialize( m_action_name )
59
+
60
+ @action_name = m_action_name
61
+
62
+ end
63
+
64
+
65
+ # This method is required by the WebFlow::Base and will be called
66
+ # once it relays the execution to this step instance
67
+ def execute(*args)
68
+
69
+ # Verify if the controller answers to the @action_name value
70
+ raise WebFlowError, "The action step #{@action_name} is not defined in your controller." unless args[0].respond_to? lookup_method_to_call(@action_name)
71
+
72
+ # The controller defines a method with the same name as @action_name.
73
+ # Kewl! A 'someone else problem' !!!
74
+ args[0].send( lookup_method_to_call( @action_name ) )
75
+
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
82
+ # Let's create the view_step definition inside the WebFlow::Base
83
+ # controller class
84
+ WebFlow::Base.class_eval do
85
+
86
+ protected
87
+
88
+ # Called to define a step into a flow. Example of usage :
89
+ #
90
+ # view_step :view_name do
91
+ # on :success => :another_step
92
+ # on :back => :yet_another_step
93
+ # end
94
+ #
95
+ # See the ActionStep class documentation for more options.
96
+ #
97
+ def action_step(*action_name, &block)
98
+
99
+ # Instantiate a ViewStep object if symbol or string received
100
+ action_name.each do |name|
101
+
102
+ step = WebFlow::ActionStep.new(name.to_s)
103
+
104
+ # Register this step in the flow repository
105
+ register name.to_s, step
106
+
107
+ # Execute the config block
108
+ step.instance_eval(&block) if block_given?
109
+
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,723 @@
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
+ module WebFlow
23
+
24
+
25
+ # The WebFlow framework turns an ActionController into a flow capable
26
+ # controller. It can handle event triggering, the back button and any
27
+ # user defined step. Here are the main concepts.
28
+ #
29
+ # == Concepts
30
+ #
31
+ # === Flow
32
+ #
33
+ # A flow is a logical procedure which defines how to handle different
34
+ # steps and guide the user through them.
35
+ #
36
+ # === Step
37
+ #
38
+ # A step is a single unit of processing included inside a flow. All steps
39
+ # have to inherit from WebFlow::FlowStep. They can be used to define one
40
+ # of the following :
41
+ #
42
+ # - ViewStep : displays a view and then relays the execution to methods or steps according to the triggered event.
43
+ #
44
+ # - ActionStep : calls a method of an WebFlow controller and then relays the execution to methods or steps according to the triggered event.
45
+ #
46
+ # More step types can be created to extend the WebFlow framework.
47
+ #
48
+ # === Event
49
+ #
50
+ # An event is nothing more than a symbol which triggers the execution of methods
51
+ # or steps, depending on the mapping. There are reserved events which cannot be
52
+ # mapped by a user inside of a subclass of WebFlow::Base controllers. To ask
53
+ # the controller if an event name has been reserved for internal purposes, use :
54
+ #
55
+ # WebFlow::Base.reserved_event? :event_name_to_verify
56
+ #
57
+ # == Creating a mapping
58
+ #
59
+ # The mapping is where you tell your controller how to connect the different steps.
60
+ #
61
+ # To declare a controller mapping, you must do it in the object's <tt>initialize</tt>
62
+ # method. This method will be automatically called upon the instantiation of your
63
+ # controller.
64
+ #
65
+ # === Simple mapping example
66
+ #
67
+ # class MyController < WebFlow::Base
68
+ #
69
+ # def initialize
70
+ #
71
+ # start_with :step_1
72
+ # end_with :end_my_flow
73
+ # redirect_invalid_flows :step_1
74
+ #
75
+ # upon :StandardError => :end_my_flow
76
+ #
77
+ # action_step :step_1 do
78
+ # method :start_my_flow
79
+ # on :success => :step_2
80
+ # end
81
+ #
82
+ # view_step :step_2 do
83
+ # on :finish => end_my_flow
84
+ # end
85
+ #
86
+ # view_step :end_my_flow
87
+ #
88
+ # end
89
+ #
90
+ # def start_my_flow
91
+ # # Nothing to be done
92
+ # event :success
93
+ # end
94
+ #
95
+ # end
96
+ #
97
+ # Let's decompose what we just did in this very minimalist controller.
98
+ #
99
+ # * The flow starts with the step named <tt>step_1</tt>.
100
+ # * The flow data will be destroyed once we reach the <tt>end_my_flow</tt> step.
101
+ # * If the user sends an invalid key or his flow data is expired, we redirect the flow to the <tt>step_1</tt> step.
102
+ # * If an error of class StandardError or any subclass of it is raised and not handled by the steps, the controller will route the flow to the <tt>end_my_flow</tt> step.
103
+ # * We declared a step named <tt>step_1</tt>. The step is implemented via the method named <tt>start_my_flow</tt>. If the <tt>success</tt> event is returned, we route the flow to the step named <tt>step_2</tt>.
104
+ # * We declared a step named <tt>step_2</tt>. If the <tt>finish</tt> event is returned, the step named <tt>end_my_flow</tt> will be called.
105
+ #
106
+ # There are many other config options which can be used. I strongly suggest reading
107
+ # the whole API documentation.
108
+ #
109
+ # == Reserved events
110
+ #
111
+ # The WebFlow::Base controller reserves the following event names for
112
+ # itself.
113
+ #
114
+ # - render : Tells the framework that we must return the control to the view
115
+ # parser, since one of the 'render' method has been called and we
116
+ # are now ready to display something.
117
+ #
118
+ # == About Plugins
119
+ #
120
+ # The WebFlow framework has a plugin system, but is unstable and
121
+ # incomplete. Don't bother using it for now, unless you want to contribute
122
+ # of course. This class is stable and ready for plugin registration though.
123
+ # The Plugin class is not. To activate the plugins system, go to the
124
+ # #{ACTIONFLOW_ROOT}/webflow/webflow.rb file and uncomment the
125
+ # plugins initialisation code.
126
+ #
127
+ class Base < ApplicationController
128
+
129
+
130
+ # Defines the symbols name prefix which are used to identify the next events.
131
+ # As an example, in a view_state, the button used to trigger the save event
132
+ # would be named '_event_save'. If we were in an action_state, to trigger the
133
+ # success event, we would 'return :_event_success'
134
+ @@event_prefix = '_event_'
135
+ cattr_accessor :event_prefix
136
+
137
+
138
+ # Defines the name of the parameter submitted which contains the next event name.
139
+ @@event_input_name_prefix = '_submit'
140
+ cattr_accessor :event_input_name_prefix
141
+
142
+
143
+ # Defines the regex used to validate the proper format of a submitted
144
+ # event name.
145
+ Event_input_name_regex = /^#{event_input_name_prefix}#{event_prefix}([a-zA-Z0-9_]*)$/
146
+
147
+
148
+ # Defines the name of the variable which holds the flow unique state id.
149
+ # As an example, a view_state would add a hidden field named 'flow_exec_key',
150
+ # which would contain the key used to store a state in the session.
151
+ #
152
+ # We could then restore the state by accessing it with :
153
+ # session[:flow_data-1234abcd].fetch( params[:flow_exec_key] )
154
+ #
155
+ # See the SessionHandler for more details
156
+ @@flow_execution_key_id = 'flow_exec_key'
157
+ cattr_accessor :flow_execution_key_id
158
+
159
+
160
+ # Aliases the 'render' method to intercept it and cache it's execution results.
161
+ # Doing this allows to delay the calls to the render method until the end of the
162
+ # current step chain so that if a plugin wishes to interrupt the chain and render
163
+ # some other content, there won't be any conflict between previous calls to the
164
+ # render method.
165
+ alias_method :old_render, :render
166
+
167
+
168
+ # Aliases the 'redirect_to' method to intercept it and cache it's execution results.
169
+ # Doing this allows to delay the calls to the redirect_to method until the end of the
170
+ # current step chain so that if a plugin wishes to interrupt the chain and redirect
171
+ # the request, there won't be any conflict between previous calls to the
172
+ # redirect_to method.
173
+ alias_method :old_redirect_to, :redirect_to
174
+
175
+
176
+ # Class method to tell the WebFlow framework to prevent users from mapping certain
177
+ # event names. Use it as :
178
+ #
179
+ # WebFlow::Base.reserve_event :some_event_name
180
+ #
181
+ def self::reserve_event(event_name)
182
+
183
+ # Reserve the event passed as a parameter
184
+ reserved_events.push( event_name.to_s )
185
+
186
+ # Remove duplicates
187
+ reserved_events.uniq!
188
+
189
+ end
190
+
191
+
192
+ # Class method to tell the WebFlow framework to notify a plugin upon
193
+ # a certain internal event happening. Use it in a plugin class as follows :
194
+ #
195
+ # WebFlow::Base.listen :some_event, self
196
+ #
197
+ def self::listen(internal_event_name, plugin)
198
+
199
+ # Reserve the event passed as a parameter but first validate
200
+ # that it's a subclass of WebFlow::Plugin
201
+ #plugin.kind_of?(WebFlow::Plugin) ? filters.fetch(internal_event_name.to_s).push(plugin) : raise(WebFlowError.new, "One of your plugins tried to register itself as a listener and is not a subclass of WebFlow::Plugin. The culprit is : " + plugin.inspect )
202
+
203
+ filters.fetch(internal_event_name.to_s).push(plugin)
204
+
205
+ end
206
+
207
+
208
+ # Class method to ask the WebFlow framework if a given event name has been reserved
209
+ # and can't be mapped by users.
210
+ #
211
+ # WebFlow::Base.reserved_event? :some_event_name
212
+ #
213
+ def self::reserved_event?(event_name)
214
+
215
+ reserved_events.include? event_name.to_s
216
+
217
+ end
218
+
219
+
220
+ # Default method to handle the requests. All the dispatching is done here.
221
+ def index
222
+
223
+ # Holds the calls to the render method to delay their execution
224
+ @renders = Array.new
225
+
226
+ # Holds the calls to the redirect method to delay it's execution
227
+ @redirects = Array.new
228
+
229
+ begin
230
+
231
+ # Notify the plugins we got here
232
+ #notify :request_entry
233
+
234
+ # Tells which was the last state
235
+ @flow_id = params[flow_execution_key_id]
236
+
237
+ # Flag used to know if we reached the end_step
238
+ @end_step_reached = false
239
+
240
+ # Check if the flow has already started
241
+ if @flow_id && !@flow_id.empty?
242
+
243
+ # Notify plugins we've just re-started a flow
244
+ #notify :before_flow_resume
245
+
246
+ # Get the event name
247
+ raise(WebFlowError.new, "Your view did not submit properly to an event name.") unless event_name = url_event_value(params)
248
+
249
+ # Make sure there's data associated to this flow
250
+ return nil if redirect_invalid_flow!
251
+
252
+ # We need to know where we came from
253
+ last_step = fetch_last_step_name
254
+
255
+ # Make sure that the step has an outcome defined for this event
256
+ raise(WebFlowError.new,
257
+ "No outcome has been defined for the event '#{event_name}' on the step named '#{last_step}' as specified in the submitted data. Use the 'on' method on your mapped step or make sure that you are submitting valid data.") unless step_registry.fetch(last_step).has_an_outcome_for?(event_name)
258
+
259
+ # Before doing anything, we create a new state so we can restore the previous one with
260
+ # the back button.
261
+ #serialize
262
+
263
+ # Call the resulting step associated to this event
264
+ execute_step step_registry.fetch(last_step).outcome(event_name)
265
+
266
+ else
267
+
268
+ # Notify plugins we've just started a new flow
269
+ #notify :before_new_flow
270
+
271
+ # Make sure there's a start_step defined
272
+ raise(WebFlowError.new,
273
+ "Your controller must declare a start step name. Use 'start_with :step_name' and define this step in the mapping.") if start_step.nil?
274
+
275
+ # Make sure there's an end_step defined
276
+ raise(WebFlowError.new,
277
+ "Your controller must declare an end step name. Use 'end_with :step_name' and define this step in the mapping. I suggest using a view step which could redirect if you don't want to create a 'thank you' screen.") if end_step.nil?
278
+
279
+ # Start a new flow session storage
280
+ start_new_flow_session_storage
281
+
282
+ # Notify plugins
283
+ #notify :before_step_execution_chain
284
+
285
+ # Execute the start_step
286
+ execute_step start_step
287
+
288
+ end
289
+
290
+ # Notify plugins
291
+ #notify :after_step_execution_chain
292
+
293
+ # Cleanup the flows which have been hanging too long in the session placeholder
294
+ cleanup
295
+
296
+ # Check if we continue
297
+ terminate if @end_step_reached
298
+
299
+ # Execute all the cached render calls
300
+ render!
301
+
302
+ # Execute all the cached redirect calls
303
+ redirect!
304
+
305
+ # Rescue interruptions thrown by the plugins
306
+ #rescue PluginInterruptionError => error
307
+
308
+ # We execute the interruption block
309
+ #instance_eval &error.block
310
+
311
+ end
312
+ end
313
+
314
+
315
+ protected
316
+
317
+
318
+ # Overrides the calls to the 'render' method so that renders are delayed
319
+ # until the end of the execution chain.
320
+ #
321
+ # Prevents conflicts between regular rendering and subsequent plugin interruptions.
322
+ #
323
+ def render(options = nil, deprecated_status = nil, &block)
324
+
325
+ # Cache the parameters
326
+ @renders.push( :options => options, :deprecated_status => deprecated_status, :block => block )
327
+
328
+ end
329
+
330
+
331
+ # Executes the 'render' method calls intercepted while executing the
332
+ # step execution chain.
333
+ def render!
334
+
335
+ @renders.each do |parameters|
336
+
337
+ old_render parameters[:options], parameters[:deprecated_status] do parameters[:block].call end
338
+
339
+ end
340
+
341
+ end
342
+
343
+
344
+ # Overrides the calls to the 'redirect_to' method so that redirects are delayed
345
+ # until the end of the execution chain.
346
+ #
347
+ # Prevents conflicts between regular redirection and subsequent plugin interruptions.
348
+ #
349
+ def redirect_to(options = {}, response_status = {})
350
+
351
+ # Cache the parameters
352
+ @redirects.push( :options => options, :response_status => response_status )
353
+
354
+ end
355
+
356
+
357
+ # Executes the 'render' method calls intercepted while executing the
358
+ # step execution chain.
359
+ def redirect!
360
+
361
+ @redirects.each do |parameters|
362
+
363
+ old_redirect_to parameters[:options], parameters[:response_status]
364
+
365
+ end
366
+
367
+ end
368
+
369
+
370
+ # Adds a step name to the registry and associates it to an object
371
+ # which will be used to handle a given step when necessary.
372
+ def register(step_name, step)
373
+ step_registry.store(step_name.to_s, step)
374
+ end
375
+
376
+
377
+ # Called in a mapping to define the end point of the flow. Once this
378
+ # step finishes it's execution, the flow data is deleted.
379
+ # Pass it a symbol which bears the same name as a defined step.
380
+ # ie.
381
+ #
382
+ # end_with :defined_step_name
383
+ #
384
+ # would point to the definition :
385
+ #
386
+ # view_state :defined_step_name do
387
+ # (...)
388
+ # end
389
+ #
390
+ def end_with(step_name)
391
+ @_end_step = step_name.to_s
392
+ end
393
+
394
+
395
+ # Called in a mapping to define the starting point of the flow.
396
+ # Pass it a symbol which bears the same name as a defined step.
397
+ # ie.
398
+ #
399
+ # start_with :defined_step_name
400
+ #
401
+ # would point to the definition :
402
+ #
403
+ # view_state :defined_step_name do
404
+ # (...)
405
+ # end
406
+ #
407
+ def start_with(step_name)
408
+ @_start_step = step_name.to_s
409
+ end
410
+
411
+
412
+ # Called in a mapping to define which method should be called
413
+ # if the user submits an invalid flow id. Use it as :
414
+ #
415
+ # redirect_invalid_flows :url => { :controller => :my_controller, :action => :index }
416
+ #
417
+ def redirect_invalid_flows( url={:action=>:index} )
418
+ @_no_flow_url = url
419
+ end
420
+
421
+
422
+ # Allows the controller to handle errors which were not handled by
423
+ # the steps themselves.
424
+ #
425
+ # class MyController < WebFlow::Base
426
+ # def initialize
427
+ # (...)
428
+ # upon :WebFlowError => :step_name
429
+ # end
430
+ # (...)
431
+ # end
432
+ #
433
+ # This is also possible :
434
+ #
435
+ # class MyController < WebFlow::Base
436
+ # def initialize
437
+ # (...)
438
+ # upon { :WebFlowError => :step_name,
439
+ # :WhateverError => :step_name }
440
+ # end
441
+ # (...)
442
+ # end
443
+ #
444
+ # Only subclasses of StandardError will be rescued. This means that RuntimeError
445
+ # cannot be handled.
446
+ #
447
+ def upon(hash)
448
+
449
+ # Make sure we received a hash
450
+ 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)
451
+
452
+ # Make sure the key is not used twice in a definition
453
+ # to enforce coherence of the mapping.
454
+ hash.each_key do |key|
455
+ raise(WebFlowError.new, "An error of the class '#{key}' is already mapped. They must be unique within a controller scope.") if handlers.has_key?(key.to_s)
456
+ end
457
+
458
+ # Store the values in the handlers hash
459
+ hash.each { |key,value| handlers.store key.to_s, value.to_s }
460
+
461
+ end
462
+
463
+
464
+ # Helper method called from subclasses to return events.
465
+ #
466
+ # Use it as :
467
+ #
468
+ # def my_action
469
+ #
470
+ # # Do something
471
+ #
472
+ # # Now return an event
473
+ # event :success
474
+ #
475
+ # end
476
+ #
477
+ def event(name)
478
+ WebFlow::Event.new(name.to_s)
479
+ end
480
+
481
+
482
+ private
483
+
484
+ # Launches the execution of a given step name. Encountered errors and events
485
+ # will be managed as defined in the mapping.
486
+ #
487
+ # If a step returns one of the reserved event names defined in the class variable,
488
+ # the execution chain will be stopped and the event will be returned to the
489
+ # caller of the execute_step method.
490
+ #
491
+ def execute_step(step_name)
492
+
493
+ # Notify plugins we've just started a new flow
494
+ #notify :before_any_step_execution
495
+
496
+ @step_name = step_name = step_name.to_s
497
+
498
+ # Make sure that this step is registered
499
+ raise(WebFlowError.new, "The desired step ( #{step_name} ) is not present in the mapping.") unless step_registry.has_key? step_name
500
+
501
+ # Validate that the controller will answer to the message
502
+ raise(WebFlowError.new, "Your controller doesn't have a method called '#{step_name}' as defined in your mapping.") if !respond_to?(step_name) && step_registry.fetch(step_name).definition_required?
503
+
504
+ # Everything is ready for execution, we just have to memorize that this step
505
+ # was called.
506
+ persist_last_step_name step_name
507
+
508
+ # Encapsulate the execution in a begin block to manage
509
+ # the errors. Runtime errors are not managed. Only subclasses
510
+ # of StandardError will be.
511
+ begin
512
+
513
+ # Execute the step and keep the returned value
514
+ return_value = step_registry.fetch(step_name).send :execute, self
515
+
516
+ # Notify plugins we've just started a new flow
517
+ #notify :after_any_step_execution
518
+
519
+ # Check if this was the end step, if so, cut the execution short
520
+ if end_step.to_s == fetch_last_step_name
521
+
522
+ # Raise that flag
523
+ @end_step_reached = true
524
+
525
+ #Notify plugins
526
+ #notify :after_end_step
527
+
528
+ # Terminate execution chain
529
+ return
530
+ end
531
+
532
+ # Make sure that the step returned an event object
533
+ raise(WebFlowError.new, "Your step definition named '#{step_name}' didn't return an WebFlow::Event object. All steps must return either events or errors.") unless return_value.kind_of? WebFlow::Event
534
+
535
+ # If the event name was reserved in the class registry by someone, ignore the
536
+ # event and return the event to the caller
537
+ if self.class.reserved_event? return_value.name
538
+
539
+ return return_value
540
+
541
+ else
542
+
543
+ # This event name wasn't reserved, so let's call the outcome.
544
+
545
+ # Make sure that the step has an outcome defined for this event
546
+ raise(WebFlowError.new, "No outcome has been defined for the event '#{return_value.name}'. Use the 'on' method on your mapped step.") unless step_registry.fetch(step_name).has_an_outcome_for?(return_value.name.to_s)
547
+
548
+ # Call recursively the step mapped to the returned event
549
+ value = execute_step step_registry.fetch(step_name).outcome(return_value.name)
550
+
551
+ return value
552
+
553
+ end
554
+
555
+ # Rescue any subclass of StandardError
556
+ rescue StandardError => error
557
+
558
+ # Ask the step if he handles this error
559
+ if step_registry.fetch(step_name).handles? error.class.to_s
560
+
561
+ # Notify plugins
562
+ #notify :upon_handled_error
563
+
564
+ # Get the handler step name
565
+ handler_name = step_registry.fetch(step_name).handler(error.class.to_s)
566
+
567
+ # Make sure this handler is in the registry
568
+ raise(WebFlowError.new, "The error handling step ( #{handler_name} ) is not present in the mapping.") unless step_registry.has_key? handler_name
569
+
570
+ # Execute the error handling step recursively
571
+ execute_step handler_name
572
+
573
+ # Check if the controller has something to say about it.
574
+ elsif handlers.has_key? error.class.to_s
575
+
576
+ # Notify plugins
577
+ #notify :upon_handled_error
578
+
579
+ # Get the handler step name
580
+ handler_name = handlers.fetch error.class.to_s
581
+
582
+ # Make sure this handler is in the registry
583
+ # Execute the error handling step recursively if present
584
+ step_registry.has_key?(handler_name) ? execute_step(handler_name) : raise(WebFlowError.new, "The error handling step ( #{handler_name} ) is not present in the mapping.")
585
+
586
+
587
+ # Nobody to route to
588
+ else
589
+
590
+ # Notify plugins
591
+ #notify :upon_unhandled_error
592
+
593
+ # Well, we've tried. It's now an official 'Someone Else Problem'
594
+ raise error
595
+
596
+ end
597
+
598
+ end
599
+
600
+ # Notify plugins
601
+ notify :after_any_step_execution
602
+
603
+ end
604
+
605
+
606
+ # Called in a mapping to define which method should be called
607
+ # if the user submits an invalid flow id
608
+ def redirect_invalid_flow!
609
+
610
+ unless validate_flow_existence
611
+
612
+ unless @_no_flow_url ||= nil
613
+
614
+ raise(WebFlowError.new, "No flow data could be found for the given flow key. Your session doesn't exist.")
615
+
616
+ else
617
+
618
+ redirect_to @_no_flow_url
619
+
620
+ return true
621
+
622
+ end
623
+
624
+ end
625
+
626
+ false
627
+
628
+ end
629
+
630
+
631
+ # Makes the step_registry available and instantiates it,
632
+ # if necessary.
633
+ def step_registry
634
+ @_step_registry ||= {}
635
+ end
636
+
637
+ # Makes the handlers available and instantiates it
638
+ # if necessary.
639
+ def handlers
640
+ @_handlers ||= {}
641
+ end
642
+
643
+ # Makes the start_step available and instantiates it
644
+ # if necessary.
645
+ def start_step
646
+ @_start_step ||= nil
647
+ end
648
+
649
+ # Makes the end_step available and instantiates it
650
+ # if necessary.
651
+ def end_step
652
+ @_end_step ||= nil
653
+ end
654
+
655
+ # Returns the array of event names which cannot be mapped by users.
656
+ # These names are class level, so they are shared by all the instances
657
+ # of WebFlow::Base.
658
+ def self::reserved_events
659
+
660
+ # Holds the reserved event names that cannot be mapped by users.
661
+ @@_reserved_events ||= Array.new
662
+
663
+ end
664
+
665
+
666
+ # This method returns a basic set of internal filter handles on which the plugin
667
+ # system can tap into. When developing a plugin, we can call the WebFlow::Base.listen
668
+ # class method to get called upon the encounter of a given internal event.
669
+ def self::filters
670
+
671
+ @@_filters ||= { 'request_entry' => Array.new,
672
+ 'before_new_flow' => Array.new,
673
+ 'before_flow_resume' => Array.new,
674
+ 'before_any_step_execution' => Array.new,
675
+ 'after_any_step_execution' => Array.new,
676
+ 'before_step_execution_chain' => Array.new,
677
+ 'after_step_execution_chain' => Array.new,
678
+ 'upon_handled_error' => Array.new,
679
+ 'upon_unhandled_error' => Array.new,
680
+ 'after_end_step' => Array.new
681
+ }
682
+
683
+ end
684
+
685
+
686
+ # Internal method used to notify all the listeners of a given event that
687
+ # we just reached one of the internal events
688
+ def notify(internal_event_name)
689
+
690
+ # Get the array of listeners for a given event
691
+ WebFlow::Base.filters.has_key?(internal_event_name.to_s) ? listeners = WebFlow::Base.filters.fetch(internal_event_name.to_s) : raise(WebFlowError.new, "Internal programming error...")
692
+
693
+ # Iterate over the listeners
694
+ listeners.each do |plugin|
695
+
696
+ # Notify each one of the event
697
+ plugin.send :notify, WebFlow::SystemEvent.new(internal_event_name.to_s, self, current_flow_data, request, response, session)
698
+
699
+ end
700
+
701
+ end
702
+
703
+
704
+ # Searches the parameters for the event name passed from the view.
705
+ # Returns nil if it can't be found.
706
+ def url_event_value(param_hash)
707
+
708
+ # Iterate over the parameters received
709
+ param_hash.each do |key,value|
710
+
711
+ # Return regex group 1 if we have a match
712
+ return $1 if key.to_s =~ Event_input_name_regex
713
+
714
+ end
715
+
716
+ # We didn't find any event name, return nil as the contract says.
717
+ nil
718
+
719
+ end
720
+
721
+ end
722
+
723
+ end