Module: Merb::ResponderMixin
- Included in:
- Controller
- Defined in:
- lib/merb-core/controller/mixins/responder.rb
Overview
The ResponderMixin adds methods that help you manage what formats your controllers have available, determine what format(s) the client requested and is capable of handling, and perform content negotiation to pick the proper content format to deliver.
If you hear someone say “Use provides” they’re talking about the Responder. If you hear someone ask “What happened to respond_to?” it was replaced by provides and the other Responder methods.
A simple example
The best way to understand how all of these pieces fit together is with an example. Here’s a simple web-service ready resource that provides a list of all the widgets we know about. The widget list is available in 3 formats: :html (the default), plus :xml and :text.
class Widgets < Application
provides :html # This is the default, but you can
# be explicit if you like.
provides :xml, :text
def index
@widgets = Widget.fetch
render @widgets
end
end
Let’s look at some example requests for this list of widgets. We’ll assume they’re all GET requests, but that’s only to make the examples easier; this works for the full set of RESTful methods.
-
The simplest case, /widgets.html Since the request includes a specific format (.html) we know what format to return. Since :html is in our list of provided formats, that’s what we’ll return.
render
will look for an index.html.erb (or another template format like index.html.mab; see the documentation on Template engines) -
Almost as simple, /widgets.xml This is very similar. They want :xml, we have :xml, so that’s what they get. If
render
doesn’t find an index.xml.builder or similar template, it will callto_xml
on @widgets. This may or may not do something useful, but you can see how it works. -
A browser request for /widgets This time the URL doesn’t say what format is being requested, so we’ll look to the HTTP Accept: header. If it’s ‘/’ (anything), we’ll use the first format on our list, :html by default.
If it parses to a list of accepted formats, we’ll look through them, in order, until we find one we have available. If we find one, we’ll use that. Otherwise, we can’t fulfill the request: they asked for a format we don’t have. So we raise 406: Not Acceptable.
A more complex example
Sometimes you don’t have the same code to handle each available format. Sometimes you need to load different data to serve /widgets.xml versus /widgets.txt. In that case, you can use content_type
to determine what format will be delivered.
class Widgets < Application
def action1
if content_type == :text
Widget.load_text_formatted(params[:id])
else
render
end
end
def action2
case content_type
when :html
handle_html()
when :xml
handle_xml()
when :text
handle_text()
else
render
end
end
end
You can do any standard Ruby flow control using content_type
. If you don’t call it yourself, it will be called (triggering content negotiation) by render
.
Once content_type
has been called, the output format is frozen, and none of the provides methods can be used.
Defined Under Namespace
Modules: ClassMethods Classes: ContentTypeAlreadySet
Constant Summary collapse
- TYPES =
Dictionary.new
- MIMES =
{}
- MIME_MUTEX =
Mutex.new
- ACCEPT_RESULTS =
{}
Class Method Summary collapse
-
.included(base) ⇒ Object
Parameters base<Module>:: The module that ResponderMixin was mixed into.
Instance Method Summary collapse
- #_accept_types ⇒ Object
-
#_perform_content_negotiation ⇒ Object
Do the content negotiation: 1.
-
#_provided_formats ⇒ Object
Returns Array:: The current list of formats provided for this instance of the controller.
-
#content_type(fmt = nil) ⇒ Object
Returns the output format for this request, based on the provided formats,
params[:format]
and the client’s HTTP Accept header. -
#content_type=(type) ⇒ Object
Sets the content type of the current response to a value based on a passed in key.
-
#does_not_provide(*formats) ⇒ Object
Removes formats from the list of provided formats for this particular request.
-
#only_provides(*formats) ⇒ Object
Sets list of provided formats for this particular request.
-
#provides(*formats) ⇒ Object
Adds formats to the list of provided formats for this particular request.
Class Method Details
.included(base) ⇒ Object
Parameters
- base<Module>
-
The module that ResponderMixin was mixed into
:api: private
111 112 113 114 115 116 117 118 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 111 def self.included(base) base.extend(ClassMethods) base.class_eval do class_inheritable_accessor :class_provided_formats self.class_provided_formats = [] end base.reset_provides end |
Instance Method Details
#_accept_types ⇒ Object
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 262 def _accept_types accept = request.accept MIME_MUTEX.synchronize do return ACCEPT_RESULTS[accept] if ACCEPT_RESULTS[accept] end types = request.accept.split(Merb::Const::ACCEPT_SPLIT).map do |entry| entry =~ Merb::Const::MEDIA_RANGE media_range, quality = $1, $3 kind, sub_type = media_range.split(Merb::Const::SLASH_SPLIT) mime_sym = Merb.available_accepts[media_range] mime = Merb.available_mime_types[mime_sym] (quality ||= 0.0) if media_range == "*/*" quality = quality ? (quality.to_f * 100).to_i : 100 quality *= (mime && mime[:default_quality] || 1) [quality, mime_sym, media_range, kind, sub_type, mime] end accepts = types.sort_by {|x| x.first }.reverse!.map! {|x| x[1]} MIME_MUTEX.synchronize do ACCEPT_RESULTS[accept] = accepts.freeze end accepts end |
#_perform_content_negotiation ⇒ Object
Do the content negotiation:
-
if params is there, and provided, use it
-
Parse the Accept header
-
If it’s /, use the first provided format
-
Look for one that is provided, in order of request
-
Raise 406 if none found
:api: private
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 299 def _perform_content_negotiation if fmt = params[:format] and !fmt.blank? accepts = [fmt.to_sym] else accepts = _accept_types end provided_formats = _provided_formats specifics = accepts & provided_formats return specifics.first unless specifics.length == 0 return provided_formats.first if accepts.include?(:all) && !provided_formats.empty? = "A format (%s) that isn't provided (%s) has been requested. " += "Make sure the action provides the format, and be " += "careful of before filters which won't recognize " += "formats provided within actions." raise Merb::ControllerExceptions::NotAcceptable, ( % [accepts.join(', '), provided_formats.join(', ')]) end |
#_provided_formats ⇒ Object
Returns
- Array
-
The current list of formats provided for this instance of the controller. It starts with what has been set in the controller (or :html by default) but can be modifed on a per-action basis.
:api: private
201 202 203 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 201 def _provided_formats @_provided_formats ||= class_provided_formats.dup end |
#content_type(fmt = nil) ⇒ Object
Returns the output format for this request, based on the provided formats, params[:format]
and the client’s HTTP Accept header.
The first time this is called, it triggers content negotiation and caches the value. Once you call content_type
you can not set or change the list of provided formats.
Called automatically by render
, so you should only call it if you need the value, not to trigger content negotiation.
Parameters
- fmt<String>
-
An optional format to use instead of performing content negotiation. This can be used to pass in the values of opts from the render function to short-circuit content-negotiation when it’s not necessary. This optional parameter should not be considered part of the public API.
Returns
- Symbol
-
The content-type that will be used for this controller.
:api: public
345 346 347 348 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 345 def content_type(fmt = nil) self.content_type = (fmt || _perform_content_negotiation) unless @_content_type @_content_type end |
#content_type=(type) ⇒ Object
Sets the content type of the current response to a value based on a passed in key. The Content-Type header will be set to the first registered header for the mime-type.
Parameters
- type<Symbol>
-
The content type.
Raises
- ArgumentError
-
type is not in the list of registered mime-types.
Returns
- Symbol
-
The content-type that was passed in.
:api: plugin
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 364 def content_type=(type) unless Merb.available_mime_types.has_key?(type) raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}") end @_content_type = type mime = Merb.available_mime_types[type] headers["Content-Type"] = mime[:content_type] # merge any format specific response headers mime[:response_headers].each { |k,v| headers[k] ||= v } # if given, use a block to finetune any runtime headers mime[:response_block].call(self) if mime[:response_block] @_content_type end |
#does_not_provide(*formats) ⇒ Object
Removes formats from the list of provided formats for this particular request. Usually used to remove formats from a single action. See also the controller-level does_not_provide that affects all actions in a controller.
Parameters
- *formats<Symbol>
-
Registered mime-type
Returns
- Array
-
List of formats that remain after removing the ones not to provide.
:api: public
258 259 260 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 258 def does_not_provide(*formats) @_provided_formats -= formats.flatten end |
#only_provides(*formats) ⇒ Object
Sets list of provided formats for this particular request. Usually used to limit formats to a single action. See also the controller-level only_provides that affects all actions in a controller.
Parameters
- *formats<Symbol>
-
A list of formats to use as the per-action list of provided formats.
Returns
- Array
-
List of formats passed in.
:api: public
240 241 242 243 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 240 def only_provides(*formats) @_provided_formats = [] provides(*formats) end |
#provides(*formats) ⇒ Object
Adds formats to the list of provided formats for this particular request. Usually used to add formats to a single action. See also the controller-level provides that affects all actions in a controller.
Parameters
- *formats<Symbol>
-
A list of formats to add to the per-action list of provided formats.
Raises
- Merb::ResponderMixin::ContentTypeAlreadySet
-
Content negotiation already occured, and the content_type is set.
Returns
- Array
-
List of formats passed in.
:api: public
221 222 223 224 225 226 |
# File 'lib/merb-core/controller/mixins/responder.rb', line 221 def provides(*formats) if @_content_type raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set" end @_provided_formats = self._provided_formats | formats # merges with class_provided_formats if not already end |