merb-core 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG +925 -0
  2. data/CONTRIBUTORS +93 -0
  3. data/PUBLIC_CHANGELOG +85 -0
  4. data/Rakefile +18 -28
  5. data/bin/merb +34 -5
  6. data/lib/merb-core/autoload.rb +2 -3
  7. data/lib/merb-core/bootloader.rb +60 -66
  8. data/lib/merb-core/config.rb +7 -1
  9. data/lib/merb-core/controller/abstract_controller.rb +35 -21
  10. data/lib/merb-core/controller/merb_controller.rb +15 -42
  11. data/lib/merb-core/controller/mixins/authentication.rb +42 -6
  12. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  13. data/lib/merb-core/controller/mixins/render.rb +3 -3
  14. data/lib/merb-core/core_ext/kernel.rb +6 -19
  15. data/lib/merb-core/dispatch/cookies.rb +96 -80
  16. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
  17. data/lib/merb-core/dispatch/request.rb +18 -16
  18. data/lib/merb-core/dispatch/router/route.rb +6 -0
  19. data/lib/merb-core/dispatch/router.rb +4 -1
  20. data/lib/merb-core/dispatch/session/container.rb +64 -0
  21. data/lib/merb-core/dispatch/session/cookie.rb +91 -101
  22. data/lib/merb-core/dispatch/session/memcached.rb +38 -174
  23. data/lib/merb-core/dispatch/session/memory.rb +62 -208
  24. data/lib/merb-core/dispatch/session/store_container.rb +145 -0
  25. data/lib/merb-core/dispatch/session.rb +174 -48
  26. data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
  27. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  28. data/lib/merb-core/rack.rb +1 -0
  29. data/lib/merb-core/script.rb +112 -0
  30. data/lib/merb-core/server.rb +2 -0
  31. data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
  32. data/lib/merb-core/test/helpers/request_helper.rb +40 -3
  33. data/lib/merb-core/test/run_specs.rb +4 -3
  34. data/lib/merb-core/vendor/facets/inflect.rb +7 -10
  35. data/lib/merb-core/version.rb +1 -1
  36. data/lib/merb-core.rb +11 -40
  37. data/spec/private/core_ext/kernel_spec.rb +0 -11
  38. data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
  39. data/spec/private/router/fixture/log/merb_test.log +12 -1728
  40. data/spec/private/router/route_spec.rb +4 -0
  41. data/spec/private/router/router_spec.rb +8 -0
  42. data/spec/private/vendor/facets/plural_spec.rb +1 -1
  43. data/spec/private/vendor/facets/singular_spec.rb +1 -1
  44. data/spec/public/abstract_controller/controllers/display.rb +8 -2
  45. data/spec/public/abstract_controller/controllers/filters.rb +18 -0
  46. data/spec/public/abstract_controller/display_spec.rb +6 -2
  47. data/spec/public/abstract_controller/filter_spec.rb +4 -0
  48. data/spec/public/controller/authentication_spec.rb +114 -43
  49. data/spec/public/controller/base_spec.rb +8 -0
  50. data/spec/public/controller/conditional_get_spec.rb +100 -0
  51. data/spec/public/controller/config/init.rb +1 -1
  52. data/spec/public/controller/controllers/authentication.rb +29 -0
  53. data/spec/public/controller/controllers/base.rb +13 -0
  54. data/spec/public/controller/controllers/conditional_get.rb +35 -0
  55. data/spec/public/controller/controllers/cookies.rb +10 -1
  56. data/spec/public/controller/cookies_spec.rb +38 -9
  57. data/spec/public/controller/spec_helper.rb +1 -0
  58. data/spec/public/controller/url_spec.rb +70 -1
  59. data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
  60. data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
  61. data/spec/public/rack/csrf_middleware_spec.rb +70 -0
  62. data/spec/public/reloading/directory/log/merb_test.log +52 -0
  63. data/spec/public/request/request_spec.rb +19 -1
  64. data/spec/public/router/fixation_spec.rb +26 -4
  65. data/spec/public/router/fixture/log/merb_test.log +234 -30332
  66. data/spec/public/session/controllers/sessions.rb +52 -0
  67. data/spec/public/session/cookie_session_spec.rb +73 -0
  68. data/spec/public/session/memcached_session_spec.rb +31 -0
  69. data/spec/public/session/memory_session_spec.rb +28 -0
  70. data/spec/public/session/multiple_sessions_spec.rb +74 -0
  71. data/spec/public/session/no_session_spec.rb +12 -0
  72. data/spec/public/session/session_spec.rb +91 -0
  73. data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
  74. data/spec/public/test/request_helper_spec.rb +15 -0
  75. data/spec/spec_helper.rb +2 -2
  76. metadata +23 -5
  77. data/spec/private/dispatch/cookies_spec.rb +0 -219
  78. data/spec/private/dispatch/session_mixin_spec.rb +0 -47
