merb-core 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/Rakefile +61 -11
  2. data/bin/merb +5 -1
  3. data/lib/merb-core.rb +202 -25
  4. data/lib/merb-core/autoload.rb +19 -17
  5. data/lib/merb-core/bootloader.rb +84 -71
  6. data/lib/merb-core/config.rb +19 -14
  7. data/lib/merb-core/controller/abstract_controller.rb +16 -17
  8. data/lib/merb-core/controller/exceptions.rb +115 -70
  9. data/lib/merb-core/controller/merb_controller.rb +62 -38
  10. data/lib/merb-core/controller/mime.rb +1 -1
  11. data/lib/merb-core/controller/mixins/authentication.rb +87 -0
  12. data/lib/merb-core/controller/mixins/controller.rb +16 -15
  13. data/lib/merb-core/controller/mixins/render.rb +113 -19
  14. data/lib/merb-core/controller/mixins/responder.rb +8 -2
  15. data/lib/merb-core/controller/template.rb +1 -1
  16. data/lib/merb-core/core_ext.rb +1 -0
  17. data/lib/merb-core/core_ext/class.rb +113 -6
  18. data/lib/merb-core/core_ext/hash.rb +43 -39
  19. data/lib/merb-core/core_ext/kernel.rb +75 -38
  20. data/lib/merb-core/core_ext/mash.rb +4 -4
  21. data/lib/merb-core/core_ext/object.rb +18 -7
  22. data/lib/merb-core/core_ext/set.rb +9 -4
  23. data/lib/merb-core/core_ext/string.rb +29 -9
  24. data/lib/merb-core/core_ext/time.rb +13 -0
  25. data/lib/merb-core/dispatch/cookies.rb +1 -2
  26. data/lib/merb-core/dispatch/dispatcher.rb +18 -10
  27. data/lib/merb-core/dispatch/exceptions.html.erb +1 -1
  28. data/lib/merb-core/dispatch/request.rb +3 -0
  29. data/lib/merb-core/dispatch/router.rb +10 -7
  30. data/lib/merb-core/dispatch/router/behavior.rb +36 -27
  31. data/lib/merb-core/dispatch/router/route.rb +7 -2
  32. data/lib/merb-core/dispatch/session/cookie.rb +4 -4
  33. data/lib/merb-core/dispatch/session/memcached.rb +17 -5
  34. data/lib/merb-core/logger.rb +2 -2
  35. data/lib/merb-core/plugins.rb +16 -4
  36. data/lib/merb-core/rack/adapter/ebb.rb +4 -1
  37. data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -0
  38. data/lib/merb-core/rack/adapter/fcgi.rb +1 -0
  39. data/lib/merb-core/rack/adapter/mongrel.rb +1 -0
  40. data/lib/merb-core/rack/adapter/runner.rb +1 -0
  41. data/lib/merb-core/rack/adapter/thin.rb +3 -1
  42. data/lib/merb-core/rack/adapter/webrick.rb +1 -0
  43. data/lib/merb-core/rack/application.rb +17 -1
  44. data/lib/merb-core/server.rb +78 -28
  45. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -3
  46. data/lib/merb-core/test/helpers/request_helper.rb +81 -27
  47. data/lib/merb-core/test/helpers/view_helper.rb +1 -1
  48. data/lib/merb-core/test/matchers/controller_matchers.rb +55 -5
  49. data/lib/merb-core/test/matchers/route_matchers.rb +8 -17
  50. data/lib/merb-core/test/matchers/view_matchers.rb +53 -11
  51. data/lib/merb-core/test/run_specs.rb +22 -14
  52. data/lib/merb-core/test/tasks/spectasks.rb +54 -33
  53. data/lib/merb-core/vendor/facets/inflect.rb +91 -2
  54. data/lib/merb-core/version.rb +2 -2
  55. data/spec/private/config/config_spec.rb +54 -26
  56. data/spec/private/core_ext/class_spec.rb +22 -0
  57. data/spec/private/core_ext/hash_spec.rb +70 -54
  58. data/spec/private/core_ext/kernel_spec.rb +149 -14
  59. data/spec/private/core_ext/object_spec.rb +92 -10
  60. data/spec/private/core_ext/string_spec.rb +162 -4
  61. data/spec/private/core_ext/time_spec.rb +16 -0
  62. data/spec/private/dispatch/bootloader_spec.rb +24 -0
  63. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +1 -1
  64. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +1 -1
  65. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +1 -1
  66. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +1 -1
  67. data/spec/private/dispatch/fixture/config/black_hole.rb +12 -0
  68. data/spec/private/dispatch/fixture/log/merb_test.log +138 -0
  69. data/spec/private/plugins/plugin_spec.rb +79 -8
  70. data/spec/private/rack/application_spec.rb +1 -1
  71. data/spec/public/abstract_controller/controllers/filters.rb +26 -0
  72. data/spec/public/abstract_controller/controllers/helpers.rb +2 -2
  73. data/spec/public/abstract_controller/controllers/partial.rb +2 -2
  74. data/spec/public/abstract_controller/controllers/render.rb +16 -4
  75. data/spec/public/abstract_controller/filter_spec.rb +8 -0
  76. data/spec/public/abstract_controller/render_spec.rb +12 -0
  77. data/spec/public/controller/authentication_spec.rb +103 -0
  78. data/spec/public/controller/base_spec.rb +4 -3
  79. data/spec/public/controller/controllers/authentication.rb +47 -0
  80. data/spec/public/controller/controllers/base.rb +1 -0
  81. data/spec/public/controller/controllers/display.rb +30 -0
  82. data/spec/public/controller/controllers/views/layout/custom_arg.html.erb +1 -0
  83. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template_argument/index.html.erb +1 -0
  84. data/spec/public/controller/display_spec.rb +17 -0
  85. data/spec/public/controller/spec_helper.rb +1 -0
  86. data/spec/public/controller/url_spec.rb +25 -7
  87. data/spec/public/core/merb_core_spec.rb +34 -0
  88. data/spec/public/directory_structure/directory/app/controllers/custom.rb +2 -2
  89. data/spec/public/directory_structure/directory/log/merb_test.log +48 -0
  90. data/spec/public/logger/logger_spec.rb +10 -4
  91. data/spec/public/reloading/directory/app/controllers/reload.rb +1 -1
  92. data/spec/public/reloading/directory/log/merb_test.log +13 -0
  93. data/spec/public/reloading/reload_spec.rb +23 -22
  94. data/spec/public/request/request_spec.rb +2 -0
  95. data/spec/public/router/nested_resources_spec.rb +7 -0
  96. data/spec/public/router/resources_spec.rb +46 -1
  97. data/spec/public/router/special_spec.rb +5 -1
  98. data/spec/public/test/controller_matchers_spec.rb +25 -1
  99. data/spec/public/test/controllers/spec_helper_controller.rb +8 -0
  100. data/spec/public/test/request_helper_spec.rb +52 -1
  101. data/spec/public/test/route_matchers_spec.rb +27 -25
  102. data/spec/public/test/view_helper_spec.rb +1 -1
  103. data/spec/public/test/view_matchers_spec.rb +148 -72
  104. metadata +23 -3
