merb-core 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data/LICENSE +1 -1
  2. data/README +3 -3
  3. data/Rakefile +144 -33
  4. data/bin/merb +0 -0
  5. data/bin/merb-specs +0 -0
  6. data/docs/bootloading.dox +1 -0
  7. data/docs/merb-core-call-stack-diagram.mmap +0 -0
  8. data/docs/merb-core-call-stack-diagram.pdf +0 -0
  9. data/docs/merb-core-call-stack-diagram.png +0 -0
  10. data/lib/merb-core.rb +159 -37
  11. data/lib/merb-core/autoload.rb +1 -0
  12. data/lib/merb-core/bootloader.rb +208 -92
  13. data/lib/merb-core/config.rb +20 -6
  14. data/lib/merb-core/controller/abstract_controller.rb +113 -61
  15. data/lib/merb-core/controller/exceptions.rb +28 -13
  16. data/lib/merb-core/controller/merb_controller.rb +73 -44
  17. data/lib/merb-core/controller/mime.rb +25 -7
  18. data/lib/merb-core/controller/mixins/authentication.rb +1 -1
  19. data/lib/merb-core/controller/mixins/controller.rb +44 -8
  20. data/lib/merb-core/controller/mixins/render.rb +191 -128
  21. data/lib/merb-core/controller/mixins/responder.rb +65 -63
  22. data/lib/merb-core/controller/template.rb +103 -54
  23. data/lib/merb-core/core_ext.rb +7 -12
  24. data/lib/merb-core/core_ext/kernel.rb +128 -136
  25. data/lib/merb-core/dispatch/cookies.rb +26 -4
  26. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  27. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  28. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  29. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +92 -0
  30. data/lib/merb-core/dispatch/dispatcher.rb +156 -224
  31. data/lib/merb-core/dispatch/request.rb +126 -25
  32. data/lib/merb-core/dispatch/router.rb +61 -6
  33. data/lib/merb-core/dispatch/router/behavior.rb +122 -41
  34. data/lib/merb-core/dispatch/router/route.rb +147 -22
  35. data/lib/merb-core/dispatch/session.rb +52 -2
  36. data/lib/merb-core/dispatch/session/cookie.rb +4 -2
  37. data/lib/merb-core/dispatch/session/memcached.rb +38 -27
  38. data/lib/merb-core/dispatch/session/memory.rb +18 -11
  39. data/lib/merb-core/dispatch/worker.rb +28 -0
  40. data/lib/merb-core/gem_ext/erubis.rb +58 -0
  41. data/lib/merb-core/logger.rb +3 -31
  42. data/lib/merb-core/plugins.rb +25 -3
  43. data/lib/merb-core/rack.rb +18 -12
  44. data/lib/merb-core/rack/adapter.rb +10 -8
  45. data/lib/merb-core/rack/adapter/ebb.rb +2 -2
  46. data/lib/merb-core/rack/adapter/irb.rb +31 -21
  47. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  48. data/lib/merb-core/rack/adapter/thin.rb +19 -9
  49. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  50. data/lib/merb-core/rack/application.rb +9 -84
  51. data/lib/merb-core/rack/middleware.rb +26 -0
  52. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  53. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  54. data/lib/merb-core/rack/middleware/static.rb +45 -0
  55. data/lib/merb-core/server.rb +27 -9
  56. data/lib/merb-core/tasks/audit.rake +68 -0
  57. data/lib/merb-core/tasks/merb.rb +1 -0
  58. data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
  59. data/lib/merb-core/tasks/stats.rake +71 -0
  60. data/lib/merb-core/test.rb +2 -1
  61. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -3
  62. data/lib/merb-core/test/helpers/request_helper.rb +66 -24
  63. data/lib/merb-core/test/matchers/controller_matchers.rb +36 -4
  64. data/lib/merb-core/test/matchers/route_matchers.rb +12 -3
  65. data/lib/merb-core/test/matchers/view_matchers.rb +3 -3
  66. data/lib/merb-core/test/run_specs.rb +1 -0
  67. data/lib/merb-core/test/tasks/spectasks.rb +13 -5
  68. data/lib/merb-core/test/test_ext/string.rb +14 -0
  69. data/lib/merb-core/vendor/facets/dictionary.rb +3 -3
  70. data/lib/merb-core/vendor/facets/inflect.rb +82 -37
  71. data/lib/merb-core/version.rb +2 -2
  72. data/spec/private/config/config_spec.rb +39 -4
  73. data/spec/private/core_ext/kernel_spec.rb +3 -14
  74. data/spec/private/dispatch/bootloader_spec.rb +1 -1
  75. data/spec/private/dispatch/cookies_spec.rb +181 -69
  76. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -2
  77. data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -2
  78. data/spec/private/dispatch/fixture/config/rack.rb +10 -0
  79. data/spec/private/dispatch/fixture/log/merb_test.log +7054 -1802
  80. data/spec/private/dispatch/route_params_spec.rb +2 -3
  81. data/spec/private/dispatch/session_mixin_spec.rb +47 -0
  82. data/spec/private/plugins/plugin_spec.rb +73 -59
  83. data/spec/private/router/behavior_spec.rb +60 -0
  84. data/spec/private/router/fixture/log/merb_test.log +1693 -0
  85. data/spec/private/router/route_spec.rb +414 -0
  86. data/spec/private/router/router_spec.rb +175 -0
  87. data/spec/private/vendor/facets/plural_spec.rb +564 -0
  88. data/spec/private/vendor/facets/singular_spec.rb +489 -0
  89. data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
  90. data/spec/public/abstract_controller/controllers/helpers.rb +12 -2
  91. data/spec/public/abstract_controller/controllers/partial.rb +17 -2
  92. data/spec/public/abstract_controller/controllers/render.rb +16 -1
  93. data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
  94. data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
  95. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
  96. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
  97. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
  98. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
  99. data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
  100. data/spec/public/abstract_controller/filter_spec.rb +20 -1
  101. data/spec/public/abstract_controller/helper_spec.rb +10 -2
  102. data/spec/public/abstract_controller/partial_spec.rb +8 -0
  103. data/spec/public/abstract_controller/render_spec.rb +8 -0
  104. data/spec/public/abstract_controller/spec_helper.rb +7 -3
  105. data/spec/public/boot_loader/boot_loader_spec.rb +2 -2
  106. data/spec/public/controller/base_spec.rb +10 -2
  107. data/spec/public/controller/config/init.rb +6 -0
  108. data/spec/public/controller/controllers/authentication.rb +9 -11
  109. data/spec/public/controller/controllers/base.rb +2 -8
  110. data/spec/public/controller/controllers/cookies.rb +16 -0
  111. data/spec/public/controller/controllers/dispatcher.rb +35 -0
  112. data/spec/public/controller/controllers/display.rb +62 -14
  113. data/spec/public/controller/controllers/redirect.rb +36 -0
  114. data/spec/public/controller/controllers/responder.rb +37 -11
  115. data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
  116. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
  117. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
  118. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
  119. data/spec/public/controller/cookies_spec.rb +23 -0
  120. data/spec/public/controller/dispatcher_spec.rb +411 -0
  121. data/spec/public/controller/display_spec.rb +43 -10
  122. data/spec/public/controller/redirect_spec.rb +33 -0
  123. data/spec/public/controller/responder_spec.rb +79 -11
  124. data/spec/public/controller/spec_helper.rb +3 -1
  125. data/spec/public/controller/url_spec.rb +10 -0
  126. data/spec/public/core/merb_core_spec.rb +11 -0
  127. data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
  128. data/spec/public/core_ext/kernel_spec.rb +9 -0
  129. data/spec/public/core_ext/spec_helper.rb +1 -0
  130. data/spec/public/directory_structure/directory/log/merb_test.log +3729 -272
  131. data/spec/public/directory_structure/directory_spec.rb +3 -4
  132. data/spec/public/logger/logger_spec.rb +4 -4
  133. data/spec/public/reloading/directory/log/merb_test.log +288066 -15
  134. data/spec/public/reloading/reload_spec.rb +49 -27
  135. data/spec/public/request/multipart_spec.rb +26 -0
  136. data/spec/public/request/request_spec.rb +21 -2
  137. data/spec/public/router/fixation_spec.rb +27 -0
  138. data/spec/public/router/fixture/log/merb_test.log +30050 -0
  139. data/spec/public/router/nested_matches_spec.rb +97 -0
  140. data/spec/public/router/resource_spec.rb +1 -9
  141. data/spec/public/router/resources_spec.rb +0 -20
  142. data/spec/public/router/spec_helper.rb +27 -9
  143. data/spec/public/router/special_spec.rb +21 -8
  144. data/spec/public/template/template_spec.rb +17 -5
  145. data/spec/public/test/controller_matchers_spec.rb +10 -0
  146. data/spec/public/test/request_helper_spec.rb +29 -0
  147. data/spec/public/test/route_helper_spec.rb +18 -1
  148. data/spec/public/test/route_matchers_spec.rb +28 -1
  149. data/spec/public/test/view_matchers_spec.rb +3 -3
  150. data/spec/spec_helper.rb +56 -12
  151. metadata +89 -47
  152. data/lib/merb-core/core_ext/class.rb +0 -299
  153. data/lib/merb-core/core_ext/hash.rb +0 -426
  154. data/lib/merb-core/core_ext/mash.rb +0 -154
  155. data/lib/merb-core/core_ext/object.rb +0 -147
  156. data/lib/merb-core/core_ext/object_space.rb +0 -14
  157. data/lib/merb-core/core_ext/rubygems.rb +0 -28
  158. data/lib/merb-core/core_ext/set.rb +0 -46
  159. data/lib/merb-core/core_ext/string.rb +0 -89
  160. data/lib/merb-core/core_ext/time.rb +0 -13
  161. data/lib/merb-core/dispatch/exceptions.html.erb +0 -297
  162. data/spec/private/core_ext/class_spec.rb +0 -22
  163. data/spec/private/core_ext/hash_spec.rb +0 -522
  164. data/spec/private/core_ext/object_spec.rb +0 -121
  165. data/spec/private/core_ext/set_spec.rb +0 -26
  166. data/spec/private/core_ext/string_spec.rb +0 -167
  167. data/spec/private/core_ext/time_spec.rb +0 -16
  168. data/spec/private/dispatch/dispatch_spec.rb +0 -26
  169. data/spec/private/dispatch/fixture/log/development.log +0 -1
  170. data/spec/private/dispatch/fixture/log/merb.4000.pid +0 -1
  171. data/spec/private/dispatch/fixture/log/production.log +0 -1
  172. data/spec/private/dispatch/fixture/merb.4000.pid +0 -1
  173. data/spec/private/rack/application_spec.rb +0 -43
  174. data/spec/public/controller/log/merb.4000.pid +0 -1
  175. data/spec/public/directory_structure/directory/log/merb.4000.pid +0 -1
  176. data/spec/public/directory_structure/directory/merb.4000.pid +0 -1
  177. data/spec/public/reloading/directory/log/merb.4000.pid +0 -1
  178. data/spec/public/reloading/directory/merb.4000.pid +0 -1