@@ -1,184 +1,48 @@
1
1
  module Merb
2
2
 
3
- module SessionMixin
4
-
5
- # Adds a before and after dispatch hook for setting up the memcached
6
- # session store.
7
- #
8
- # ==== Parameters
9
- # base<Class>:: The class to which the SessionMixin is mixed into.
10
- def setup_session
11
- orig_key = cookies[_session_id_key]
12
- session, key = Merb::MemCacheSession.persist(orig_key)
13
- request.session = session
14
- @_fingerprint = Marshal.dump(request.session.data).hash
15
- if key != orig_key
16
- set_session_id_cookie(key)
17
- end
18
- end
19
-
20
- # Finalizes the session by storing the session ID in a cookie, if the
21
- # session has changed.
22
- def finalize_session
23
- if @_fingerprint != Marshal.dump(request.session.data).hash
24
- begin
25
- CACHE.set("session:#{request.session.session_id}", request.session.data)
26
- rescue => err
27
- Merb.logger.debug("MemCache Error: #{err.message}")
28
- Merb::SessionMixin::finalize_session_exception_callbacks.each {|x| x.call(err) }
29
- end
30
- end
31
- if request.session.needs_new_cookie or @_new_cookie
32
- set_session_id_cookie(request.session.session_id)
33
- end
34
- end
35
-
36
- # ==== Returns
37
- # String:: The session store type, i.e. "memcache".
38
- def session_store_type
39
- "memcache"
40
- end
41
- end
42
-
43
- ##
44
3
  # Sessions stored in memcached.
45
4
  #
46
5
  # Requires setup in your +init.rb+.
47
6
  #
48
- # require 'memcache'
49
- # CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
50
- #
51
- # And a setting in +init.rb+:
52
- #
53
- # c[:session_store] = 'memcache'
54
- #
55
- # If you are using the memcached gem instead of memcache-client, you must setup like this:
56
- #
57
- # require 'memcached'
58
- # CACHE = Memcached.new('127.0.0.1:11211', { :namespace => 'my_app' })
59
- #
60
- class MemCacheSession
61
-
62
- attr_accessor :session_id
63
- attr_accessor :data
64
- attr_accessor :needs_new_cookie
65
-
66
- # ==== Parameters
67
- # session_id<String>:: A unique identifier for this session.
68
- def initialize(session_id)
69
- @session_id = session_id
70
- @data = {}
71
- end
72
-
73
- class << self
74
-
75
- # Generates a new session ID and creates a new session.
76
- #
77
- # ==== Returns
78
- # MemCacheSession:: The new session.
79
- def generate
80
- sid = Merb::SessionMixin::rand_uuid
81
- new(sid)
82
- end
83
-
84
- # ==== Parameters
85
- # session_id<String:: The ID of the session to retrieve.
86
- #
87
- # ==== Returns
88
- # Array::
89
- # A pair consisting of a MemCacheSession and the session's ID. If no
90
- # sessions matched session_id, a new MemCacheSession will be generated.
91
- #
92
- # ==== Notes
93
- # If there are persiste exceptions callbacks to execute, they all get executed
94
- # when Memcache library raises an exception.
95
- def persist(session_id)
96
- unless session_id.blank?
97
- begin
98
- session = CACHE.get("session:#{session_id}")
99
- rescue => err
100
- Merb.logger.warn!("Could not persist session to MemCache: #{err.message}")
101
- Merb::SessionMixin::persist_exception_callbacks.each {|x| x.call(err) }
102
- end
103
- if session.nil?
104
- # Not in memcached, but assume that cookie exists
105
- session = new(session_id)
106
- end
107
- else
108
- # No cookie...make a new session_id
109
- session = generate
110
- end
111
- if session.is_a?(MemCacheSession)
112
- [session, session.session_id]
113
- else
114
- # recreate using the rails session as the data
115
- session_object = MemCacheSession.new(session_id)
116
- session_object.data = session
117
- [session_object, session_object.session_id]
118
- end
119
- end
120
-
121
- # Don't try to reload in dev mode.
122
- def reloadable?
123
- false
124
- end
125
-
126
- end
127
-
128
- # Regenerate the session ID.
129
- def regenerate
130
- @session_id = Merb::SessionMixin::rand_uuid
131
- self.needs_new_cookie=true
132
- end
133
-
134
- # Recreates the cookie with the default expiration time. Useful during log
135
- # in for pushing back the expiration date.
136
- def refresh_expiration
137
- self.needs_new_cookie=true
138
- end
139
-
140
- # Deletes the session by emptying stored data.
141
- def delete
142
- @data = {}
143
- end
144
-
145
- # ==== Returns
146
- # Boolean:: True if session has been loaded already.
147
- def loaded?
148
- !! @data
149
- end
150
-
151
- # ==== Parameters
152
- # k<~to_s>:: The key of the session parameter to set.
153
- # v<~to_s>:: The value of the session parameter to set.
154
- def []=(k, v)
155
- @data[k] = v
156
- end
157
-
158
- # ==== Parameters
159
- # k<~to_s>:: The key of the session parameter to retrieve.
160
- #
161
- # ==== Returns
162
- # String:: The value of the session parameter.
163
- def [](k)
164
- @data[k]
165
- end
166
-
167
- # Yields the session data to an each block.
168
- #
169
- # ==== Parameter
170
- # &b:: The block to pass to each.
171
- def each(&b)
172
- @data.each(&b)
173
- end
174
-
175
- private
7
+ # Merb::BootLoader.after_app_loads do
8
+ # require 'memcached'
9
+ # Merb::MemcacheSession.store =
10
+ # Memcached.new('127.0.0.1:11211', { :namespace => 'my_app' })
11
+ # end
12
+
13
+ class MemcacheSession < SessionStoreContainer
14
+
15
+ # The session store type
16
+ self.session_store_type = :memcache
17
+
18
+ end
176
19
 