@@ -1,22 +1,23 @@
1
1
  class Merb::Controller < Merb::AbstractController
2
-
2
+
3
3
  class_inheritable_accessor :_hidden_actions, :_shown_actions
4
4
  cattr_accessor :_subclasses, :_session_id_key, :_session_secret_key, :_session_expiry
5
5
  self._subclasses = Set.new
6
6
 
7
7
  def self.subclasses_list() _subclasses end
8
-
8
+
9
9
  self._session_secret_key = nil
10
10
  self._session_id_key = Merb::Config[:session_id_key] || '_session_id'
11
11
  self._session_expiry = Merb::Config[:session_expiry] || Merb::Const::WEEK * 2
12
-
12
+
13
13
  include Merb::ResponderMixin
14
14
  include Merb::ControllerMixin
15
+ include Merb::AuthenticationMixin
15
16
 
16
17
  attr_accessor :route
17
-
18
+
18
19
  class << self
19
-
20
+
20
21
  # ==== Parameters
21
22
  # klass<Merb::Controller>::
22
23
  # The Merb::Controller inheriting from the base class.
@@ -34,7 +35,7 @@ class Merb::Controller < Merb::AbstractController
34
35
  # ==== Returns
35
36
  # Array[String]::
36
37
  # An array of actions that should not be possible to dispatch to.
37
- #
38
+ #
38
39
  #---
39
40
  # @public
40
41
  def hide_action(*names)
@@ -51,7 +52,7 @@ class Merb::Controller < Merb::AbstractController
51
52
  # Array[String]::
52
53
  # An array of actions that should be dispatched to even if they would not
