webflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,315 @@
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
+ # Simply put, this module makes sure that the operations to the state
23
+ # registry associated to a user are transparent to the business logic.
24
+ #
25
+ # Session and it's flow states are all stored inside a session variable
26
+ # called WebFlow::SessionHandler.session_flow_data_prefix followed by
27
+ # 32 random characters.
28
+ #
29
+
30
+ WebFlow::Base.class_eval do
31
+
32
+ private
33
+
34
+ # Prefix of the variables which will be created in the user session
35
+ # to store the flow data.
36
+ @@session_flow_data_prefix = 'flow_data-'
37
+ cattr_accessor :session_flow_data_prefix
38
+
39
+ # Prefix of the variables which will be created in the flow's user session
40
+ # to store the state data.
41
+ @@session_state_data_prefix = 'state_data-'
42
+ cattr_accessor :session_state_data_prefix
43
+
44
+ # Name of the variables which will be created in the flow's user session
45
+ # to store the user data.
46
+ @@session_user_data_name = 'user_data'
47
+ cattr_accessor :session_user_data_name
48
+
49
+ # Used to detect flow data keys in the session hash
50
+ Session_data_prefix_regex = /^#{session_flow_data_prefix}[0-9]{32}$/
51
+
52
+ # Name of the key in the flow hash stored in the session which tells when was
53
+ # this flow last executed
54
+ @@session_timestamp_placeholder = 'updated_at'
55
+ cattr_accessor :session_timestamp_placeholder
56
+
57
+ # Defines for how long a flow execution data is maintained in the session
58
+ # placeholder.
59
+ @@session_ttl = 1.hour
60
+ cattr_accessor :session_ttl
61
+
62
+ # Defines where to store the name of the last executed step
63
+ @@last_step_placeholder = 'last_step_name'
64
+ cattr_accessor :last_step_placeholder
65
+
66
+ # Cleans the flows data for flows which have not been executed since
67
+ # the value defined by Session_ttl and Session_timestamp_placeholder.
68
+ def cleanup
69
+
70
+ # Iterate over the real session keys to find flow placeholders
71
+ session.keys.each do |key|
72
+
73
+ # Check if the key is a flow placeholder
74
+ if key =~ Session_data_prefix_regex
75
+
76
+ # Delete it if the flow has expired
77
+ session.delete(key) if (session[key].fetch(session_timestamp_placeholder) < (Time.now - session_ttl))
78
+
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+
86
+ # Saves the current flow state data and makes sure that everything will be available
87
+ # to restore the flow state later on.
88
+ #def serialize
89
+ #
90
+ # # Update the flow timestamp
91
+ # update_timestamp
92
+ #
93
+ # # Copy the current state data
94
+ # old_state = (state).deep_copy
95
+ #
96
+ # # Generate a new flow id.
97
+ # # With old flows, we use the first 8 characters of the last execution id
98
+ # # but we generate 56 new ones which we append
99
+ # #
100
+ # # Fix for bug #10559 added. Collisions are prevented.
101
+ # continue = true
102
+ # while continue
103
+ #
104
+ # # Generate a new state id to test
105
+ # new_id = WebFlow::RandomGenerator.random_string(32)
106
+ #
107
+ # # Test it
108
+ # continue = current_flow_data.has_key?(new_id)
109
+ #
110
+ # end
111
+ #
112
+ # # We can update the flow id
113
+ # @flow_id = @flow_id[0, 9] + new_id
114
+ #
115
+ # # Initialize the flow session storage for this execution (state)
116
+ # init_state_session_space
117
+ #
118
+ # # Copy the shadow session state into the new state to freeze it
119
+ # state = old_state.deep_copy
120
+ #
121
+ # # Return nil
122
+ # nil
123
+ #
124
+ #end
125
+
126
+
127
+ #
128
+ # Cleans all data concerning the flow from the session.
129
+ #
130
+ def terminate
131
+
132
+ # Destroy the flow placeholder in the session
133
+ session.delete(flow_session_name)
134
+
135
+ nil
136
+
137
+ end
138
+
139
+
140
+ # Does everything needed to store data concerning a new flow execution
141
+ def start_new_flow_session_storage
142
+
143
+ # With new flows, we define a completely new 32 characters id
144
+ @flow_id = WebFlow::RandomGenerator.random_string(32)
145
+
146
+ # Initialize the flow session storage for the flow itself, since it will
147
+ # then contain all the states
148
+ init_flow_session_space
149
+
150
+ # Initialize the flow session state storage for this execution
151
+ init_state_session_space
152
+
153
+ # Initialize the flow session user storage for this execution
154
+ init_user_session_space
155
+
156
+ end
157
+
158
+
159
+ # Updates the timestamp for the current flow. Is to be called on each session
160
+ # modification.
161
+ def update_timestamp
162
+
163
+ # Update the timestamp for the current flow
164
+ session[flow_session_name].store session_timestamp_placeholder, Time.now
165
+
166
+ end
167
+
168
+
169
+ # Adds the last step name to the current flow data so we can interpret the
170
+ # returned event correctly once the user submits data back.
171
+ def persist_last_step_name(state_name)
172
+
173
+ # Store the last step name in the correct placeholder
174
+ current_flow_data.fetch(state_session_name).store(last_step_placeholder, state_name.to_s)
175
+
176
+ # Return nil
177
+ nil
178
+
179
+ end
180
+
181
+
182
+ # Adds the last step name to the current flow data so we can interpret the
183
+ # returned event correctly once the user submits data back.
184
+ def fetch_last_step_name
185
+
186
+ # Fetch the last step name from the correct placeholder
187
+ current_flow_data.fetch(state_session_name).fetch(last_step_placeholder)
188
+
189
+ end
190
+
191
+
192
+ # Validates that the given flow key corresponds to a stored flow data
193
+ def validate_flow_existence
194
+
195
+ !current_flow_data.nil? && !state.nil?
196
+
197
+ end
198
+
199
+
200
+ # Initializes the session variables needed to save the flow data.
201
+ def init_flow_session_space
202
+
203
+ # The placeholder for the flow will be named...
204
+ flow_placeholder = flow_session_name
205
+
206
+ # Create a new placeholder for this flow data in the real session space
207
+ session[flow_placeholder] = HashWithIndifferentAccess.new
208
+
209
+ # Timestamp the creation time
210
+ update_timestamp
211
+
212
+ # Return nil
213
+ nil
214
+
215
+ end
216
+
217
+
218
+ # Initializes the session variables needed to save the state data.
219
+ def init_state_session_space
220
+
221
+ # Create a new placeholder for this state data in the real session space
222
+ # reserved for this flow data
223
+ current_flow_data.store state_session_name, HashWithIndifferentAccess.new
224
+
225
+ # Return nil
226
+ nil
227
+
228
+ end
229
+
230
+
231
+ # Initializes the session variables needed to save the user data.
232
+ def init_user_session_space
233
+
234
+ # Create a new placeholder for this user data in the real session space
235
+ # reserved for this flow data
236
+ current_flow_data.store session_user_data_name, HashWithIndifferentAccess.new
237
+
238
+ # Return nil
239
+ nil
240
+
241
+ end
242
+
243
+
244
+ # Returns the name of the variable used in the real session data hash
245
+ # to store this flow data
246
+ def flow_session_name
247
+ (session_flow_data_prefix + @flow_id).to_s
248
+ end
249
+
250
+
251
+ # Returns the name of the variable used in the flow session data hash
252
+ # to store this state data
253
+ def state_session_name
254
+ (session_state_data_prefix + @flow_id).to_s
255
+ end
256
+
257
+
258
+ # Returns the current flow data hash from within the
259
+ # user session.
260
+ def current_flow_data
261
+ session[flow_session_name]
262
+ end
263
+
264
+
265
+ # Returns the current flow user data hash from within the user session.
266
+ #
267
+ # @returns current flow user session hash
268
+ #
269
+ def current_flow_user_data
270
+ current_flow_data.fetch session_user_data_name
271
+ end
272
+
273
+
274
+ # Saves or updates data to the current flow session data hash.
275
+ #
276
+ # @params values hash containing values to be added to the flow user session
277
+ # @params options hash containing options
278
+ #
279
+ # @returns nil
280
+ #
281
+ def store_flow_data(values, options = {})
282
+ update = options[:update] || true
283
+
284
+ current_flow_user_data.merge!(values) { |key, val1, val2| update ? val2 : val1 }
285
+
286
+ update_timestamp
287
+
288
+ nil
289
+ end
290
+
291
+
292
+ public
293
+
294
+ # Returns the current flow user data hash from within the user session.
295
+ #
296
+ # @returns current flow user session hash
297
+ #
298
+ def current_flow_user_data
299
+ current_flow_data.fetch session_user_data_name
300
+ end
301
+
302
+ end
303
+
304
+
305
+ # Used to add a deep copy mechanism to objects
306
+ Object.class_eval do
307
+
308
+ # Performs a deep copy of an object and returns a copy of the source and all it's sub objects
309
+ # as copies. Useful for example when a hash contains objects and you want a copy of the Hash
310
+ # and copies of the objects it contains and not the objects themselves
311
+ def deep_copy
312
+ Marshal.load(Marshal.dump(self))
313
+ end
314
+
315
+ end
@@ -0,0 +1,70 @@
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
+ # The State module is simply a method inclusion mechanism which allows
25
+ # the user to store data relevant to a flow state. It is used exactly like
26
+ # the 'session' attribute of the ActionController::Base. See the WebFlow
27
+ # wiki for more details.
28
+
29
+ WebFlow::Base.class_eval do
30
+
31
+
32
+ # TODO Send the state method to the ActionViews
33
+
34
+ protected
35
+
36
+
37
+ # Returns the state data hash
38
+ def state
39
+
40
+ session[ flow_session_name ].fetch( state_session_name )
41
+
42
+ end
43
+
44
+ # Changes the state hash for another one
45
+ def state=(hash)
46
+ session[ flow_session_name ].store( state_session_name, hash )
47
+ state
48
+ end
49
+
50
+ # Does the same as state(k,v)
51
+ #def []=(k, v)
52
+ # return session[ flow_session_name ].fetch( state_session_name ).store( k.to_s, v )
53
+ #end
54
+
55
+
56
+ # Does the same as state(k)
57
+ #def [](k)
58
+
59
+ # return session[ flow_session_name ].fetch( state_session_name ).fetch( k )
60
+ #
61
+ #end
62
+
63
+
64
+ # Adds ActionView helper methods
65
+ def self.included(base)
66
+ base.send :helper_method, :state
67
+ end
68
+ end
69
+
70
+
@@ -0,0 +1,3 @@
1
+ module WebFlow
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,238 @@
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 'flow_step'
22
+ #require 'base'
23
+ #require 'event'
24
+
25
+ module WebFlow
26
+
27
+ # This class allows the controllers to create view steps. It adds the
28
+ # view_step method to the WebFlow::Base class evaluation so we can
29
+ # define view steps from the controllers.
30
+ #
31
+ # == Simple usage
32
+ #
33
+ # view_step :view_name do
34
+ # on :success => :another_step
35
+ # on :back => :yet_another_step
36
+ # end
37
+ #
38
+ # The view name will be stored in the @view_name instance variable
39
+ # until it is needed.
40
+ #
41
+ # If no method corresponding to @view_name is defined inside the
42
+ # calling controller upon execution, the ViewStep tries to render
43
+ # a file with the same name as the @view_step value. Therefore,
44
+ # it is not mandatory to define view_step methods in the flow controller.
45
+ #
46
+ # The view_step instruction can also be used to batch define view steps.
47
+ # Just pass a comma separated list of view names. Note that the mappings
48
+ # defined in the following block will be applied to all the view steps
49
+ # in the given list.
50
+ #
51
+ # view_step :view_name, :another_view_step
52
+ #
53
+ # == Advanced usage
54
+ #
55
+ # You can define how to render the view steps directly in the mapping,
56
+ # instead of doing it in the action itself. This gives a more clean code
57
+ # and separates the rendering concern from the business logic. The to_render
58
+ # instruction does this job. Here's a simple usage.
59
+ #
60
+ # view_step :view_name do
61
+ #
62
+ # (...)
63
+ #
64
+ # to_render :my_event do
65
+ # render 'template/path'
66
+ # end
67
+ # end
68
+ #
69
+ # The to_render instruction tells the view step that upon the returning of the
70
+ # my_event event from the step implementation, we must render the 'template/path'
71
+ # file. As a matter of fact, all the to_render does is to call the given block
72
+ # right after the step definition execution. One could use the to_render mechanism
73
+ # as he wishes and therefore perform actions other than rendering.
74
+ #
75
+ # The to_render can also take many event names as arguments simultaneously,
76
+ # as shown here :
77
+ #
78
+ # view_step :view_name do
79
+ #
80
+ # (...)
81
+ #
82
+ # to_render :my_event, :some_other_event, :yet_more_events do
83
+ # render 'template/path'
84
+ # end
85
+ # end
86
+ #
87
+ # == Inherited instructions
88
+ #
89
+ # The ViewStep class inherits all the standard step methods defined in FlowStep class.
90
+ #
91
+ #
92
+ class ViewStep < WebFlow::FlowStep
93
+
94
+ # Initializes the instance
95
+ def initialize( m_view_name )
96
+
97
+ # Holds the view name to render once the execution is complete
98
+ @view_name = m_view_name
99
+
100
+ # Tells the framework that no method definition is required for
101
+ # this step type
102
+ @definition_required = false
103
+
104
+ end
105
+
106
+
107
+ # This method is required by the WebFlow::Base and will be called
108
+ # once it relays the execution to this step instance
109
+ def execute(*args)
110
+
111
+ # Verify if the controller answers to the @view_name value
112
+ if args[0].respond_to? lookup_method_to_call(@view_name)
113
+
114
+ # The controller defines a method with the same name as @view_name.
115
+ # Kewl! A 'someone else problem' !!!
116
+
117
+ # Launch execution
118
+ result = args[0].send( lookup_method_to_call(@view_name) )
119
+
120
+ else
121
+
122
+ # Do we have to do everything here? lazy user...
123
+
124
+ # Render something but only if the user hasn't
125
+ # defined a custom render block.
126
+ args[0].send :render, :action => lookup_method_to_call(@view_name) unless renders.has_key?('render')
127
+
128
+ # Return the :render event
129
+ result = WebFlow::Event.new(:render)
130
+
131
+ end
132
+
133
+ # Verify if we have to override the render result.
134
+ # Execute if necessary.
135
+ # Also validate that the return type is an event, or let go and it will
136
+ # crash as soon as execution completes anyways...
137
+ if result.kind_of?(WebFlow::Event) && renders.has_key?(result.name)
138
+
139
+ # Execute the render block
140
+ args[0].instance_eval( &renders.fetch( result.name ) )
141
+
142
+ end
143
+
144
+ result
145
+
146
+ end
147
+
148
+
149
+ # Used to specify the render action to be taken by the step.
150
+ # The render result defined here overrides the one defined in the step
151
+ # definition.
152
+ # Use it as :
153
+ #
154
+ # class Controller < WebFlow::Base
155
+ #
156
+ # def initialize
157
+ #
158
+ # to_render [:some_event_name, :some_other_event_name] do
159
+ # render(:action => :whatever )
160
+ # end
161
+ #
162
+ # end
163
+ #
164
+ # end
165
+ #def to_render(*events, &block)
166
+ #
167
+ # # Make sure we have a block as a parameter
168
+ # raise(WebFlow::WebFlowError.new, "The renders instruction takes a block as a parameter. Use the 'do' format. See API.") unless block_given?
169
+ #
170
+ # # Iterate over the events to map
171
+ # events.each do |name|
172
+ #
173
+ # # Associate each event name with the received block
174
+ # renders.store name.to_s, block
175
+ #
176
+ # end
177
+ #
178
+ #end
179
+
180
+
181
+ def on_render(&block)
182
+
183
+ # Make sure we have a block as a parameter
184
+ raise(WebFlow::WebFlowError.new, "The renders instruction takes a block as a parameter. Use the 'do' format. See API.") unless block_given?
185
+
186
+ # Associate render event with the received block
187
+ renders.store "render", block
188
+
189
+ end
190
+
191
+ private
192
+
193
+ # Returns the mapped rendering overrides hash.
194
+ def renders
195
+
196
+ @_renders ||= {}
197
+
198
+ end
199
+
200
+ end
201
+
202
+
203
+ end
204
+
205
+ # Let's create the view_step definition inside the WebFlow::Base
206
+ # controller class
207
+ WebFlow::Base.class_eval do
208
+
209
+ protected
210
+
211
+ # Called to define a step into a flow. Example of usage :
212
+ #
213
+ # view_step :view_name do
214
+ # on :success => :another_step
215
+ # on :back => :yet_another_step
216
+ # end
217
+ #
218
+ # See the ViewStep class documentation for more options.
219
+ #
220
+ def view_step(*view_name, &block)
221
+
222
+ # Instantiate a ViewStep object if symbol or string received
223
+ view_name.each do |name|
224
+
225
+ step = WebFlow::ViewStep.new(name.to_s)
226
+
227
+ # Register this step in the flow repository
228
+ register name.to_s, step
229
+
230
+ # Execute the config block
231
+ step.instance_eval(&block) if block_given?
232
+
233
+ end
234
+
235
+ end
236
+
237
+
238
+ end