merb-core 0.9.2 → 0.9.3

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