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,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