53
54
  # otherwise be.
54
- #
55
+ #
55
56
  # ==== Example
56
57
  # module Foo
57
58
  # def self.included(base)
@@ -74,7 +75,7 @@ class Merb::Controller < Merb::AbstractController
74
75
  end
75
76
 
76
77
  # This list of actions that should not be callable.
77
- #
78
+ #
78
79
  # ==== Returns
79
80
  # Array[String]:: An array of actions that should not be dispatchable.
80
81
  def _hidden_actions
@@ -83,14 +84,14 @@ class Merb::Controller < Merb::AbstractController
83
84
  end
84
85
 
85
86
  # This list of actions that should be callable.
86
- #
87
+ #
87
88
  # ==== Returns
88
89
  # Array[String]::
89
90
  # An array of actions that should be dispatched to even if they would not
90
91
  # otherwise be.
91
92
  def _shown_actions
92
93
  actions = read_inheritable_attribute(:_shown_actions)
93
- actions ? actions : write_inheritable_attribute(:_shown_actions, [])
94
+ actions ? actions : write_inheritable_attribute(:_shown_actions, [])
94
95
  end
95
96
 
96
97
  # The list of actions that are callable, after taking defaults,
@@ -98,7 +99,7 @@ class Merb::Controller < Merb::AbstractController
98
99
  # once, the first time an action is dispatched for this controller.
99
100
  #
100
101
  # ==== Returns
101
- # Array[String]:: A list of actions that should be callable.
102
+ # SimpleSet[String]:: A set of actions that should be callable.
102
103
  def callable_actions
103
104
  unless @callable_actions
104
105
  callables = []
@@ -106,40 +107,53 @@ class Merb::Controller < Merb::AbstractController
106
107
  begin
107
108
  callables << (klass.public_instance_methods(false) + klass._shown_actions) - klass._hidden_actions
108
109
  klass = klass.superclass
109
- end until klass == Merb::Controller || klass == Object
110
+ end until klass == Merb::AbstractController || klass == Object
110
111
  @callable_actions = Merb::SimpleSet.new(callables.flatten)
111
112
  end
112
113
  @callable_actions
113
114
  end
114
-
115
- end
116
-
117
- # The location to look for a template for a particular controller, action,
115
+
116
+ # This is a stub method so plugins can implement param filtering if they want.
117
+ #
118
+ # ==== Parameters
119
+ # params<Hash{Symbol => String}>:: A list of params
120
+ #
121
+ # ==== Returns
122
+ # Hash{Symbol => String}:: A new list of params, filtered as desired
123
+ #---
124
+ # @semipublic
125
+ def _filter_params(params)
126
+ params
127
+ end
128
+
129
+ end # class << self
130
+
131
+ # The location to look for a template for a particular controller, context,
118
132
  # and mime-type. This is overridden from AbstractController, which defines a
119
133
  # version of this that does not involve mime-types.
120
134
  #
121
135
  # ==== Parameters
122
- # action<~to_s>:: The name of the action that will be rendered.
136
+ # context<~to_s>:: The name of the action or template basename that will be rendered.
123
137
  # type<~to_s>::
124
138
  # The mime-type of the template that will be rendered. Defaults to nil.
125
139
  # controller<~to_s>::
126
140
  # The name of the controller that will be rendered. Defaults to
127
141
  # controller_name.
128
142
  #
129
- # ==== Note
143
+ # ==== Notes
130
144
  # By default, this renders ":controller/:action.:type". To change this,
131
145
  # override it in your application class or in individual controllers.
132
146
  #
133
147
  #---
134
148
  # @public
135
- def _template_location(action, type = nil, controller = controller_name)
136
- "#{controller}/#{action}.#{type}"
137
- end
138
-
149
+ def _template_location(context, type = nil, controller = controller_name)
150
+ controller ? "#{controller}/#{context}.#{type}" : "#{context}.#{type}"
151
+ end
152
+
139
153
  # Build a new controller.
140
154
  #
141
155
  # Sets the variables that came in through the dispatch as available to
142
- # the controller.
156
+ # the controller.
143
157
  #
144
158
  # This method uses the :session_id_cookie_only and :query_string_whitelist
145
159
  # configuration options. See CONFIG for more details.
@@ -147,7 +161,7 @@ class Merb::Controller < Merb::AbstractController
147
161
  # ==== Parameters
148
162
  # request<Merb::Request>:: The Merb::Request that came in from Mongrel.