177
- # Attempts to redirect any messages to the data object.
178
- def method_missing(name, *args, &block)
179
- @data.send(name, *args, &block)
180
- end
20
+ end
181
21
 
22
+ class Memcached
23
+
24
+ # Make the Memcached gem conform to the SessionStoreContainer interface
25
+
26
+ # ==== Parameters
27
+ # session_id<String>:: ID of the session to retrieve.
28
+ #
29
+ # ==== Returns
30
+ # ContainerSession:: The session corresponding to the ID.
31
+ def retrieve_session(session_id)
32
+ get("session:#{session_id}")
182
33
  end
183
-
34
+
35
+ # ==== Parameters
36
+ # session_id<String>:: ID of the session to set.
37
+ # data<ContainerSession>:: The session to set.
38
+ def store_session(session_id, data)
39
+ set("session:#{session_id}", data)
40
+ end
41
+
42
+ # ==== Parameters
43
+ # session_id<String>:: ID of the session to delete.
44
+ def delete_session(session_id)
45
+ delete("session:#{session_id}")
46
+ end
47
+
184
48
  end
@@ -1,37 +1,5 @@
1
1
  module Merb
2
-
3
- module SessionMixin
4
-
5
- # Adds a before and after dispatch hook for setting up the memory session
6
- # store.
7
- #
8
- # ==== Parameters
9
- # base<Class>:: The class to which the SessionMixin is mixed into.
10
- def setup_session
11
- orig_key = cookies[_session_id_key]
12
- session, key = Merb::MemorySession.persist(orig_key)
13
- request.session = session
14
- if key != orig_key
15
- set_session_id_cookie(key)
16
- end
17
- end
18
-
19
- # Finalizes the session by storing the session ID in a cookie, if the
20
- # session has changed.
21
- def finalize_session
22
- if request.session.needs_new_cookie or @_new_cookie
23
- set_session_id_cookie(request.session.session_id)
24
- end
25
- end
26
-
27
- # ==== Returns
28
- # String:: The session store type, i.e. "memory".
29
- def session_store_type
30
- "memory"
31
- end
32
- end
33
2
 
34
- ##
35
3
  # Sessions stored in memory.
36
4
  #
37
5
  # Set it up by adding the following to your init file:
@@ -43,199 +11,85 @@ module Merb
43
11
  #
