Module | Merb::ResponderMixin |
In: |
merb-core/lib/merb-core/controller/mixins/responder.rb
|
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.
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.
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.
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.
TYPES | = | Dictionary.new |
MIMES | = | {} |
MIME_MUTEX | = | Mutex.new |
ACCEPT_RESULTS | = | {} |
base<Module>: | The module that ResponderMixin was mixed into |
:api: private
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 111 111: def self.included(base) 112: base.extend(ClassMethods) 113: base.class_eval do 114: class_inheritable_accessor :class_provided_formats 115: self.class_provided_formats = [] 116: end 117: base.reset_provides 118: end
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 262 262: def _accept_types 263: accept = request.accept 264: 265: MIME_MUTEX.synchronize do 266: return ACCEPT_RESULTS[accept] if ACCEPT_RESULTS[accept] 267: end 268: 269: types = request.accept.split(Merb::Const::ACCEPT_SPLIT).map do |entry| 270: entry =~ Merb::Const::MEDIA_RANGE 271: media_range, quality = $1, $3 272: 273: kind, sub_type = media_range.split(Merb::Const::SLASH_SPLIT) 274: mime_sym = Merb.available_accepts[media_range] 275: mime = Merb.available_mime_types[mime_sym] 276: (quality ||= 0.0) if media_range == "*/*" 277: quality = quality ? (quality.to_f * 100).to_i : 100 278: quality *= (mime && mime[:default_quality] || 1) 279: [quality, mime_sym, media_range, kind, sub_type, mime] 280: end 281: 282: accepts = types.sort_by {|x| x.first }.reverse!.map! {|x| x[1]} 283: 284: MIME_MUTEX.synchronize do 285: ACCEPT_RESULTS[accept] = accepts.freeze 286: end 287: 288: accepts 289: end
Do the content negotiation:
:api: private
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 299 299: def _perform_content_negotiation 300: if fmt = params[:format] 301: accepts = [fmt.to_sym] 302: else 303: accepts = _accept_types 304: end 305: 306: provided_formats = _provided_formats 307: 308: specifics = accepts & provided_formats 309: return specifics.first unless specifics.length == 0 310: return provided_formats.first if accepts.include?(:all) && !provided_formats.empty? 311: 312: message = "A format (%s) that isn't provided (%s) has been requested. " 313: message += "Make sure the action provides the format, and be " 314: message += "careful of before filters which won't recognize " 315: message += "formats provided within actions." 316: raise Merb::ControllerExceptions::NotAcceptable, 317: (message % [accepts.join(', '), provided_formats.join(', ')]) 318: end
Array[Symbol]: | 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
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 201 201: def _provided_formats 202: @_provided_formats ||= class_provided_formats.dup 203: end
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.
fmt<String>: | An optional format to use instead of performing content negotiation. This can be used to pass in the values of opts[:format] 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. |
Symbol: | The content-type that will be used for this controller. |
:api: public
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 345 345: def content_type(fmt = nil) 346: self.content_type = (fmt || _perform_content_negotiation) unless @_content_type 347: @_content_type 348: end
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.
type<Symbol>: | The content type. |
ArgumentError: | type is not in the list of registered mime-types. |
Symbol: | The content-type that was passed in. |
:api: plugin
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 364 364: def content_type=(type) 365: unless Merb.available_mime_types.has_key?(type) 366: raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}") 367: end 368: 369: @_content_type = type 370: 371: mime = Merb.available_mime_types[type] 372: 373: headers["Content-Type"] = mime[:content_type] 374: 375: # merge any format specific response headers 376: mime[:response_headers].each { |k,v| headers[k] ||= v } 377: 378: # if given, use a block to finetune any runtime headers 379: mime[:response_block].call(self) if mime[:response_block] 380: 381: @_content_type 382: end
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.
*formats<Symbol>: | Registered mime-type |
Array[Symbol]: | List of formats that remain after removing the ones not to provide. |
:api: public
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 258 258: def does_not_provide(*formats) 259: @_provided_formats -= formats.flatten 260: end
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.
*formats<Symbol>: | A list of formats to use as the per-action list of provided formats. |
Array[Symbol]: | List of formats passed in. |
:api: public
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 240 240: def only_provides(*formats) 241: @_provided_formats = [] 242: provides(*formats) 243: end
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.
*formats<Symbol>: | A list of formats to add to the per-action list of provided formats. |
Merb::ResponderMixin::ContentTypeAlreadySet: | Content negotiation already occured, and the content_type is set. |
Array[Symbol]: | List of formats passed in. |
:api: public
# File merb-core/lib/merb-core/controller/mixins/responder.rb, line 221 221: def provides(*formats) 222: if @_content_type 223: raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set" 224: end 225: @_provided_formats = self._provided_formats | formats # merges with class_provided_formats if not already 226: end