149
163
  # status<Integer>:: An integer code for the status. Defaults to 200.
150
- # headers<Hash{header => value}>::
164
+ # headers<Hash{header => value}>::
151
165
  # A hash of headers to start the controller with. These headers can be
152
166
  # overridden later by the #headers method.
153
167
  #---
@@ -156,7 +170,7 @@ class Merb::Controller < Merb::AbstractController
156
170
  super()
157
171
  @request, @status, @headers = request, status, headers
158
172
  end
159
-
173
+
160
174
  # Dispatch the action.
161
175
  #
162
176
  # ==== Parameters
@@ -178,37 +192,47 @@ class Merb::Controller < Merb::AbstractController
178
192
  end
179
193
  @_benchmarks[:action_time] = Time.now - start
180
194
  end
181
-
195
+
182
196
  attr_reader :request, :headers
183
- attr_accessor :status
184
-
197
+ attr_reader :status
198
+
199
+ # Set the response status code.
200
+ #
201
+ # ==== Parameters
202
+ # s<Fixnum, Symbol>:: A status-code or named http-status
203
+ def status=(s)
204
+ if s.is_a?(Symbol) && STATUS_CODES.key?(s)
205
+ @status = STATUS_CODES[s]
206
+ elsif s.is_a?(Fixnum)
207
+ @status = s
208
+ else
209
+ raise ArgumentError, "Status should be of type Fixnum or Symbol, was #{s.class}"
210
+ end
211
+ end
212
+
185
213
  # ==== Returns
186
214
  # Hash:: The parameters from the request object
187
215
  def params() request.params end
188
-
216
+
189
217
  # ==== Returns
190
- # Merb::Cookies::
218
+ # Merb::Cookies::
191
219
  # A new Merb::Cookies instance representing the cookies that came in
192
220
  # from the request object
193
221
  #
194
- # ==== Note
222
+ # ==== Notes
195
223
  # Headers are passed into the cookie object so that you can do:
196
224
  # cookies[:foo] = "bar"
197
225
  def cookies() @_cookies ||= _setup_cookies end
198
-
226
+
199
227
  # ==== Returns
200
228
  # Hash:: The session that was extracted from the request object.
201
229
  def session() request.session end
202
-
230
+
203
231
  private
204
232
 
205
233
  # Create a default cookie jar, and pre-set a fixation cookie
206
234
  # if fixation is enabled
207
235
  def _setup_cookies
208
- cookies = ::Merb::Cookies.new(request.cookies, @headers)
209
- if request.params.key?(_session_id_key) && route.allow_fixation?
210
- cookies[_session_id_key] = request.params[_session_id_key]
211
- end
212
- cookies
236
+ ::Merb::Cookies.new(request.cookies, @headers)
213
237
  end
214
238
  end
@@ -53,7 +53,7 @@ module Merb
53
53
  # ==== Parameters
54
54
  # key<Symbol>:: The key that represents the mime-type to remove.
55
55
  #
56
- # ==== Note
56
+ # ==== Notes
57
57
  # :all is the key for */*; It can't be removed.
58
58
  def remove_mime_type(key)
59
59
  return false if key == :all