@@ -1,7 +1,12 @@
1
1
  class Merb::Controller < Merb::AbstractController
2
2
 
3
- class_inheritable_accessor :_hidden_actions, :_shown_actions
4
- cattr_accessor :_subclasses, :_session_id_key, :_session_secret_key, :_session_expiry
3
+ class_inheritable_accessor :_hidden_actions, :_shown_actions,
4
+ :_session_id_key, :_session_secret_key, :_session_expiry, :_session_cookie_domain
5
+
6
+ self._hidden_actions ||= []
7
+ self._shown_actions ||= []
8
+
9
+ cattr_accessor :_subclasses
5
10
  self._subclasses = Set.new
6
11
 
7
12
  def self.subclasses_list() _subclasses end
@@ -9,13 +14,12 @@ class Merb::Controller < Merb::AbstractController
9
14
  self._session_secret_key = nil
10
15
  self._session_id_key = Merb::Config[:session_id_key] || '_session_id'
11
16
  self._session_expiry = Merb::Config[:session_expiry] || Merb::Const::WEEK * 2
17
+ self._session_cookie_domain = Merb::Config[:session_cookie_domain]
12
18
 
13
19
  include Merb::ResponderMixin
14
20
  include Merb::ControllerMixin
15
21
  include Merb::AuthenticationMixin
16
22
 
17
- attr_accessor :route
18
-
19
23
  class << self
20
24
 
21
25
  # ==== Parameters
@@ -23,8 +27,8 @@ class Merb::Controller < Merb::AbstractController
23
27
  # The Merb::Controller inheriting from the base class.
