Class: WebFlow::Base
- Inherits:
-
ApplicationController
- Object
- ApplicationController
- WebFlow::Base
- Defined in:
- lib/webflow/base.rb
Overview
The WebFlow framework turns an ActionController into a flow capable controller. It can handle event triggering, the back button and any user defined step. Here are the main concepts.
Concepts
Flow
A flow is a logical procedure which defines how to handle different steps and guide the user through them.
Step
A step is a single unit of processing included inside a flow. All steps have to inherit from WebFlow::FlowStep. They can be used to define one of the following :
-
ViewStep : displays a view and then relays the execution to methods or steps according to the triggered event.
-
ActionStep : calls a method of an WebFlow controller and then relays the execution to methods or steps according to the triggered event.
More step types can be created to extend the WebFlow framework.
Event
An event is nothing more than a symbol which triggers the execution of methods or steps, depending on the mapping. There are reserved events which cannot be mapped by a user inside of a subclass of WebFlow::Base controllers. To ask the controller if an event name has been reserved for internal purposes, use :
WebFlow::Base.reserved_event? :event_name_to_verify
Creating a mapping
The mapping is where you tell your controller how to connect the different steps.
To declare a controller mapping, you must do it in the object’s initialize
method. This method will be automatically called upon the instantiation of your controller.
Simple mapping example
class MyController < WebFlow::Base
def initialize
start_with :step_1
end_with :end_my_flow
redirect_invalid_flows :step_1
upon :StandardError => :end_my_flow
action_step :step_1 do
method :start_my_flow
on :success => :step_2
end
view_step :step_2 do
on :finish => end_my_flow
end
view_step :end_my_flow
end
def start_my_flow
# Nothing to be done
event :success
end
end
Let’s decompose what we just did in this very minimalist controller.
-
The flow starts with the step named
step_1
. -
The flow data will be destroyed once we reach the
end_my_flow
step. -
If the user sends an invalid key or his flow data is expired, we redirect the flow to the
step_1
step. -
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
end_my_flow
step. -
We declared a step named
step_1
. The step is implemented via the method namedstart_my_flow
. If thesuccess
event is returned, we route the flow to the step namedstep_2
. -
We declared a step named
step_2
. If thefinish
event is returned, the step namedend_my_flow
will be called.
There are many other config options which can be used. I strongly suggest reading the whole API documentation.
Reserved events
The WebFlow::Base controller reserves the following event names for itself.
- render : Tells the framework that we must return the control to the view
parser, since one of the 'render' method has been called and we
are now ready to display something.
About Plugins
The WebFlow framework has a plugin system, but is unstable and incomplete. Don’t bother using it for now, unless you want to contribute of course. This class is stable and ready for plugin registration though. The Plugin class is not. To activate the plugins system, go to the #ACTIONFLOW_ROOT/webflow/webflow.rb file and uncomment the plugins initialisation code.
Constant Summary collapse
- Event_input_name_regex =
Defines the regex used to validate the proper format of a submitted event name.
/^#{event_input_name_prefix}#{event_prefix}([a-zA-Z0-9_]*)$/
- @@event_prefix =
Defines the symbols name prefix which are used to identify the next events. As an example, in a view_state, the button used to trigger the save event would be named ‘_event_save’. If we were in an action_state, to trigger the success event, we would ‘return :_event_success’
'_event_'
- @@event_input_name_prefix =
Defines the name of the parameter submitted which contains the next event name.
'_submit'
- @@flow_execution_key_id =
Defines the name of the variable which holds the flow unique state id. As an example, a view_state would add a hidden field named ‘flow_exec_key’, which would contain the key used to store a state in the session.
We could then restore the state by accessing it with : session.fetch( params )
See the SessionHandler for more details
'flow_exec_key'
Class Method Summary collapse
-
.listen(internal_event_name, plugin) ⇒ Object
Class method to tell the WebFlow framework to notify a plugin upon a certain internal event happening.
-
.reserve_event(event_name) ⇒ Object
Class method to tell the WebFlow framework to prevent users from mapping certain event names.
-
.reserved_event?(event_name) ⇒ Boolean
Class method to ask the WebFlow framework if a given event name has been reserved and can’t be mapped by users.
Instance Method Summary collapse
-
#index ⇒ Object
Default method to handle the requests.
-
#old_redirect_to ⇒ Object
Aliases the ‘redirect_to’ method to intercept it and cache it’s execution results.
-
#old_render ⇒ Object
Aliases the ‘render’ method to intercept it and cache it’s execution results.
Class Method Details
.listen(internal_event_name, plugin) ⇒ Object
197 198 199 200 201 202 203 204 205 |
# File 'lib/webflow/base.rb', line 197 def self::listen(internal_event_name, plugin) # Reserve the event passed as a parameter but first validate # that it's a subclass of WebFlow::Plugin #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 ) filters.fetch(internal_event_name.to_s).push(plugin) end |
.reserve_event(event_name) ⇒ Object
181 182 183 184 185 186 187 188 189 |
# File 'lib/webflow/base.rb', line 181 def self::reserve_event(event_name) # Reserve the event passed as a parameter reserved_events.push( event_name.to_s ) # Remove duplicates reserved_events.uniq! end |
.reserved_event?(event_name) ⇒ Boolean
213 214 215 216 217 |
# File 'lib/webflow/base.rb', line 213 def self::reserved_event?(event_name) reserved_events.include? event_name.to_s end |
Instance Method Details
#index ⇒ Object
Default method to handle the requests. All the dispatching is done here.
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/webflow/base.rb', line 221 def index # Holds the calls to the render method to delay their execution @renders = Array.new # Holds the calls to the redirect method to delay it's execution @redirects = Array.new begin # Notify the plugins we got here #notify :request_entry # Tells which was the last state @flow_id = params[flow_execution_key_id] # Flag used to know if we reached the end_step @end_step_reached = false # Check if the flow has already started if @flow_id && !@flow_id.empty? # Notify plugins we've just re-started a flow #notify :before_flow_resume # Get the event name raise(WebFlowError.new, "Your view did not submit properly to an event name.") unless event_name = url_event_value(params) # Make sure there's data associated to this flow return nil if redirect_invalid_flow! # We need to know where we came from last_step = fetch_last_step_name # Make sure that the step has an outcome defined for this event raise(WebFlowError.new, "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) # Before doing anything, we create a new state so we can restore the previous one with # the back button. #serialize # Call the resulting step associated to this event execute_step step_registry.fetch(last_step).outcome(event_name) else # Notify plugins we've just started a new flow #notify :before_new_flow # Make sure there's a start_step defined raise(WebFlowError.new, "Your controller must declare a start step name. Use 'start_with :step_name' and define this step in the mapping.") if start_step.nil? # Make sure there's an end_step defined raise(WebFlowError.new, "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? # Start a new flow session storage start_new_flow_session_storage # Notify plugins #notify :before_step_execution_chain # Execute the start_step execute_step start_step end # Notify plugins #notify :after_step_execution_chain # Cleanup the flows which have been hanging too long in the session placeholder cleanup # Check if we continue terminate if @end_step_reached # Execute all the cached render calls render! # Execute all the cached redirect calls redirect! # Rescue interruptions thrown by the plugins #rescue PluginInterruptionError => error # We execute the interruption block #instance_eval &error.block end end |
#old_redirect_to ⇒ Object
Aliases the ‘redirect_to’ method to intercept it and cache it’s execution results. Doing this allows to delay the calls to the redirect_to method until the end of the current step chain so that if a plugin wishes to interrupt the chain and redirect the request, there won’t be any conflict between previous calls to the redirect_to method.
173 |
# File 'lib/webflow/base.rb', line 173 alias_method :old_redirect_to, :redirect_to |
#old_render ⇒ Object
Aliases the ‘render’ method to intercept it and cache it’s execution results. Doing this allows to delay the calls to the render method until the end of the current step chain so that if a plugin wishes to interrupt the chain and render some other content, there won’t be any conflict between previous calls to the render method.
165 |
# File 'lib/webflow/base.rb', line 165 alias_method :old_render, :render |