44
12
  # Sessions will remain in memory until the server is stopped or the time
45
13
  # as set in :memory_session_ttl expires.
46
- class MemorySession
47
-
48
- attr_accessor :session_id
49
- attr_accessor :data
50
- attr_accessor :needs_new_cookie
51
-
52
- # ==== Parameters
53
- # session_id<String>:: A unique identifier for this session.
54
- def initialize(session_id)
55
- @session_id = session_id
56
- @data = {}
57
- end
58
-
59
- class << self
60
-
61
- # Generates a new session ID and creates a new session.
62
- #
63
- # ==== Returns
64
- # MemorySession:: The new session.
65
- def generate
66
- sid = Merb::SessionMixin::rand_uuid
67
- MemorySessionContainer[sid] = new(sid)
68
- end
69
-
70
- # ==== Parameters
71
- # session_id<String:: The ID of the session to retrieve.
72
- #
73
- # ==== Returns
74
- # Array::
75
- # A pair consisting of a MemorySession and the session's ID. If no
76
- # sessions matched session_id, a new MemorySession will be generated.
77
- def persist(session_id)
78
- if session_id
79
- session = MemorySessionContainer[session_id]
80
- end
81
- unless session
82
- session = generate
83
- end
84
- [session, session.session_id]
85
- end
86
-
87
- end
88
-
89
- # Regenerate the Session ID
90
- def regenerate
91
- new_sid = Merb::SessionMixin::rand_uuid
92
- old_sid = @session_id
93
- MemorySessionContainer[new_sid] = MemorySessionContainer[old_sid]
94
- @session_id = new_sid
95
- MemorySessionContainer.delete(old_sid)
96
- self.needs_new_cookie=true
97
- end
98
-
99
- # Recreates the cookie with the default expiration time. Useful during log
100
- # in for pushing back the expiration date.
101
- def refresh_expiration
102
- self.needs_new_cookie=true
103
- end
14
+ class MemorySession < SessionStoreContainer
104
15
 
105
- # Deletes the session by emptying stored data.
106
- def delete
107
- @data = {}
16
+ # The session store type
17
+ self.session_store_type = :memory
18
+
19
+ # Bypass normal implicit class attribute reader - see below.
20
+ def store
21
+ self.class.store
108
22
  end
109
-
110
- # ==== Returns
111
- # Boolean:: True if session has been loaded already.
112
- def loaded?
113
- !! @data
23
+
24
+ # Lazy load/setup of MemorySessionStore.
25
+ def self.store
26
+ @_store ||= MemorySessionStore.new(Merb::Config[:memory_session_ttl])
114
27
  end
115
28
 
29
+ end
30
+
31
+ # Used for handling multiple sessions stored in memory.
32
+ class MemorySessionStore
33
+
116
34
  # ==== Parameters
117
- # k<~to_s>:: The key of the session parameter to set.
118
- # v<~to_s>:: The value of the session parameter to set.
119
- def []=(k, v)
120
- @data[k] = v
35
+ # ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
36
+ def initialize(ttl=nil)
37
+ @sessions = Hash.new
38
+ @timestamps = Hash.new
39
+ @mutex = Mutex.new
40
+ @session_ttl = ttl || 60*60 # default 1 hour
41
+ start_timer
121
42
  end
122
-
43
+
123
44
  # ==== Parameters
124
- # k<~to_s>:: The key of the session parameter to retrieve.
45
+ # session_id<String>:: ID of the session to retrieve.
125
46
  #
126
47
  # ==== Returns
127
- # String:: The value of the session parameter.
128
- def [](k)
129
- @data[k]
48
+ # ContainerSession:: The session corresponding to the ID.
49
+ def retrieve_session(session_id)
50
+ @mutex.synchronize {
51
+ @timestamps[session_id] = Time.now
52
+ @sessions[session_id]
53
+ }
130
54
  end
131
55
 
132
- # Yields the session data to an each block.
133
- #
134
- # ==== Parameter
135
- # &b:: The block to pass to each.
136
- def each(&b)
137
- @data.each(&b)
56
+ # ==== Parameters
57
+ # session_id<String>:: ID of the session to set.
58
+ # data<ContainerSession>:: The session to set.
59
+ def store_session(session_id, data)
60
+ @mutex.synchronize {
61
+ @timestamps[session_id] = Time.now
62
+ @sessions[session_id] = data
63
+ }
138
64
  end