24
28
  def inherited(klass)
25
29
  _subclasses << klass.to_s
26
- self._template_root = Merb.dir_for(:view) unless self._template_root
27
30
  super
31
+ klass._template_root = Merb.dir_for(:view) unless self._template_root
28
32
  end
29
33
 
30
34
  # Hide each of the given methods from being callable as actions.
@@ -74,26 +78,6 @@ class Merb::Controller < Merb::AbstractController
74
78
  self._shown_actions = self._shown_actions | names.map {|n| n.to_s}
75
79
  end
76
80
 
77
- # This list of actions that should not be callable.
78
- #
79
- # ==== Returns
80
- # Array[String]:: An array of actions that should not be dispatchable.
81
- def _hidden_actions
82
- actions = read_inheritable_attribute(:_hidden_actions)
83
- actions ? actions : write_inheritable_attribute(:_hidden_actions, [])
84
- end
85
-
86
- # This list of actions that should be callable.
87
- #
88
- # ==== Returns
89
- # Array[String]::
90
- # An array of actions that should be dispatched to even if they would not
91
- # otherwise be.
92
- def _shown_actions
93
- actions = read_inheritable_attribute(:_shown_actions)
94
- actions ? actions : write_inheritable_attribute(:_shown_actions, [])
95
- end
96
-
97
81
  # The list of actions that are callable, after taking defaults,
98
82
  # _hidden_actions and _shown_actions into consideration. It is calculated
99
83
  # once, the first time an action is dispatched for this controller.
@@ -101,16 +85,7 @@ class Merb::Controller < Merb::AbstractController
101
85
  # ==== Returns
102
86
  # SimpleSet[String]:: A set of actions that should be callable.
103
87
  def callable_actions
104
- unless @callable_actions
105
- callables = []
106
- klass = self
107
- begin
108
- callables << (klass.public_instance_methods(false) + klass._shown_actions) - klass._hidden_actions
109
- klass = klass.superclass
110
- end until klass == Merb::AbstractController || klass == Object
111
- @callable_actions = Merb::SimpleSet.new(callables.flatten)
112
- end
113
- @callable_actions
88
+ @callable_actions ||= Extlib::SimpleSet.new(_callable_methods)
114
89
  end
115
90
 
116
91
  # This is a stub method so plugins can implement param filtering if they want.
@@ -126,6 +101,22 @@ class Merb::Controller < Merb::AbstractController
126
101
  params
127
102
  end
128
103
 
104
+ private
105
+
106
+ # All methods that are callable as actions.
107
+ #
108
+ # ==== Returns
109
+ # Array:: A list of method names that are also actions
110
+ def _callable_methods
111
+ callables = []
112
+ klass = self
113
+ begin
114
+ callables << (klass.public_instance_methods(false) + klass._shown_actions) - klass._hidden_actions
115
+ klass = klass.superclass
116
+ end until klass == Merb::AbstractController || klass == Object
117
+ callables.flatten.reject{|action| action =~ /^_.*/}
118
+ end
119
+
129
120
  end # class << self
130
121
 
131
122
  # The location to look for a template for a particular controller, context,
@@ -147,7 +138,24 @@ class Merb::Controller < Merb::AbstractController
147
138
  #---
148
139
  # @public
149
140
  def _template_location(context, type = nil, controller = controller_name)
150
- controller ? "#{controller}/#{context}.#{type}" : "#{context}.#{type}"
141
+ _conditionally_append_extension(controller ? "#{controller}/#{context}" : "#{context}", type)
142
+ end
143
+
144
+ # The location to look for a template and mime-type. This is overridden
145
+ # from AbstractController, which defines a version of this that does not
146
+ # involve mime-types.
147
+ #
148
+ # ==== Parameters
149
+ # template<String>::
150
+ # The absolute path to a template - without mime and template extension.
151
+ # The mime-type extension is optional - it will be appended from the
152
+ # current content type if it hasn't been added already.
153
+ # type<~to_s>::
154
+ # The mime-type of the template that will be rendered. Defaults to nil.
155
+ #
156
+ # @public
157
+ def _absolute_template_location(template, type)
158
+ _conditionally_append_extension(template, type)
151
159
  end
152
160
 
153
161
  # Build a new controller.
@@ -168,7 +176,7 @@ class Merb::Controller < Merb::AbstractController
168
176
  # @semipublic
169
177
  def initialize(request, status=200, headers={'Content-Type' => 'text/html; charset=utf-8'})
170
178
  super()
171
- @request, @status, @headers = request, status, headers
179
+ @request, @_status, @headers = request, status, headers
172
180
  end
173
181
 
174
182
  # Dispatch the action.
@@ -184,6 +192,7 @@ class Merb::Controller < Merb::AbstractController
184
192
  #---
185
193
  # @semipublic
186
194
  def _dispatch(action=:index)
195
+ Merb.logger.info("Params: #{self.class._filter_params(request.params).inspect}")
187
196
  start = Time.now
188
197
  if self.class.callable_actions.include?(action.to_s)
189
198
  super(action)
@@ -191,10 +200,14 @@ class Merb::Controller < Merb::AbstractController
191
200
  raise ActionNotFound, "Action '#{action}' was not found in #{self.class}"
192
201
  end
193
202
  @_benchmarks[:action_time] = Time.now - start
203
+ self
194
204
  end
195
205
 
196
206
  attr_reader :request, :headers
197
- attr_reader :status
207
+
208
+ def status
209
+ @_status
210
+ end
198
211
 
199
212
  # Set the response status code.
200
213
  #
@@ -202,9 +215,9 @@ class Merb::Controller < Merb::AbstractController
202
215
  # s<Fixnum, Symbol>:: A status-code or named http-status
203
216
  def status=(s)
204
217
  if s.is_a?(Symbol) && STATUS_CODES.key?(s)
205
- @status = STATUS_CODES[s]
218
+ @_status = STATUS_CODES[s]
206
219
  elsif s.is_a?(Fixnum)
207
- @status = s
220
+ @_status = s
208
221
  else
209
222
  raise ArgumentError, "Status should be of type Fixnum or Symbol, was #{s.class}"
210
223
  end
@@ -227,12 +240,28 @@ class Merb::Controller < Merb::AbstractController
227
240
  # ==== Returns
228
241
  # Hash:: The session that was extracted from the request object.