@@ -0,0 +1,87 @@
1
+ module Merb::AuthenticationMixin
2
+
3
+ # Attempts to authenticate the user via HTTP Basic authentication. Takes a
4
+ # block with the username and password, if the block yields false the
5
+ # authentication is not accepted and :halt is thrown.
6
+ #
7
+ # If no block is passed, +basic_authentication+, the +request+ and +authenticate+
8
+ # methods can be chained. These can be used to independently request authentication
9
+ # or confirm it, if more control is desired.
10
+ #
11
+ # ==== Parameters
12
+ # realm<~to_s>:: The realm to authenticate against. Defaults to 'Application'.
13
+ # &authenticator:: A block to check if the authentication is valid.
14
+ #
15
+ # ==== Examples
16
+ # class Application < Merb::Controller
17
+ #
18
+ # before :authenticate
19
+ #
20
+ # protected
21
+ #
22
+ # def authenticate
23
+ # basic_authentication("My App") do |username, password|
24
+ # password == "secret"
25
+ # end
26
+ # end
27
+ #
28
+ # end
29
+ #
30
+ # class Application < Merb::Controller
31
+ #
32
+ # before :authenticate
33
+ #
34
+ # def authenticate
35
+ # user = basic_authentication.authenticate do |username, password|
36
+ # User.authenticate(username, password)
37
+ # end
38
+ #
39
+ # if user
40
+ # @current_user = user
41
+ # else
42
+ # basic_authentication.request
43
+ # end
44
+ # end
45
+ #
46
+ # end
47
+ #
48
+ #---
49
+ # @public
50
+ def basic_authentication(realm = "Application", &authenticator)
51
+ BasicAuthentication.new(self, realm, &authenticator)
52
+ end
53
+
54
+ class BasicAuthentication #:nodoc:
55
+ # So we can have access to the status codes
56
+ include Merb::ControllerExceptions
57
+
58
+ def initialize(controller, realm = "Application", &authenticator)
59
+ @controller = controller
60
+ @realm = realm
61
+ authenticate_or_request(&authenticator) if authenticator
62
+ end
63
+
64
+ def authenticate(&authenticator)
65
+ auth = Rack::Auth::Basic::Request.new(@controller.request.env)
66
+
67
+ if auth.provided? and auth.basic?
68
+ authenticator.call(*auth.credentials)
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def request
75
+ @controller.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
76
+ throw :halt, @controller.render("HTTP Basic: Access denied.\n", :status => Unauthorized.status, :layout => false)
77
+ end
78
+
79
+ protected
80
+
81
+ def authenticate_or_request(&authenticator)
82
+ authenticate(&authenticator) || request
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -186,15 +186,17 @@ module Merb
186
186
  opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
187
187
  disposition = opts[:disposition].dup || 'attachment'
188
188
  disposition << %(; filename="#{opts[:filename]}")
