merb-core 0.9.5 → 0.9.6

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