229
242
  def session() request.session end
230
-
243
+
244
+ # The results of the controller's render, to be returned to Rack.
245
+ #
246
+ # ==== Returns
247
+ # Array[Integer, Hash, String]::
248
+ # The controller's status code, headers, and body
249
+ def rack_response
250
+ [status, headers, body]
251
+ end
252
+
253
+ # Hide any methods that may have been exposed as actions before.
254
+ hide_action(*_callable_methods)
255
+
231
256
  private
232
257
 
233
- # Create a default cookie jar, and pre-set a fixation cookie
234
- # if fixation is enabled
258
+ # If not already added, add the proper mime extension to the template path.
259
+ def _conditionally_append_extension(template, type = nil)
260
+ type && !template.match(/\.#{type.to_s.escape_regexp}$/) ? "#{template}.#{type}" : template
261
+ end
262
+
263
+ # Create a default cookie jar, and pre-set a fixation cookie if fixation is enabled.
235
264
  def _setup_cookies
236
265
  ::Merb::Cookies.new(request.cookies, @headers)
237
266
  end
238
- end
267
+ end
@@ -28,17 +28,35 @@ module Merb
28
28
  # The associated method to call on objects to convert them to the
29
29
  # appropriate mime-type. For instance, :json would use :to_json as its
30
30
  # transform_method.
31
- # values<Array[String]>::
31
+ # mimes<Array[String]>::
32
32
  # A list of possible values sent in the Accept header, such as text/html,
33
33
  # that should be associated with this content-type.
34
34
  # new_response_headers<Hash>::
35
- # The response headers to set for the the mime type.
36
- def add_mime_type(key, transform_method, values, new_response_headers = {})
37
- enforce!(key => Symbol, values => Array)
35
+ # The response headers to set for the the mime type. For example:
36
+ # 'Content-Type' => 'application/json; charset=utf-8'; As a shortcut for
37
+ # the common charset option, use :charset => 'utf-8', which will be
38
+ # correctly appended to the mimetype itself.
39
+ # &block:: a block which recieves the current controller when the format
40
+ # is set (in the controller's #content_type method)
41
+ def add_mime_type(key, transform_method, mimes, new_response_headers = {}, &block)
42
+ enforce!(key => Symbol, mimes => Array)
43
+
44
+ content_type = new_response_headers["Content-Type"] || mimes.first
45
+
46
+ if charset = new_response_headers.delete(:charset)
47
+ content_type += "; charset=#{charset}"
48
+ end
49
+
38
50
  ResponderMixin::TYPES.update(key =>
39
- {:request_headers => values,
51
+ {:accepts => mimes,
40
52
  :transform_method => transform_method,
41
- :response_headers => new_response_headers })
53
+ :content_type => content_type,
54
+ :response_headers => new_response_headers,
55
+ :response_block => block })
56
+
57
+ mimes.each do |mime|
58
+ ResponderMixin::MIMES.update(mime => key)
59
+ end
42
60
 
43
61
  Merb::RenderMixin.class_eval <<-EOS, __FILE__, __LINE__
44
62
  def render_#{key}(thing = nil, opts = {})
@@ -81,7 +99,7 @@ module Merb
81
99
  # ==== Returns
82
100
  # Hash:: The mime type information.
83
101
  def mime_by_request_header(header)
84
- available_mime_types.find {|key,info| info[request_headers].include?(header)}.first
102
+ available_mime_types.find {|key,info| info[:accepts].include?(header)}.first
85
103
  end
86
104
 
87
105
  end
@@ -51,7 +51,7 @@ module Merb::AuthenticationMixin
51
51
  BasicAuthentication.new(self, realm, &authenticator)
52
52
  end
53
53
 
54
- class BasicAuthentication #:nodoc:
54
+ class BasicAuthentication
55
55
  # So we can have access to the status codes
56
56
  include Merb::ControllerExceptions
57
57
 
@@ -1,6 +1,22 @@
1
1
  module Merb
2
2
  # Module that is mixed in to all implemented controllers.
3
3
  module ControllerMixin
4
+
5
+ # Enqueu a block to run in a background thread outside of the request
6
+ # response dispatch
7
+ #
8
+ # ==== Parameters
9
+ # takes a block to run later
10
+ #
11
+ # ==== Example
12
+ # run_later do
13
+ # SomeBackgroundTask.run
14
+ # end
15
+ #
16
+ def run_later(&blk)
17
+ Merb::Dispatcher.work_queue << blk
18
+ end
19
+
4
20
  # Renders the block given as a parameter using chunked encoding.
5
21
  #
6
22
  # ==== Parameters
@@ -88,24 +104,43 @@ module Merb
88
104
  blk.call
89
105
  }
90
106
  end
91
-
107
+
92
108
  # ==== Parameters
93
109
  # url<String>::
94
110
  # URL to redirect to. It can be either a relative or fully-qualified URL.
111
+ # opts<Hash>:: An options hash (see below)
112
+ #
113
+ # ==== Options (opts)
114
+ # :message<Hash>::
115
+ # Messages to pass in url query string as value for "_message"
116
+ # :permanent<Boolean>::
117
+ # When true, return status 301 Moved Permanently
95
118
  #
96
119
  # ==== Returns
97
120
  # String:: Explanation of redirect.
98
121
  #
99
122
  # ==== Examples
100
123
  # redirect("/posts/34")
124
+ # redirect("/posts/34", :message => { :notice => 'Post updated successfully!' })
101
125
  # redirect("http://www.merbivore.com/")
102
- def redirect(url)
103
- Merb.logger.info("Redirecting to: #{url}")
104
- self.status = 302
126
+ # redirect("http://www.merbivore.com/", :permanent => true)
127
+ def redirect(url, opts = {})
128
+ default_redirect_options = { :message => nil, :permanent => false }
129
+ opts = default_redirect_options.merge(opts)
130
+ if opts[:message]
131
+ notice = Merb::Request.escape([Marshal.dump(opts[:message])].pack("m"))
132
+ url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
133
+ end
134
+ self.status = opts[:permanent] ? 301 : 302
135
+ Merb.logger.info("Redirecting to: #{url} (#{self.status})")
105
136
  headers['Location'] = url
106
137
  "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
107
138
  end
108
139
 
140
+ def message
141
+ @_message = defined?(@_message) ? @_message : request.message
142
+ end
143
+
109
144
  # Sends a file over HTTP. When given a path to a file, it will set the
