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.
- data/.gitignore +6 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README +0 -0
- data/Rakefile +1 -0
- data/lib/plugins/controller_level_error_handler.rb +79 -0
- data/lib/plugins/flow_resume_validations.rb +48 -0
- data/lib/plugins/new_flow_validations.rb +52 -0
- data/lib/webflow.rb +18 -0
- data/lib/webflow/action_step.rb +114 -0
- data/lib/webflow/base.rb +723 -0
- data/lib/webflow/base_helper.rb +312 -0
- data/lib/webflow/event.rb +82 -0
- data/lib/webflow/exceptions.rb +80 -0
- data/lib/webflow/flow_step.rb +324 -0
- data/lib/webflow/plugin.rb +77 -0
- data/lib/webflow/random_generator.rb +48 -0
- data/lib/webflow/session_handler.rb +315 -0
- data/lib/webflow/state.rb +70 -0
- data/lib/webflow/version.rb +3 -0
- data/lib/webflow/view_step.rb +238 -0
- data/webflow.gemspec +24 -0
- metadata +69 -0
@@ -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
|