139
-
140
- private
141
65
 
142
- # Attempts to redirect any messages to the data object.
143
- def method_missing(name, *args, &block)
144
- @data.send(name, *args, &block)
66
+ # ==== Parameters
67
+ # session_id<String>:: ID of the session to delete.
68
+ def delete_session(session_id)
69
+ @mutex.synchronize {
70
+ @timestamps.delete(session_id)
71
+ @sessions.delete(session_id)
72
+ }
145
73
  end
146
74
 
147
- end
148
-
149
- # Used for handling multiple sessions stored in memory.
150
- class MemorySessionContainer
151
- class << self
152
-
153
- # ==== Parameters
154
- # ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
155
- #
156
- # ==== Returns
157
- # MemorySessionContainer:: The new session container.
158
- def setup(ttl=nil)
159
- @sessions = Hash.new
160
- @timestamps = Hash.new
161
- @mutex = Mutex.new
162
- @session_ttl = ttl || 60*60 # default 1 hour
163
- start_timer
164
- self
165
- end
166
-
167
- # Creates a new session based on the options.
168
- #
169
- # ==== Parameters
170
- # opts<Hash>:: The session options (see below).
171
- #
172
- # ==== Options (opts)
173
- # :session_id<String>:: ID of the session to create in the container.
174
- # :data<MemorySession>:: The session to create in the container.
175
- def create(opts={})
176
- self[opts[:session_id]] = opts[:data]
177
- end
178
-
179
- # ==== Parameters
180
- # key<String>:: ID of the session to retrieve.
181
- #
182
- # ==== Returns
183
- # MemorySession:: The session corresponding to the ID.
184
- def [](key)
185
- @mutex.synchronize {
186
- @timestamps[key] = Time.now
187
- @sessions[key]
188
- }
75
+ # Deletes any sessions that have reached their maximum validity.
76
+ def reap_old_sessions
77
+ @timestamps.each do |session_id,stamp|
78
+ delete_session(session_id) if (stamp + @session_ttl) < Time.now
189
79
  end
80
+ GC.start
81
+ end
190
82
 
