merb-core 0.9.2

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 (246) hide show
  1. data/LICENSE +20 -0
  2. data/README +21 -0
  3. data/Rakefile +285 -0
  4. data/TODO +0 -0
  5. data/bin/merb +8 -0
  6. data/bin/merb-specs +5 -0
  7. data/docs/bootloading.dox +57 -0
  8. data/docs/documentation_standards +40 -0
  9. data/docs/new_render_api +51 -0
  10. data/lib/merb-core.rb +304 -0
  11. data/lib/merb-core/autoload.rb +29 -0
  12. data/lib/merb-core/bootloader.rb +601 -0
  13. data/lib/merb-core/config.rb +284 -0
  14. data/lib/merb-core/constants.rb +43 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +531 -0
  16. data/lib/merb-core/controller/exceptions.rb +257 -0
  17. data/lib/merb-core/controller/merb_controller.rb +214 -0
  18. data/lib/merb-core/controller/mime.rb +88 -0
  19. data/lib/merb-core/controller/mixins/controller.rb +262 -0
  20. data/lib/merb-core/controller/mixins/render.rb +324 -0
  21. data/lib/merb-core/controller/mixins/responder.rb +464 -0
  22. data/lib/merb-core/controller/template.rb +205 -0
  23. data/lib/merb-core/core_ext.rb +12 -0
  24. data/lib/merb-core/core_ext/class.rb +192 -0
  25. data/lib/merb-core/core_ext/hash.rb +422 -0
  26. data/lib/merb-core/core_ext/kernel.rb +304 -0
  27. data/lib/merb-core/core_ext/mash.rb +154 -0
  28. data/lib/merb-core/core_ext/object.rb +136 -0
  29. data/lib/merb-core/core_ext/object_space.rb +14 -0
  30. data/lib/merb-core/core_ext/rubygems.rb +28 -0
  31. data/lib/merb-core/core_ext/set.rb +41 -0
  32. data/lib/merb-core/core_ext/string.rb +69 -0
  33. data/lib/merb-core/dispatch/cookies.rb +92 -0
  34. data/lib/merb-core/dispatch/dispatcher.rb +233 -0
  35. data/lib/merb-core/dispatch/exceptions.html.erb +297 -0
  36. data/lib/merb-core/dispatch/request.rb +560 -0
  37. data/lib/merb-core/dispatch/router.rb +141 -0
  38. data/lib/merb-core/dispatch/router/behavior.rb +777 -0
  39. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  40. data/lib/merb-core/dispatch/router/route.rb +212 -0
  41. data/lib/merb-core/dispatch/session.rb +28 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +166 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +161 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +234 -0
  45. data/lib/merb-core/gem_ext/erubis.rb +19 -0
  46. data/lib/merb-core/logger.rb +230 -0
  47. data/lib/merb-core/plugins.rb +25 -0
  48. data/lib/merb-core/rack.rb +15 -0
  49. data/lib/merb-core/rack/adapter.rb +42 -0
  50. data/lib/merb-core/rack/adapter/ebb.rb +22 -0
  51. data/lib/merb-core/rack/adapter/evented_mongrel.rb +24 -0
  52. data/lib/merb-core/rack/adapter/fcgi.rb +16 -0
  53. data/lib/merb-core/rack/adapter/irb.rb +108 -0
  54. data/lib/merb-core/rack/adapter/mongrel.rb +25 -0
  55. data/lib/merb-core/rack/adapter/runner.rb +27 -0
  56. data/lib/merb-core/rack/adapter/thin.rb +27 -0
  57. data/lib/merb-core/rack/adapter/webrick.rb +35 -0
  58. data/lib/merb-core/rack/application.rb +77 -0
  59. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  60. data/lib/merb-core/server.rb +184 -0
  61. data/lib/merb-core/test.rb +10 -0
  62. data/lib/merb-core/test/helpers.rb +9 -0
  63. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  64. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  65. data/lib/merb-core/test/helpers/request_helper.rb +257 -0
  66. data/lib/merb-core/test/helpers/route_helper.rb +33 -0
  67. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  68. data/lib/merb-core/test/matchers.rb +9 -0
  69. data/lib/merb-core/test/matchers/controller_matchers.rb +269 -0
  70. data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
  71. data/lib/merb-core/test/matchers/view_matchers.rb +293 -0
  72. data/lib/merb-core/test/run_specs.rb +38 -0
  73. data/lib/merb-core/test/tasks/spectasks.rb +39 -0
  74. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  75. data/lib/merb-core/test/test_ext/object.rb +14 -0
  76. data/lib/merb-core/vendor/facets.rb +2 -0
  77. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  78. data/lib/merb-core/vendor/facets/inflect.rb +211 -0
  79. data/lib/merb-core/version.rb +11 -0
  80. data/spec/private/config/adapter_spec.rb +32 -0
  81. data/spec/private/config/config_spec.rb +139 -0
  82. data/spec/private/config/environment_spec.rb +13 -0
  83. data/spec/private/config/spec_helper.rb +1 -0
  84. data/spec/private/core_ext/hash_spec.rb +506 -0
  85. data/spec/private/core_ext/kernel_spec.rb +46 -0
  86. data/spec/private/core_ext/object_spec.rb +39 -0
  87. data/spec/private/core_ext/set_spec.rb +26 -0
  88. data/spec/private/core_ext/string_spec.rb +9 -0
  89. data/spec/private/dispatch/cookies_spec.rb +107 -0
  90. data/spec/private/dispatch/dispatch_spec.rb +26 -0
  91. data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
  92. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
  93. data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
  94. data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
  95. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
  96. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
  97. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
  98. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
  99. data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
  100. data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
  101. data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
  102. data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
  103. data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
  104. data/spec/private/dispatch/fixture/config/init.rb +45 -0
  105. data/spec/private/dispatch/fixture/config/rack.rb +1 -0
  106. data/spec/private/dispatch/fixture/config/router.rb +35 -0
  107. data/spec/private/dispatch/fixture/log/development.log +1 -0
  108. data/spec/private/dispatch/fixture/log/merb.4000.pid +1 -0
  109. data/spec/private/dispatch/fixture/log/merb_test.log +2040 -0
  110. data/spec/private/dispatch/fixture/log/production.log +1 -0
  111. data/spec/private/dispatch/fixture/merb.4000.pid +1 -0
  112. data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
  113. data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
  114. data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
  115. data/spec/private/dispatch/route_params_spec.rb +24 -0
  116. data/spec/private/dispatch/spec_helper.rb +1 -0
  117. data/spec/private/plugins/plugin_spec.rb +81 -0
  118. data/spec/private/rack/application_spec.rb +43 -0
  119. data/spec/public/DEFINITIONS +11 -0
  120. data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
  121. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
  122. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
  123. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
  124. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
  125. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  126. data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
  127. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
  128. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
  129. data/spec/public/abstract_controller/controllers/display.rb +54 -0
  130. data/spec/public/abstract_controller/controllers/filters.rb +167 -0
  131. data/spec/public/abstract_controller/controllers/helpers.rb +31 -0
  132. data/spec/public/abstract_controller/controllers/partial.rb +106 -0
  133. data/spec/public/abstract_controller/controllers/render.rb +86 -0
  134. data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
  135. data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
  136. data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
  137. data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
  138. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
  139. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
  140. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
  141. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
  142. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
  143. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  144. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
  145. data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
  146. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
  147. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
  148. data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
  149. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
  150. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
  151. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
  152. data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
  153. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
  154. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
  155. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
  156. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
  157. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
  158. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
  159. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
  160. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
  161. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
  162. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
  163. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
  164. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
  165. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
  166. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
  167. data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
  168. data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
  169. data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
  170. data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
  171. data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
  172. data/spec/public/abstract_controller/display_spec.rb +33 -0
  173. data/spec/public/abstract_controller/filter_spec.rb +80 -0
  174. data/spec/public/abstract_controller/helper_spec.rb +13 -0
  175. data/spec/public/abstract_controller/partial_spec.rb +53 -0
  176. data/spec/public/abstract_controller/render_spec.rb +70 -0
  177. data/spec/public/abstract_controller/spec_helper.rb +27 -0
  178. data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
  179. data/spec/public/boot_loader/spec_helper.rb +1 -0
  180. data/spec/public/controller/base_spec.rb +31 -0
  181. data/spec/public/controller/controllers/base.rb +41 -0
  182. data/spec/public/controller/controllers/display.rb +40 -0
  183. data/spec/public/controller/controllers/responder.rb +67 -0
  184. data/spec/public/controller/controllers/url.rb +7 -0
  185. data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
  186. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
  187. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
  188. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
  189. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
  190. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
  191. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
  192. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
  193. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
  194. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
  195. data/spec/public/controller/display_spec.rb +34 -0
  196. data/spec/public/controller/log/merb.4000.pid +1 -0
  197. data/spec/public/controller/responder_spec.rb +95 -0
  198. data/spec/public/controller/spec_helper.rb +9 -0
  199. data/spec/public/controller/url_spec.rb +152 -0
  200. data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
  201. data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
  202. data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
  203. data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
  204. data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
  205. data/spec/public/directory_structure/directory/config/router.rb +3 -0
  206. data/spec/public/directory_structure/directory/log/merb.4000.pid +1 -0
  207. data/spec/public/directory_structure/directory/log/merb_test.log +265 -0
  208. data/spec/public/directory_structure/directory/merb.4000.pid +1 -0
  209. data/spec/public/directory_structure/directory_spec.rb +44 -0
  210. data/spec/public/logger/logger_spec.rb +175 -0
  211. data/spec/public/logger/spec_helper.rb +1 -0
  212. data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
  213. data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
  214. data/spec/public/reloading/directory/config/init.rb +2 -0
  215. data/spec/public/reloading/directory/log/merb.4000.pid +1 -0
  216. data/spec/public/reloading/directory/log/merb_test.log +59 -0
  217. data/spec/public/reloading/directory/merb.4000.pid +1 -0
  218. data/spec/public/reloading/reload_spec.rb +80 -0
  219. data/spec/public/request/multipart_spec.rb +15 -0
  220. data/spec/public/request/request_spec.rb +207 -0
  221. data/spec/public/router/default_spec.rb +21 -0
  222. data/spec/public/router/deferred_spec.rb +22 -0
  223. data/spec/public/router/namespace_spec.rb +113 -0
  224. data/spec/public/router/nested_resources_spec.rb +34 -0
  225. data/spec/public/router/resource_spec.rb +45 -0
  226. data/spec/public/router/resources_spec.rb +57 -0
  227. data/spec/public/router/spec_helper.rb +72 -0
  228. data/spec/public/router/special_spec.rb +44 -0
  229. data/spec/public/router/string_spec.rb +61 -0
  230. data/spec/public/template/template_spec.rb +92 -0
  231. data/spec/public/template/templates/error.html.erb +2 -0
  232. data/spec/public/template/templates/template.html.erb +1 -0
  233. data/spec/public/template/templates/template.html.myt +1 -0
  234. data/spec/public/test/controller_matchers_spec.rb +378 -0
  235. data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
  236. data/spec/public/test/controllers/dispatch_controller.rb +11 -0
  237. data/spec/public/test/controllers/spec_helper_controller.rb +30 -0
  238. data/spec/public/test/multipart_request_helper_spec.rb +159 -0
  239. data/spec/public/test/multipart_upload_text_file.txt +1 -0
  240. data/spec/public/test/request_helper_spec.rb +153 -0
  241. data/spec/public/test/route_helper_spec.rb +54 -0
  242. data/spec/public/test/route_matchers_spec.rb +133 -0
  243. data/spec/public/test/view_helper_spec.rb +96 -0
  244. data/spec/public/test/view_matchers_spec.rb +107 -0
  245. data/spec/spec_helper.rb +71 -0
  246. metadata +488 -0
