webflow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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