191
- # ==== Parameters
192
- # key<String>:: ID of the session to set.
193
- # val<MemorySession>:: The session to set.
194
- def []=(key, val)
195
- @mutex.synchronize {
196
- @timestamps[key] = Time.now
197
- @sessions[key] = val
83
+ # Starts the timer that will eventually reap outdated sessions.
84
+ def start_timer
85
+ Thread.new do
86
+ loop {
87
+ sleep @session_ttl
88
+ reap_old_sessions
198
89
  }
199
- end
200
-
201
- # ==== Parameters
202
- # key<String>:: ID of the session to delete.
203
- def delete(key)
204
- @mutex.synchronize {
205
- @sessions.delete(key)
206
- @timestamps.delete(key)
207
- }
208
- end
209
-
210
- # Deletes any sessions that have reached their maximum validity.
211
- def reap_old_sessions
212
- @timestamps.each do |key,stamp|
213
- if stamp + @session_ttl < Time.now
214
- delete(key)
215
- end
216
- end
217
- GC.start
218
- end
219
-
220
- # Starts the timer that will eventually reap outdated sessions.
221
- def start_timer
222
- Thread.new do
223
- loop {
224
- sleep @session_ttl
225
- reap_old_sessions
226
- }
227
- end
228
- end
229
-
230
- # ==== Returns
231
- # Array:: The sessions stored in this container.
232
- def sessions
233
- @sessions
234
90
  end
235
-
236
- end # end singleton class
237
-
238
- end # end MemorySessionContainer
239
- end
91
+ end
92
+
93
+ end
240
94
 
241
- Merb::MemorySessionContainer.setup(Merb::Config[:memory_session_ttl])
95
+ end
@@ -0,0 +1,145 @@
1
+ module Merb
2
+
3
+ class SessionStoreContainer < SessionContainer
4
+
5
+ class_inheritable_accessor :store
6
+ attr_accessor :_fingerprint
7
+
8
+ # The class attribute :store holds a reference to an object that implements
9
+ # the following interface (either as class or instance methods):
10
+ #
11
+ # - retrieve_session(session_id) # returns data as Hash
12
+ # - store_session(session_id, data) # data should be a Hash
13
+ # - delete_session(session_id)
14
+ #
15
+ # You can use this session store directly by assigning to :store in your
16
+ # config/init.rb after_app_loads step, for example:
17
+ #
18
+ # Merb::BootLoader.after_app_loads do
19
+ # SessionStoreContainer.store = BarSession.new(:option => 'value')
20
+ # end
21
+ #
22
+ # Or you can inherit from SessionStoreContainer to create a SessionContainer
23
+ # that delegates to its 'store' attribute.
24
+ #
25
+ # class FooSession < SessionStoreContainer
26
+ #
27
+ # self.store = FooContainer
28
+ #
29
+ # end
30
+ #
31
+ # class FooContainer
32
+ #
33
+ # def self.retrieve_session(session_id)
34
+ # ...
35
+ # end
36
+ #
37
+ # def self.store_session(session_id, data)
38
+ # ...
39
+ # end
40
+ #
41
+ # def self.delete_session(session_id)
42
+ # ...
43
+ # end
44
+ #
45
+ # end
46
+
47
+ # When used directly, report as :store store
48
+ self.session_store_type = :store
49
+
50
+ class << self
51
+
52
+ # Generates a new session ID and creates a new session.
53
+ #
54
+ # ==== Returns
55
+ # SessionStoreContainer:: The new session.
56
+ def generate
57
+ session = new(Merb::SessionMixin.rand_uuid)
58
+ session.needs_new_cookie = true
59
+ session
60
+ end
61
+
62
+ # Setup a new session.
63
+ #
64
+ # ==== Parameters
65
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
66
+ #
67
+ # ==== Returns
68
+ # SessionContainer:: a SessionContainer. If no sessions were found,
69
+ # a new SessionContainer will be generated.
70
+ def setup(request)
71
+ session = retrieve(request.session_id)
72
+ request.session = session
73
+ # TODO Marshal.dump is slow - needs optimization
74
+ session._fingerprint = Marshal.dump(request.session.to_hash).hash
75
+ session
76
+ end
77
+
78
+ private
79
+
80
+ # ==== Parameters
81
+ # session_id<String:: The ID of the session to retrieve.
82
+ #
83
+ # ==== Returns
84
+ # SessionStoreContainer:: SessionStoreContainer instance with the session data. If no
85
+ # sessions matched session_id, a new SessionStoreContainer will be generated.
86
+ #
87
+ # ==== Notes
88
+ # If there are persisted exceptions callbacks to execute, they all get executed
89
+ # when Memcache library raises an exception.
90
+ def retrieve(session_id)
91
+ unless session_id.blank?
92
+ begin
93
+ session_data = store.retrieve_session(session_id)
94
+ rescue => err
95
+ Merb.logger.warn!("Could not retrieve session from #{self.name}: #{err.message}")
96
+ end
97
+ # Not in container, but assume that cookie exists
98
+ session_data = new(session_id) if session_data.nil?
99
+ else
100
+ # No cookie...make a new session_id
101
+ session_data = generate
102
+ end
103
+ if session_data.is_a?(self)
104
+ session_data
105
+ else
106
+ # Recreate using the existing session as the data, when switching
107
+ # from another session type for example, eg. cookie to memcached
108
+ # or when the data is just a hash
109
+ new(session_id).update(session_data)
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ # Teardown and/or persist the current session.
116
+ #
117
+ # ==== Parameters
118
+ # request<Merb::Request>:: The Merb::Request that came in from Rack.
119
+ #
120
+ # ==== Notes
121
+ # The data (self) is converted to a Hash first, since a container might
122
+ # choose to do a full Marshal on the data, which would make it persist
123
+ # attributes like 'needs_new_cookie', which it shouldn't.
124
+ def finalize(request)
125
+ if _fingerprint != Marshal.dump(data = self.to_hash).hash
126
+ begin
127
+ store.store_session(request.session(self.class.session_store_type).session_id, data)
128
+ rescue => err
129
+ Merb.logger.warn!("Could not persist session to #{self.class.name}: #{err.message}")
130
+ end
131
+ end
132
+ if needs_new_cookie || Merb::SessionMixin.needs_new_cookie?
133
+ request.set_session_id_cookie(session_id)
134
+ end
135
+ end
136
+
137
+ # Regenerate the session ID.
138
+ def regenerate
139
+ store.delete_session(self.session_id)
140
+ self.session_id = Merb::SessionMixin.rand_uuid
141
+ store.store_session(self.session_id, self)
142
+ end
143
+
144
+ end
145
+ end