@@ -0,0 +1,161 @@
1
+ require 'memcache_util'
2
+ module Merb
3
+
4
+ module SessionMixin #:nodoc:
5
+
6
+ # Adds a before and after dispatch hook for setting up the memcached
7
+ # session store.
8
+ #
9
+ # ==== Parameters
10
+ # base<Class>:: The class to which the SessionMixin is mixed into.
11
+ def setup_session
12
+ before = cookies[_session_id_key]
13
+ request.session, cookies[_session_id_key] = Merb::MemCacheSession.persist(cookies[_session_id_key])
14
+ @_fingerprint = Marshal.dump(request.session.data).hash
15
+ @_new_cookie = cookies[_session_id_key] != before
16
+ end
17
+
18
+ # Finalizes the session by storing the session ID in a cookie, if the
19
+ # session has changed.
20
+ def finalize_session
21
+ if @_fingerprint != Marshal.dump(request.session.data).hash
22
+ ::Cache.put("session:#{request.session.session_id}", request.session.data)
23
+ end
24
+ set_cookie(_session_id_key, request.session.session_id, Time.now + _session_expiry) if (@_new_cookie || request.session.needs_new_cookie)
25
+ end
26
+
27
+ # ==== Returns
28
+ # String:: The session store type, i.e. "memcache".
29
+ def session_store_type
30
+ "memcache"
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Sessions stored in memcached.
36
+ #
37
+ # Requires setup in your +init.rb+:
38
+ #
39
+ # require 'memcache'
40
+ # CACHE = MemCache.new('127.0.0.1:11211', { :namespace => 'my_app' })
41
+ #
42
+ # And a setting in +init.rb+:
43
+ #
44
+ # c[:session_store] = 'memcache'
45
+ class MemCacheSession
46
+
47
+ attr_accessor :session_id
48
+ attr_accessor :data
49
+ attr_accessor :needs_new_cookie
50
+
51
+ # ==== Parameters
52
+ # session_id<String>:: A unique identifier for this session.
53
+ def initialize(session_id)
54
+ @session_id = session_id
55
+ @data = {}
56
+ end
57
+
58
+ class << self
59
+
60
+ # Generates a new session ID and creates a new session.
61
+ #
62
+ # ==== Returns
63
+ # MemCacheSession:: The new session.
64
+ def generate
65
+ sid = Merb::SessionMixin::rand_uuid
66
+ new(sid)
67
+ end
68
+
69
+ # ==== Parameters
70
+ # session_id<String:: The ID of the session to retrieve.
71
+ #
72
+ # ==== Returns
73
+ # Array::
74
+ # A pair consisting of a MemCacheSession and the session's ID. If no
75
+ # sessions matched session_id, a new MemCacheSession will be generated.
76
+ def persist(session_id)
77
+ unless session_id.blank?
78
+ session = ::Cache.get("session:#{session_id}")
79
+ if session.nil?
80
+ # Not in memcached, but assume that cookie exists
81
+ session = new(session_id)
82
+ end
83
+ else
84
+ # No cookie...make a new session_id
85
+ session = generate
86
+ end
87
+ if session.is_a?(MemCacheSession)
88
+ [session, session.session_id]
89
+ else
90
+ # recreate using the rails session as the data
91
+ session_object = MemCacheSession.new(session_id)
92
+ session_object.data = session
93
+ [session_object, session_object.session_id]
94
+ end
95
+
96
+ end
97
+
98
+ # Don't try to reload in dev mode.
99
+ def reloadable? #:nodoc:
100
+ false
101
+ end
102
+
103
+ end
104
+
105
+ # Regenerate the session ID.
106
+ def regenerate
107
+ @session_id = Merb::SessionMixin::rand_uuid
108
+ self.needs_new_cookie=true
109
+ end
110
+
111
+ # Recreates the cookie with the default expiration time. Useful during log
112
+ # in for pushing back the expiration date.
113
+ def refresh_expiration
114
+ self.needs_new_cookie=true
115
+ end
116
+
117
+ # Deletes the session by emptying stored data.
118
+ def delete
119
+ @data = {}
120
+ end
121
+
122
+ # ==== Returns
123
+ # Boolean:: True if session has been loaded already.
124
+ def loaded?
125
+ !! @data
126
+ end
127
+
128
+ # ==== Parameters
129
+ # k<~to_s>:: The key of the session parameter to set.
130
+ # v<~to_s>:: The value of the session parameter to set.
131
+ def []=(k, v)
132
+ @data[k] = v
133
+ end
134
+
135
+ # ==== Parameters
136
+ # k<~to_s>:: The key of the session parameter to retrieve.
137
+ #
138
+ # ==== Returns
139
+ # String:: The value of the session parameter.
140
+ def [](k)
141
+ @data[k]
142
+ end
143
+
144
+ # Yields the session data to an each block.
145
+ #
146
+ # ==== Parameter
147
+ # &b:: The block to pass to each.
148
+ def each(&b)
149
+ @data.each(&b)
150
+ end
151
+
152
+ private
153
+
154
+ # Attempts to redirect any messages to the data object.
155
+ def method_missing(name, *args, &block)
156
+ @data.send(name, *args, &block)
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,234 @@
1
+ module Merb
2
+
3
+ module SessionMixin #:nodoc:
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
+ before = cookies[_session_id_key]
12
+ request.session , cookies[_session_id_key] = Merb::MemorySession.persist(cookies[_session_id_key])
13
+ @_new_cookie = cookies[_session_id_key] != before
14
+ end
15
+
16
+ # Finalizes the session by storing the session ID in a cookie, if the
17
+ # session has changed.
18
+ def finalize_session
19
+ set_cookie(_session_id_key, request.session.session_id, Time.now + _session_expiry) if (@_new_cookie || request.session.needs_new_cookie)
20
+ end
21
+
22
+ # ==== Returns
23
+ # String:: The session store type, i.e. "memory".
24
+ def session_store_type
25
+ "memory"
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Sessions stored in memory.
31
+ #
32
+ # And a setting in +merb.yml+:
33
+ #
34
+ # :session_store: memory
35
+ # :memory_session_ttl: 3600 (in seconds, one hour)
36
+ #
37
+ # Sessions will remain in memory until the server is stopped or the time
38
+ # as set in :memory_session_ttl expires.
39
+ class MemorySession
40
+
41
+ attr_accessor :session_id
42
+ attr_accessor :data
43
+ attr_accessor :needs_new_cookie
44
+
45
+ # ==== Parameters
46
+ # session_id<String>:: A unique identifier for this session.
47
+ def initialize(session_id)
48
+ @session_id = session_id
49
+ @data = {}
50
+ end
51
+
52
+ class << self
53
+
54
+ # Generates a new session ID and creates a new session.
55
+ #
56
+ # ==== Returns
57
+ # MemorySession:: The new session.
58
+ def generate
59
+ sid = Merb::SessionMixin::rand_uuid
60
+ MemorySessionContainer[sid] = new(sid)
61
+ end
62
+
63
+ # ==== Parameters
64
+ # session_id<String:: The ID of the session to retrieve.
65
+ #
66
+ # ==== Returns
67
+ # Array::
68
+ # A pair consisting of a MemorySession and the session's ID. If no
69
+ # sessions matched session_id, a new MemorySession will be generated.
70
+ def persist(session_id)
71
+ if session_id
72
+ session = MemorySessionContainer[session_id]
73
+ end
74
+ unless session
75
+ session = generate
76
+ end
77
+ [session, session.session_id]
78
+ end
79
+
80
+ end
81
+
82
+ # Regenerate the Session ID
83
+ def regenerate
84
+ new_sid = Merb::SessionMixin::rand_uuid
85
+ old_sid = @session_id
86
+ MemorySessionContainer[new_sid] = MemorySessionContainer[old_sid]
87
+ @session_id = new_sid
88
+ MemorySessionContainer.delete(old_sid)
89
+ self.needs_new_cookie=true
90
+ end
91
+
92
+ # Recreates the cookie with the default expiration time. Useful during log
93
+ # in for pushing back the expiration date.
94
+ def refresh_expiration
95
+ self.needs_new_cookie=true
96
+ end
97
+
98
+ # Deletes the session by emptying stored data.
99
+ def delete
100
+ @data = {}
101
+ end
102
+
103
+ # ==== Returns
104
+ # Boolean:: True if session has been loaded already.
105
+ def loaded?
106
+ !! @data
107
+ end
108
+
109
+ # ==== Parameters
110
+ # k<~to_s>:: The key of the session parameter to set.
111
+ # v<~to_s>:: The value of the session parameter to set.
112
+ def []=(k, v)
113
+ @data[k] = v
114
+ end
115
+
116
+ # ==== Parameters
117
+ # k<~to_s>:: The key of the session parameter to retrieve.
118
+ #
119
+ # ==== Returns
120
+ # String:: The value of the session parameter.
121
+ def [](k)
122
+ @data[k]
123
+ end
124
+
125
+ # Yields the session data to an each block.
126
+ #
127
+ # ==== Parameter
128
+ # &b:: The block to pass to each.
129
+ def each(&b)
130
+ @data.each(&b)
131
+ end
132
+
133
+ private
134
+
135
+ # Attempts to redirect any messages to the data object.
136
+ def method_missing(name, *args, &block)
137
+ @data.send(name, *args, &block)
138
+ end
139
+
140
+ end
141
+
142
+ # Used for handling multiple sessions stored in memory.
143
+ class MemorySessionContainer
144
+ class << self
145
+
146
+ # ==== Parameters
147
+ # ttl<Fixnum>:: Session validity time in seconds. Defaults to 1 hour.
148
+ #
149
+ # ==== Returns
150
+ # MemorySessionContainer:: The new session container.
151
+ def setup(ttl=nil)
152
+ @sessions = Hash.new
153
+ @timestamps = Hash.new
154
+ @mutex = Mutex.new
155
+ @session_ttl = ttl || 60*60 # default 1 hour
156
+ start_timer
157
+ self
158
+ end
159
+
160
+ # Creates a new session based on the options.
161
+ #
162
+ # ==== Parameters
163
+ # opts<Hash>:: The session options (see below).
164
+ #
165
+ # ==== Options (opts)
166
+ # :session_id<String>:: ID of the session to create in the container.
167
+ # :data<MemorySession>:: The session to create in the container.
168
+ def create(opts={})
169
+ self[opts[:session_id]] = opts[:data]
170
+ end
171
+
172
+ # ==== Parameters
173
+ # key<String>:: ID of the session to retrieve.
174
+ #
175
+ # ==== Returns
176
+ # MemorySession:: The session corresponding to the ID.
177
+ def [](key)
178
+ @mutex.synchronize {
179
+ @timestamps[key] = Time.now
180
+ @sessions[key]
181
+ }
182
+ end
183
+
184
+ # ==== Parameters
185
+ # key<String>:: ID of the session to set.
186
+ # val<MemorySession>:: The session to set.
187
+ def []=(key, val)
188
+ @mutex.synchronize {
189
+ @timestamps[key] = Time.now
190
+ @sessions[key] = val
191
+ }
192
+ end
193
+
194
+ # ==== Parameters
195
+ # key<String>:: ID of the session to delete.
196
+ def delete(key)
197
+ @mutex.synchronize {
198
+ @sessions.delete(key)
199
+ @timestamps.delete(key)
200
+ }
201
+ end
202
+
203
+ # Deletes any sessions that have reached their maximum validity.
204
+ def reap_old_sessions
205
+ @timestamps.each do |key,stamp|
206
+ if stamp + @session_ttl < Time.now
207
+ delete(key)
208
+ end
209
+ end
210
+ GC.start
211
+ end
212
+
213
+ # Starts the timer that will eventually reap outdated sessions.
214
+ def start_timer
215
+ Thread.new do
216
+ loop {
217
+ sleep @session_ttl
218
+ reap_old_sessions
219
+ }
220
+ end
221
+ end
222
+
223
+ # ==== Returns
224
+ # Array:: The sessions stored in this container.
225
+ def sessions
226
+ @sessions
227
+ end
228
+
229
+ end # end singleton class
230
+
231
+ end # end MemorySessionContainer
232
+ end
233
+
234
+ Merb::MemorySessionContainer.setup(Merb::Config[:memory_session_ttl])
@@ -0,0 +1,19 @@
1
+ require 'erubis'
2
+ module Erubis
3
+
4
+ class MEruby < Erubis::Eruby
5
+ include PercentLineEnhancer
6
+ include StringBufferEnhancer
7
+ end
8
+
9
+ # Loads a file, runs it through Erubis and parses it as YAML.
10
+ #
11
+ # ===== Parameters
12
+ # file<String>:: The name of the file to load.
13
+ # binding<Binding>::
14
+ # The binding to use when evaluating the ERB tags. Defaults to the current
15
+ # binding.
16
+ def self.load_yaml_file(file, binding = binding)
17
+ YAML::load(Erubis::MEruby.new(IO.read(File.expand_path(file))).result(binding))
18
+ end
19
+ end
@@ -0,0 +1,230 @@
1
+ require "time" # httpdate
2
+ # ==== Public Merb Logger API
3
+ #
4
+ # To replace an existing logger with a new one:
5
+ # Merb::Logger.set_log(log{String, IO},level{Symbol, String})
6
+ #
7
+ # Available logging levels are
8
+ # Merb::Logger::{ Fatal, Error, Warn, Info, Debug }
9
+ #
10
+ # Logging via:
11
+ # Merb.logger.fatal(message<String>,&block)
12
+ # Merb.logger.error(message<String>,&block)
13
+ # Merb.logger.warn(message<String>,&block)
14
+ # Merb.logger.info(message<String>,&block)
15
+ # Merb.logger.debug(message<String>,&block)
16
+ #
17
+ # Logging with autoflush:
18
+ # Merb.logger.fatal!(message<String>,&block)
19
+ # Merb.logger.error!(message<String>,&block)
20
+ # Merb.logger.warn!(message<String>,&block)
21
+ # Merb.logger.info!(message<String>,&block)
22
+ # Merb.logger.debug!(message<String>,&block)
23
+ #
24
+ # Flush the buffer to
25
+ # Merb.logger.flush
26
+ #
27
+ # Remove the current log object
28
+ # Merb.logger.close
29
+ #
30
+ # ==== Private Merb Logger API
31
+ #
32
+ # To initialize the logger you create a new object, proxies to set_log.
33
+ # Merb::Logger.new(log{String, IO},level{Symbol, String})
34
+ module Merb
35
+
36
+ class << self #:nodoc:
37
+ attr_accessor :logger
38
+ end
39
+
40
+ class Logger
41
+
42
+ attr_accessor :aio
43
+ attr_accessor :level
44
+ attr_accessor :delimiter
45
+ attr_accessor :auto_flush
46
+ attr_reader :buffer
47
+ attr_reader :log
48
+ attr_reader :init_args
49
+
50
+ # ==== Notes
51
+ # Ruby (standard) logger levels:
52
+ # :fatal:: An unhandleable error that results in a program crash
53
+ # :error:: A handleable error condition
54
+ # :warn:: A warning
55
+ # :info:: generic (useful) information about system operation
56
+ # :debug:: low-level information for developers
57
+ Levels =
58
+ {
59
+ :fatal => 7,
60
+ :error => 6,
61
+ :warn => 4,
62
+ :info => 3,
63
+ :debug => 0
64
+ }
65
+
66
+ private
67
+
68
+ # Define the write method based on if asynchronous IO an be used.
69
+ #
70
+ # ==== Notes
71
+ # The idea here is that instead of performing an 'if' conditional check on
72
+ # each logging we do it once when the log object is setup.
73
+ def set_write_method
74
+ @log.instance_eval do
75
+
76
+ # ==== Returns
77
+ # Boolean:: True if asynchronous IO can be used.
78
+ def aio?
79
+ @aio = !Merb.environment.to_s.match(/development|test/) &&
80
+ !RUBY_PLATFORM.match(/java|mswin/) &&
81
+ !(@log == STDOUT) &&
82
+ @log.respond_to?(:write_nonblock)
83
+ end
84
+
85
+ undef write_method if defined? write_method #:nodoc:
86
+ if aio?
87
+ alias :write_method :write_nonblock
88
+ else
89
+ alias :write_method :write
90
+ end
91
+ end
92
+ end
93
+
94
+ # Readies a log for writing.
95
+ #
96
+ # ==== Parameters
97
+ # log<IO, String>:: Either an IO object or a name of a logfile.
98
+ def initialize_log(log)
99
+ close if @log # be sure that we don't leave open files laying around.
100
+
101
+ if log.respond_to?(:write)
102
+ @log = log
103
+ elsif File.exist?(log)
104
+ @log = open(log, (File::WRONLY | File::APPEND))
105
+ @log.sync = true
106
+ else
107
+ FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
108
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
109
+ @log.sync = true
110
+ @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
111
+ end
112
+ set_write_method
113
+ end
114
+
115
+ public
116
+
117
+ # To initialize the logger you create a new object, proxies to set_log.
118
+ #
119
+ # ==== Parameters
120
+ # *args:: Arguments to create the log from. See set_logs for specifics.
121
+ def initialize(*args)
122
+ @init_args = args
123
+ set_log(*args)
124
+ end
125
+
126
+ # Replaces an existing logger with a new one.
127
+ #
128
+ # ==== Parameters
129
+ # log<IO, String>:: Either an IO object or a name of a logfile.
130
+ # log_level<~to_sym>::
131
+ # The log level from, e.g. :fatal or :info. Defaults to :error in the
132
+ # production environment and :debug otherwise.
133
+ # delimiter<String>::
134
+ # Delimiter to use between message sections. Defaults to " ~ ".
135
+ # auto_flush<Boolean>::
136
+ # Whether the log should automatically flush after new messages are
137
+ # added. Defaults to false.
138
+ def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
139
+ if log_level && Levels[log_level.to_sym]
140
+ @level = Levels[log_level.to_sym]
141
+ elsif Merb.environment == "production"
142
+ @level = Levels[:warn]
143
+ else
144
+ @level = Levels[:debug]
145
+ end
146
+ @buffer = []
147
+ @delimiter = delimiter
148
+ @auto_flush = auto_flush
149
+
150
+ initialize_log(log)
151
+
152
+ Merb.logger = self
153
+ end
154
+
155
+ # Flush the entire buffer to the log object.
156
+ def flush
157
+ return unless @buffer.size > 0
158
+ @log.write_method(@buffer.slice!(0..-1).to_s)
159
+ end
160
+
161
+ # Close and remove the current log object.
162
+ def close
163
+ flush
164
+ @log.close if @log.respond_to?(:close)
165
+ @log = nil
166
+ end
167
+
168
+ # Appends a message to the log. The methods yield to an optional block and
169
+ # the output of this block will be appended to the message.
170
+ #
171
+ # ==== Parameters
172
+ # string<String>:: The message to be logged. Defaults to nil.
173
+ #
174
+ # ==== Returns
175
+ # String:: The resulting message added to the log file.
176
+ def <<(string = nil)
177
+ message = ""
178
+ message << delimiter
179
+ message << string if string
180
+ message << "\n" unless message[-1] == ?\n
181
+ @buffer << message
182
+ flush if @auto_flush
183
+
184
+ message
185
+ end
186
+ alias :push :<<
187
+
188
+ # Generate the logging methods for Merb.logger for each log level.
189
+ Levels.each_pair do |name, number|
190
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
191
+
192
+ # Appends a message to the log if the log level is at least as high as
193
+ # the log level of the logger.
194
+ #
195
+ # ==== Parameters
196
+ # string<String>:: The message to be logged. Defaults to nil.
197
+ #
198
+ # ==== Returns
199
+ # self:: The logger object for chaining.
200
+ def #{name}(message = nil)
201
+ self << message if #{number} >= level
202
+ self
203
+ end
204
+
205
+ # Appends a message to the log if the log level is at least as high as
206
+ # the log level of the logger. The bang! version of the method also auto
207
+ # flushes the log buffer to disk.
208
+ #
209
+ # ==== Parameters
210
+ # string<String>:: The message to be logged. Defaults to nil.
211
+ #
212
+ # ==== Returns
213
+ # self:: The logger object for chaining.
214
+ def #{name}!(message = nil)
215
+ self << message if #{number} >= level
216
+ flush if #{number} >= level
217
+ self
218
+ end
219
+
220
+ # ==== Returns
221
+ # Boolean:: True if this level will be logged by this logger.
222
+ def #{name}?
223
+ #{number} >= level
224
+ end
225
+ LEVELMETHODS
226
+ end
227
+
228
+ end
229
+
230
+ end