189
- response.headers.update(
189
+ headers.update(
190
190
  'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
191
191
  'Content-Disposition' => disposition,
192
192
  'Content-Transfer-Encoding' => 'binary',
193
193
  'CONTENT-LENGTH' => opts[:content_length]
194
194
  )
195
- response.send_status(opts[:content_length])
196
- response.send_header
197
- stream
195
+ Proc.new{|response|
196
+ response.send_status(opts[:content_length])
197
+ response.send_header
198
+ stream.call(response)
199
+ }
198
200
  end
199
201
 
200
202
  # Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
@@ -208,22 +210,19 @@ module Merb
208
210
  return
209
211
  end
210
212
 
211
- # Sets a cookie to be included in the response. This method is used
212
- # primarily internally in Merb.
213
+ # Sets a cookie to be included in the response.
213
214
  #
214
215
  # If you need to set a cookie, then use the +cookies+ hash.
215
216
  #
216
217
  # ==== Parameters
217
218
  # name<~to_s>:: A name for the cookie.
218
219
  # value<~to_s>:: A value for the cookie.
219
- # expires<~gmtime:~strftime>:: An expiration time for the cookie.
220
+ # expires<~gmtime:~strftime, Hash>:: An expiration time for the cookie, or a hash of cookie options.
221
+ # ---
222
+ # @public
220
223
  def set_cookie(name, value, expires)
221
- (headers['Set-Cookie'] ||=[]) << (Merb::Const::SET_COOKIE % [
222
- name.to_s,
223
- ::Merb::Request.escape(value.to_s),
224
- # Cookie expiration time must be GMT. See RFC 2109
225
- expires.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
226
- ])
224
+ options = expires.is_a?(Hash) ? expires : {:expires => expires}
225
+ cookies.set_cookie(name, value, options)
227
226
  end
228
227
 
229
228
  # Marks a cookie as deleted and gives it an expires stamp in the past. This
@@ -256,7 +255,9 @@ module Merb
256
255
  # ==== Raises
257
256
  # NotImplemented:: The Rack adapter doens't support streaming.
258
257
  def must_support_streaming!
259
- raise(NotImplemented, "Current Rack adapter does not support streaming") unless request.env['rack.streaming']
258
+ unless request.env['rack.streaming']
259
+ raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter does not support streaming")
260
+ end
260
261
  end
261
262
  end
262
- end
263
+ end
@@ -5,11 +5,49 @@ module Merb::RenderMixin
5
5
  # ==== Parameters
6
6
  # base<Module>:: Module that is including RenderMixin (probably a controller)
7
7
  def self.included(base)
8
+ base.extend(ClassMethods)
8
9
  base.class_eval do
9
- class_inheritable_accessor :_layout, :_cached_templates
10
+ class_inheritable_accessor :_default_render_options
10
11
  end
11
12
  end
12
13
 
14
+ module ClassMethods
15
+
16
+ # Return the default render options.
17
+ #
18
+ # ==== Returns
19
+ # Hash:: An options hash
20
+ def default_render_options
21
+ self._default_render_options ||= {}
22
+ end
23
+
24
+ # Set default render options at the class level.
25
+ #
26
+ # ==== Parameters
27
+ # opts<Hash>:: An options hash
28
+ def render_options(opts)
29
+ self._default_render_options = opts
30
+ end
31
+
32
+ # Set the default layout to use or nil/false to disable layout rendering.
33
+ # This is a shortcut for render_options :layout => false.
34
+ #
35
+ # ==== Parameters
36
+ # layout<~to_s>:: The layout that should be used for this class
37
+ #
38
+ # ==== Returns
39
+ # Hash:: The default render options.
40
+ def layout(layout)
41
+ self.default_render_options.update(:layout => (layout ? layout : false))
42
+ end
43
+
44
+ # Enable the default layout logic - reset the layout option.
45
+ def default_layout
46
+ self.default_render_options.delete(:layout)
47
+ end
48
+
49
+ end
50
+
13
51
  # Render the specified item, with the specified options.
14
52
  #
15
53
  # ==== Parameters
@@ -46,6 +84,9 @@ module Merb::RenderMixin
46
84
  # render :format => :xml means render nil, :format => :xml
47
85
  opts, thing = thing, nil if thing.is_a?(Hash)
48
86
 
87
+ # Merge with class level default render options
88
+ opts = self.class.default_render_options.merge(opts)
89
+
49
90
  # If you don't specify a thing to render, assume they want to render the current action
50
91
  thing ||= action_name.to_sym
51
92
 
@@ -91,7 +132,9 @@ module Merb::RenderMixin
91
132
  # thing<String, Symbol>::
92
133
  # The thing to attempt to render via #render before calling the transform
93
134
  # method on the object. Defaults to nil.
94
- # opts<Hash>:: An options hash that will be passed on to #render
135
+ # opts<Hash>::
136
+ # An options hash that will be used for rendering
137
+ # (passed on to #render or serialization methods like #to_json or #to_xml)
95
138
  #
96
139
  # ==== Returns
97
140
  # String::
@@ -111,32 +154,59 @@ module Merb::RenderMixin
111
154
  # display @object, :layout => "zoo"
112
155
  # #=> display @object, nil, :layout => "zoo"
113
156
  #
114
- # ==== Note
157
+ # If you need to pass extra parameters to serialization method, for instance,
158
+ # to exclude some of attributes or serialize associations, just pass options
159
+ # for it.
160
+ # For instance,
161
+ #
162
+ # display @locations, :except => [:locatable_type, :locatable_id], :include => [:locatable]
163
+ #
164
+ # serializes object with polymorphic association, not raw locatable_* attributes.
165
+ #
166
+ #
167
+ # ==== Options
168
+ #
169
+ # :template a template to use for rendering
170
+ # :layout a layout to use for rendering
171
+
172
+ # all other options options that will be pass to serialization method
173
+ # like #to_json or #to_xml
174
+ #
175
+ # ==== Notes
115
176
  # The transformed object will not be used in a layout unless a :layout is
116
177
  # explicitly passed in the opts.
178
+ #
117
179
  def display(object, thing = nil, opts = {})
118
180
  # display @object, "path/to/foo" means display @object, nil, :template => "path/to/foo"
119
181
  # display @object, :template => "path/to/foo" means display @object, nil, :template => "path/to/foo"
120
- opts[:template], thing = thing, nil if thing.is_a?(String) || thing.is_a?(Hash)
182
+ template_opt = opts.delete(:template)
183
+
184
+ case thing
185
+ when String
186
+ template_opt, thing = thing, nil
187
+ when Hash
188
+ opts, thing = thing, nil
189
+ end
121
190
 
122
191
  # Try to render without the object
123
- render(thing || action_name.to_sym, opts)
192
+ render(thing || action_name.to_sym, opts.merge(:template => template_opt))
124
193
 
125
194
  # If the render fails (i.e. a template was not found)
126
195
  rescue TemplateNotFound
196
+ # Merge with class level default render options
197
+ opts = self.class.default_render_options.merge(opts)
127
198
 
128
199
  # Figure out what to transform and raise NotAcceptable unless there's a transform method assigned
129
200
  transform = Merb.mime_transform_method(content_type)
130
201
  raise NotAcceptable unless transform && object.respond_to?(transform)
131
-
132
- # Throw the transformed object for later consumption by the layout
133
- throw_content(:for_layout, object.send(transform))
134
-
202
+
135
203
  # Only use a layout if one was specified
136
- if opts[:layout]
204
+ layout_opt = opts.delete(:layout)
205
+
206
+ if layout_opt
137
207
  # Look for the layout under the default layout directly. If it's not found, reraise
138
208
  # the TemplateNotFound error
139
- template = _template_location(opts[:layout], layout.index(".") ? content_type : nil, "layout")
209
+ template = _template_location(layout_opt, layout.index(".") ? content_type : nil, "layout")
140
210
  layout = _template_for(_template_root / template) ||
141
211
  (raise TemplateNotFound, "No layout found at #{_template_root / template}.*")
142
212
 
@@ -145,6 +215,12 @@ module Merb::RenderMixin
145
215
 
146
216
  # Otherwise, just render the transformed object
147
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
148
224
  catch_content(:for_layout)
149
225
  end
150
226
  end
@@ -195,7 +271,11 @@ module Merb::RenderMixin
195
271
  end.join
196
272
  else
197
273
  @_merb_partial_locals = opts
198
- sent_template = send(template_method)
274
+ if template_method && self.respond_to?(template_method)
275
+ sent_template = send(template_method)
276
+ else
277
+ raise TemplateNotFound, "Could not find template at #{template_location}.*"
278
+ end
199
279
  end
200
280
  @_merb_partial_locals = @_old_partial_locals.pop
201
281
  sent_template
@@ -235,9 +315,7 @@ module Merb::RenderMixin
235
315
  # one in to this method), and not found. No error will be raised if no
236
316
  # layout was specified, and the default layouts were not found.
237
317
  def _get_layout(layout = nil)
238
- if _layout && !layout
239
- layout = _layout.instance_of?(Symbol) && self.respond_to?(_layout, true) ? send(_layout) : _layout
240
- end
318
+ layout = layout.instance_of?(Symbol) && self.respond_to?(layout, true) ? send(layout) : layout
241
319
  layout = layout.to_s if layout
242
320
 
243
321
  # If a layout was provided, throw an error if it's not found
@@ -258,24 +336,30 @@ module Merb::RenderMixin
258
336
  # and template location of the first match.
259
337
  #
260
338
  # ==== Parameters
261
- # thing<Object>:: The controller action.
339
+ # context<Object>:: The controller action or template basename.
262
340
  # content_type<~to_s>:: The content type. Defaults to nil.
263
341
  # controller<~to_s>:: The name of the controller. Defaults to nil.
264
342
  #
265
343
  # ==== Options (opts)
266
344
  # :template<String>::
267
345
  # The location of the template to use. Defaults to whatever matches this
268
- # thing, content_type and controller.
346
+ # context, content_type and controller.
269
347
  #
270
348
  # ==== Returns
271
349
  # Array[Symbol, String]::
272
350
  # A pair consisting of the template method and location.
273
- def _template_for(thing, content_type, controller=nil, opts={})
351
+ def _template_for(context, content_type, controller=nil, opts={})
274
352
  template_method = nil
275
353
  template_location = nil
276
354
 
277
355
  self.class._template_roots.reverse_each do |root, template_location|
278
- template_location = root / (opts[:template] || self.send(template_location, thing, content_type, controller))
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)
360
+ end
361
+
362
+ template_location = root / (opts[:template] || self.send(template_location, context, content_type, controller))
279
363
  template_method = Merb::Template.template_for(template_location)
280
364
  break if template_method && self.respond_to?(template_method)
281
365
  end
@@ -296,6 +380,16 @@ module Merb::RenderMixin
296
380
  @_caught_content[obj]
297
381
  end
298
382
 
383
+ # Called in templates to test for the existence of previously thrown content.
384
+ #
385
+ # ==== Parameters
386
+ # obj<Object>:: The key in the thrown_content hash. Defaults to :for_layout.
387
+ #---
388
+ # @public
389
+ def thrown_content?(obj = :for_layout)
390
+ @_caught_content.key?(obj)
391
+ end
392
+
299
393
  # Called in templates to store up content for later use. Takes a string
300
394
  # and/or a block. First, the string is evaluated, and then the block is
301
395
  # captured using the capture() helper provided by the template languages. The