110
145
  # right headers so that the static file is served directly.
111
146
  #
@@ -131,7 +166,7 @@ module Merb
131
166
  'Content-Disposition' => disposition,
132
167
  'Content-Transfer-Encoding' => 'binary'
133
168
  )
134
- File.open(file)
169
+ File.open(file, 'rb')
135
170
  end
136
171
 
137
172
  # Send binary data over HTTP to the user as a file download. May set content type,
@@ -190,7 +225,8 @@ module Merb
190
225
  'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
191
226
  'Content-Disposition' => disposition,
192
227
  'Content-Transfer-Encoding' => 'binary',
193
- 'CONTENT-LENGTH' => opts[:content_length]
228
+ # Rack specification requires header values to respond to :each
229
+ 'CONTENT-LENGTH' => opts[:content_length].to_s
194
230
  )
195
231
  Proc.new{|response|
196
232
  response.send_status(opts[:content_length])
@@ -206,8 +242,8 @@ module Merb
206
242
  # ==== Parameters
207
243
  # file<String>:: Path to file to send to the client.
208
244
  def nginx_send_file(file)
209
- headers['X-Accel-Redirect'] = File.expand_path(file)
210
- return
245
+ headers['X-Accel-Redirect'] = file
246
+ return ' '
211
247
  end
212
248
 
213
249
  # Sets a cookie to be included in the response.
@@ -1,7 +1,7 @@
1
1
  module Merb::RenderMixin
2
2
  # So we can do raise TemplateNotFound
3
3
  include Merb::ControllerExceptions
4
-
4
+
5
5
  # ==== Parameters
6
6
  # base<Module>:: Module that is including RenderMixin (probably a controller)
7
7
  def self.included(base)
@@ -10,9 +10,9 @@ module Merb::RenderMixin
10
10
  class_inheritable_accessor :_default_render_options
11
11
  end
12
12
  end
13
-
13
+
14
14
  module ClassMethods
15
-
15
+
16
16
  # Return the default render options.
17
17
  #
18
18
  # ==== Returns
@@ -20,7 +20,7 @@ module Merb::RenderMixin
20
20
  def default_render_options
21
21
  self._default_render_options ||= {}
22
22
  end
23
-
23
+
24
24
  # Set default render options at the class level.
25
25
  #
26
26
  # ==== Parameters
@@ -28,53 +28,57 @@ module Merb::RenderMixin
28
28
  def render_options(opts)
29
29
  self._default_render_options = opts
30
30
  end
31
-
31
+
32
32
  # Set the default layout to use or nil/false to disable layout rendering.
33
- # This is a shortcut for render_options :layout => false.
33
+ # This is a shortcut for render_options :layout => false.
34
34
  #
35
35
  # ==== Parameters
36
- # layout<~to_s>:: The layout that should be used for this class
37
- #
36
+ # layout<~to_s>:: The layout that should be used for this class.
37
+ #
38
+ # ==== Notes
39
+ # You can override by passing :layout => true to render method.
40
+ #
38
41
  # ==== Returns
39
42
  # Hash:: The default render options.
40
43
  def layout(layout)
41
44
  self.default_render_options.update(:layout => (layout ? layout : false))
42
45
  end
43
-
46
+
44
47
  # Enable the default layout logic - reset the layout option.
45
48
  def default_layout
46
49
  self.default_render_options.delete(:layout)
47
50
  end
48
-
51
+
49
52
  end
50
-
53
+
51
54
  # Render the specified item, with the specified options.
52
55
  #
53
56
  # ==== Parameters
54
- # thing<String, Symbol, nil>::
57
+ # thing<String, Symbol, nil>::
55
58
  # The thing to render. This will default to the current action
56
59
  # opts<Hash>:: An options hash (see below)
57
60
  #
58
61
  # ==== Options (opts)
59
62
  # :format<Symbol>:: A registered mime-type format
60
- # :template<String>::
63
+ # :template<String>::
61
64
  # The path to the template relative to the template root
62
- # :status<~to_i>::
65
+ # :status<~to_i>::
63
66
  # The status to send to the client. Typically, this would be an integer
64
67
  # (200), or a Merb status code (Accepted)
65
- # :layout<~to_s>::
68
+ # :layout<~to_s, FalseClass>::
66
69
  # A layout to use instead of the default. This should be relative to the
67
70
  # layout root. By default, the layout will be either the controller_name or
68
71
  # application. If you want to use an alternative content-type than the one
69
72
  # that the base template was rendered as, you will need to do :layout =>
70
- # "foo.#{content_type}" (i.e. "foo.json")
73
+ # "foo.#{content_type}" (i.e. "foo.json"). If you want to render without
74
+ # layout, use :layout => false. This overrides layout set by +layout+ method.
71
75
  #
72
76
  # ==== Returns
73
77
  # String:: The rendered template, including layout, if appropriate.
74
78
  #
75
79
  # ==== Raises
76
80
  # TemplateNotFound:: There is no template for the specified location.
77
- #
81
+ #
78
82
  # ==== Alternatives
79
83
  # If you pass a Hash as the first parameter, it will be moved to opts and
80
84
  # "thing" will be the current action
@@ -83,59 +87,65 @@ module Merb::RenderMixin
83
87
  def render(thing = nil, opts = {})
84
88
  # render :format => :xml means render nil, :format => :xml
85
89
  opts, thing = thing, nil if thing.is_a?(Hash)
86
-
90
+
87
91
  # Merge with class level default render options
88
92
  opts = self.class.default_render_options.merge(opts)
89
-
93
+
90
94
  # If you don't specify a thing to render, assume they want to render the current action
91
95
  thing ||= action_name.to_sym
92
96
 
93
97
  # Content negotiation
94
- opts[:format] ? (self.content_type = opts[:format]) : content_type
95
-
98
+ opts[:format] ? (self.content_type = opts[:format]) : content_type
99
+
96
100
  # Handle options (:status)
97
- _handle_options!(opts)
98
-
101
+ _handle_options!(opts)
102
+
99
103
  # Do we have a template to try to render?
100
104
  if thing.is_a?(Symbol) || opts[:template]
101
105
 
102
- template_method, template_location = _template_for(thing, content_type, controller_name, opts)
103
-
106
+ template_method, template_location =
107
+ _template_for(thing, content_type, controller_name, opts[:template])
108
+
104
109
  # Raise an error if there's no template
105
- raise TemplateNotFound, "No template found at #{template_location}.*" \
106
- unless template_method && self.respond_to?(template_method)
110
+ unless template_method && self.respond_to?(template_method)
111
+ template_files = Merb::Template.template_extensions.map { |ext| "#{template_location}.#{ext}" }
112
+ raise TemplateNotFound, "Oops! No template found. Merb was looking for #{template_files.join(', ')}" +
113
+ "for content type '#{content_type}'. You might have mispelled the template or file name. " +
114
+ "Registered template extensions: #{Merb::Template.template_extensions.join(', ')}. " +
115
+ "If you use Haml or some other template plugin, make sure you required Merb plugin dependency " +
116
+ "in your init file."
117
+ end
107
118
 
108
119
  # Call the method in question and throw the content for later consumption by the layout
109
120
  throw_content(:for_layout, self.send(template_method))
110
-
121
+
111
122
  # Do we have a string to render?
112
123
  elsif thing.is_a?(String)
113
-
124
+
114
125
  # Throw it for later consumption by the layout
115
126
  throw_content(:for_layout, thing)
116
127
  end
117
-
128
+
118
129
  # If we find a layout, use it. Otherwise, just render the content thrown for layout.
119
- layout = opts[:layout] != false && _get_layout(opts[:layout])
120
- layout ? send(layout) : catch_content(:for_layout)
130
+ (layout = _get_layout(opts[:layout])) ? send(layout) : catch_content(:for_layout)
121
131
  end
122
-
132
+
123
133
  # Renders an object using to registered transform method based on the
124
134
  # negotiated content-type, if a template does not exist. For instance, if the
125
135
  # content-type is :json, Merb will first look for current_action.json.*.
126
136
  # Failing that, it will run object.to_json.
127
137
  #
128
138
  # ==== Parameter
129
- # object<Object>::
139
+ # object<Object>::
130
140
  # An object that responds_to? the transform method registered for the
131
141
  # negotiated mime-type.
132
142
  # thing<String, Symbol>::
133
143
  # The thing to attempt to render via #render before calling the transform
134
144
  # method on the object. Defaults to nil.
135
- # opts<Hash>::
145
+ # opts<Hash>::
136
146
  # An options hash that will be used for rendering
137
147
  # (passed on to #render or serialization methods like #to_json or #to_xml)
138
- #
148
+ #
139
149
  # ==== Returns
140
150
  # String::
141
151
  # The rendered template or if no template is found, the transformed object.
@@ -144,10 +154,10 @@ module Merb::RenderMixin
144
154
  # NotAcceptable::
145
155
  # If there is no transform method for the specified mime-type or the object
146
156
  # does not respond to the transform method.
147
- #
157
+ #
148
158
  # ==== Alternatives
149
159
  # A string in the second parameter will be interpreted as a template:
150
- # display @object, "path/to/foo"
160
+ # display @object, "path/to/foo"
151
161
  # #=> display @object, nil, :template => "path/to/foo"
152
162
  #
153
163
  # A hash in the second parameters will be interpreted as opts:
@@ -157,18 +167,20 @@ module Merb::RenderMixin
157
167
  # If you need to pass extra parameters to serialization method, for instance,
158
168
  # to exclude some of attributes or serialize associations, just pass options
159
169
  # for it.
160
- # For instance,
170
+ # For instance,
161
171
  #
162
- # display @locations, :except => [:locatable_type, :locatable_id], :include => [:locatable]
172
+ # display @locations, :except => [:locatable_type, :locatable_id], :include => [:locatable]
173
+ #
174
+ # serializes object with polymorphic association, not raw locatable_* attributes.
163
175
  #
164
- # serializes object with polymorphic association, not raw locatable_* attributes.
165
176
  #
166
- #
167
177
  # ==== Options
168
178
  #
169
179
  # :template a template to use for rendering
170
180
  # :layout a layout to use for rendering
171
-
181
+ # :status the status code to return (defaults to 200)
182
+ # :location the value of the Location header
183
+ #
172
184
  # all other options options that will be pass to serialization method
173
185
  # like #to_json or #to_xml
174
186
  #
@@ -177,62 +189,50 @@ module Merb::RenderMixin
177
189
  # explicitly passed in the opts.
178
190
  #
179
191
  def display(object, thing = nil, opts = {})
180
- # display @object, "path/to/foo" means display @object, nil, :template => "path/to/foo"
181
- # display @object, :template => "path/to/foo" means display @object, nil, :template => "path/to/foo"
182
192
  template_opt = opts.delete(:template)
183
-
193
+
184
194
  case thing
195
+ # display @object, "path/to/foo" means display @object, nil, :template => "path/to/foo"
185
196
  when String
186
197
  template_opt, thing = thing, nil
198
+ # display @object, :template => "path/to/foo" means display @object, nil, :template => "path/to/foo"
187
199
  when Hash
188
200
  opts, thing = thing, nil
189
201
  end
190
-
202
+
191
203
  # Try to render without the object
192
204
  render(thing || action_name.to_sym, opts.merge(:template => template_opt))
193
-
205
+
194
206
  # If the render fails (i.e. a template was not found)
195
- rescue TemplateNotFound
207
+ rescue TemplateNotFound => e
196
208
  # Merge with class level default render options
209
+ # @todo can we find a way to refactor this out so we don't have to do it everywhere?
197
210
  opts = self.class.default_render_options.merge(opts)
198
-
211
+
199
212
  # Figure out what to transform and raise NotAcceptable unless there's a transform method assigned
200
213
  transform = Merb.mime_transform_method(content_type)
201
- raise NotAcceptable unless transform && object.respond_to?(transform)
214
+ if !transform
215
+ raise NotAcceptable, "#{e.message} and there was no transform method registered for #{content_type.inspect}"
216
+ elsif !object.respond_to?(transform)
217
+ raise NotAcceptable, "#{e.message} and your object does not respond to ##{transform}"
218
+ end
202
219
 
203
- # Only use a layout if one was specified
204
220
  layout_opt = opts.delete(:layout)
221
+ _handle_options!(opts)
222
+ throw_content(:for_layout, opts.empty? ? object.send(transform) : object.send(transform, opts))
205
223
 
206
- if layout_opt
207
- # Look for the layout under the default layout directly. If it's not found, reraise
208
- # the TemplateNotFound error
209
- template = _template_location(layout_opt, layout.index(".") ? content_type : nil, "layout")
210
- layout = _template_for(_template_root / template) ||
211
- (raise TemplateNotFound, "No layout found at #{_template_root / template}.*")
212
-
213
- # If the layout was found, call it
214
- send(layout)
215
-
216
- # Otherwise, just render the transformed object
217
- else
218
- unless opts.empty?
219
- # there are options for serialization method
220
- throw_content(:for_layout, object.send(transform, opts))
221
- else
222
- throw_content(:for_layout, object.send(transform))
223
- end
224
- catch_content(:for_layout)
225
- end
224
+ meth, _ = _template_for(layout_opt, layout_opt.to_s.index(".") ? nil : content_type, "layout") if layout_opt
225
+ meth ? send(meth) : catch_content(:for_layout)
226
226
  end
227
227
 
228
228
  # Render a partial template.
229
229
  #
230
230
  # ==== Parameters
231
231
  # template<~to_s>::
232
- # The path to the template, relative to the current controller or the
233
- # template root. If the template contains a "/", Merb will search for it
234
- # relative to the template root; otherwise, Merb will search for it
235
- # relative to the current controller.
232
+ # The path to the template, relative to the current controller or the
233
+ # template root; absolute path will work too. If the template contains a "/",
234
+ # Merb will search for it relative to the template root; otherwise,
235
+ # Merb will search for it relative to the current controller.
236
236
  # opts<Hash>:: A hash of options (see below)
237
237
  #
238
238
  # ==== Options (opts)
@@ -244,56 +244,95 @@ module Merb::RenderMixin
244
244
  # A Hash object names and values that will be the local names and values
245
245
  # inside the partial.
246
246
  #
247
- # ==== Example
247
+ # ==== Examples
248
248
  # partial :foo, :hello => @object
249
249
  #
250
250
  # The "_foo" partial will be called, relative to the current controller,
251
251
  # with a local variable of +hello+ inside of it, assigned to @object.
252
+ #
253
+ # partial :bar, :with => ['one', 'two', 'three']
254
+ #
255
+ # The "_bar" partial will be called once for each element of the array
256
+ # specified by :with for a total of three iterations. Each element
257
+ # of the array will be available in the partial via a local variable named
258
+ # +bar+. Additionally, there will be two extra local variables:
259
+ # +collection_index+ and +collection_size+. +collection_index+ is the index
260
+ # of the object currently referenced by +bar+ in the collection passed to
261
+ # the partial. +collection_size+ is the total size of the collection.
262
+ #
263
+ # By default, the object specified by :with will be available through a
264
+ # local variable with the same name as the partial template. However,
265
+ # this can be changed using the :as option.
266
+ #
267
+ # partial :bar, :with => "one", :as => :number
268
+ #
269
+ # In this case, "one" will be available in the partial through the local
270
+ # variable named +number+.
271
+ #
272
+ # ==== Notes
273
+ # It is important to note that the object being passed to the partial
274
+ # as well as any extra local variables cannot use names of helper methods
275
+ # since any helper method of the same name will take precedence over the
276
+ # passed variable. Example:
277
+ #
278
+ # partial :bar, :with => "one", :as => :partial
279
+ #
280
+ # In this case, "one" will not be available in the partial because "partial"
281
+ # is already a helper method.
252
282
  def partial(template, opts={})
253
283
 
254
284
  # partial :foo becomes "#{controller_name}/_foo"
255
285
  # partial "foo/bar" becomes "foo/_bar"
256
286
  template = template.to_s
257
- kontroller = (m = template.match(/.*(?=\/)/)) ? m[0] : controller_name
258
- template = "_#{File.basename(template)}"
259
-
260
- template_method, template_location = _template_for(template, opts.delete(:format) || content_type, kontroller)
287
+ if template =~ %r{^/}
288
+ template_path = File.dirname(template) / "_#{File.basename(template)}"
289
+ else
290
+ kontroller = (m = template.match(/.*(?=\/)/)) ? m[0] : controller_name
291
+ template = "_#{File.basename(template)}"
292
+ end
293
+ template_method, template_location =
294
+ _template_for(template, opts.delete(:format) || content_type, kontroller, template_path)
261
295
 
262
296
  (@_old_partial_locals ||= []).push @_merb_partial_locals
263
-
264
- if opts.key?(:with)
265
- with = opts.delete(:with)
266
- as = opts.delete(:as) || template_location.match(%r[.*/_([^\.]*)])[1]
267
- @_merb_partial_locals = opts
268
- sent_template = [with].flatten.map do |temp|
269
- @_merb_partial_locals[as.to_sym] = temp
270
- send(template_method)
271
- end.join
272
- else
273
- @_merb_partial_locals = opts
297
+
298
+ # This handles no :with as well
299
+ with = [opts.delete(:with)].flatten
300
+ as = opts.delete(:as) || template_location.match(%r[.*/_([^\.]*)])[1]
301
+
302
+ @_merb_partial_locals = opts.merge(:collection_index => -1, :collection_size => with.size)
303
+
304
+ # this handles an edge-case where the name of the partial is _foo.* and your opts
305
+ # have :foo as a key.
306
+ named_local = @_merb_partial_locals.key?(as.to_sym)
307
+
308
+ sent_template = with.map do |temp|
309
+ @_merb_partial_locals[as.to_sym] = temp unless named_local
274
310
  if template_method && self.respond_to?(template_method)
275
- sent_template = send(template_method)
311
+ @_merb_partial_locals[:collection_index] += 1
312
+ send(template_method)
276
313
  else
277
314
  raise TemplateNotFound, "Could not find template at #{template_location}.*"
278
315
  end
279
- end
316
+ end.join
317
+
280
318
  @_merb_partial_locals = @_old_partial_locals.pop
281
319
  sent_template
282
- end
283
-
320
+ end
321
+
284
322
  # Take the options hash and handle it as appropriate.
285
- #
323
+ #
286
324
  # ==== Parameters
287
325
  # opts<Hash>:: The options hash that was passed into render.
288
- #
326
+ #
289
327
  # ==== Options
290
328
  # :status<~to_i>::
291
329
  # The status of the response will be set to opts[:status].to_i
292
- #
330
+ #
293
331
  # ==== Returns
294
332
  # Hash:: The options hash that was passed in.
295
333
  def _handle_options!(opts)
296
- self.status = opts[:status].to_i if opts[:status]
334
+ self.status = opts.delete(:status).to_i if opts[:status]
335
+ headers["Location"] = opts.delete(:location) if opts[:location]
297
336
  opts
298
337
  end
299
338
 
@@ -305,22 +344,26 @@ module Merb::RenderMixin
305
344
  #
306
345
  # ==== Parameters
307
346
  # layout<~to_s>:: A layout, relative to the layout root. Defaults to nil.
308
- #
347
+ #
309
348
  # ==== Returns
310
349
  # String:: The method name that corresponds to the found layout.
311
- #
350
+ #
312
351
  # ==== Raises
313
352
  # TemplateNotFound::
314
353
  # If a layout was specified (either via layout in the class or by passing
315
354
  # one in to this method), and not found. No error will be raised if no
316
355
  # layout was specified, and the default layouts were not found.
317
356
  def _get_layout(layout = nil)
357
+ return false if layout == false
358
+
318
359
  layout = layout.instance_of?(Symbol) && self.respond_to?(layout, true) ? send(layout) : layout
319
360
  layout = layout.to_s if layout
320
-
361
+
321
362
  # If a layout was provided, throw an error if it's not found
322
- if layout
323
- template_method, template_location = _template_for(layout, layout.index(".") ? nil : content_type, "layout")
363
+ if layout
364
+ template_method, template_location =
365
+ _template_for(layout, layout.index(".") ? nil : content_type, "layout")
366
+
324
367
  raise TemplateNotFound, "No layout found at #{template_location}" unless template_method
325
368
  template_method
326
369
 
@@ -331,13 +374,13 @@ module Merb::RenderMixin
331
374
  template
332
375
  end
333
376
  end
334
-
335
- # Iterate over the template roots in reverse order, and return the template
377
+
378
+ # Iterate over the template roots in reverse order, and return the template
336
379
  # and template location of the first match.
337
380
  #
338
381
  # ==== Parameters
339
- # context<Object>:: The controller action or template basename.
340
- # content_type<~to_s>:: The content type. Defaults to nil.
382
+ # context<Object>:: The controller action or template (basename or absolute path).
383
+ # content_type<~to_s>:: The content type (like html or json).
341
384
  # controller<~to_s>:: The name of the controller. Defaults to nil.
342
385
  #
343
386
  # ==== Options (opts)
@@ -348,25 +391,44 @@ module Merb::RenderMixin
348
391
  # ==== Returns
349
392
  # Array[Symbol, String]::
350
393
  # A pair consisting of the template method and location.
351
- def _template_for(context, content_type, controller=nil, opts={})
352
- template_method = nil
353
- template_location = nil
354
-
355
- self.class._template_roots.reverse_each do |root, template_location|
356
- if opts[:template] # use the given template as the location context
357
- template_location = root / self.send(template_location, opts[:template], content_type, nil)
358
- template_method = Merb::Template.template_for(template_location)
359
- break if template_method && self.respond_to?(template_method)
394
+ def _template_for(context, content_type, controller=nil, template=nil)
395
+ template_method, template_location = nil, nil
396
+
397
+ # absolute path to a template (:template => "/foo/bar")
398
+ if template.is_a?(String) && template =~ %r{^/}
399
+ template_location = self._absolute_template_location(template, content_type)
400
+ return [_template_method_for(template_location), template_location]
401
+ end
402
+
403
+ self.class._template_roots.reverse_each do |root, template_meth|
404
+ # :template => "foo/bar.html" where root / "foo/bar.html.*" exists
405
+ if template
406
+ template_location = root / self.send(template_meth, template, content_type, nil)
407
+ # :layout => "foo" where root / "layouts" / "#{controller}.html.*" exists
408
+ else
409
+ template_location = root / self.send(template_meth, context, content_type, controller)
360
410
  end
361
411
 
362
- template_location = root / (opts[:template] || self.send(template_location, context, content_type, controller))
363
- template_method = Merb::Template.template_for(template_location)
364
- break if template_method && self.respond_to?(template_method)
412
+ break if template_method = _template_method_for(template_location.to_s)
365
413
  end
366
414
 
367
- [template_method, template_location]
415
+ # template_location is a Pathname
416
+ [template_method, template_location.to_s]
368
417
  end
369
-
418
+
419
+ # Return the template method for a location, and check to make sure the current controller
420
+ # actually responds to the method.
421
+ #
422
+ # ==== Parameters
423
+ # template_location<String>:: The phyical path of the template
424
+ #
425
+ # ==== Returns
426
+ # String:: The method, if it exists. Otherwise return nil.
427
+ def _template_method_for(template_location)
428
+ meth = Merb::Template.template_for(template_location)
429
+ meth && self.respond_to?(meth) ? meth : nil
430
+ end
431
+
370
432
  # Called in templates to get at content thrown in another template. The
371
433
  # results of rendering a template are automatically thrown into :for_layout,
372
434
  # so catch_content or catch_content(:for_layout) can be used inside layouts
@@ -377,9 +439,9 @@ module Merb::RenderMixin
377
439
  #---
378
440
  # @public
379
441
  def catch_content(obj = :for_layout)
380
- @_caught_content[obj]
442
+ @_caught_content[obj] * '' unless @_caught_content[obj].nil?
381
443
  end
382
-
444
+
383
445
  # Called in templates to test for the existence of previously thrown content.
384
446
  #
385
447
  # ==== Parameters
@@ -389,7 +451,7 @@ module Merb::RenderMixin
389
451
  def thrown_content?(obj = :for_layout)
390
452
  @_caught_content.key?(obj)
391
453
  end
392
-
454
+
393
455
  # Called in templates to store up content for later use. Takes a string
394
456
  # and/or a block. First, the string is evaluated, and then the block is
395
457
  # captured using the capture() helper provided by the template languages. The
@@ -412,7 +474,8 @@ module Merb::RenderMixin
412
474
  unless string || block_given?
413
475
  raise ArgumentError, "You must pass a block or a string into throw_content"
414
476
  end
415
- @_caught_content[obj] = string.to_s << (block_given? ? capture(&block) : "")
477
+ @_caught_content[obj] = [] if @_caught_content[obj].nil?
478
+ @_caught_content[obj] << string.to_s << (block_given? ? capture(&block) : "")
416
479
  end
417
-
480
+
418
481
  end