Module: Merb::ResponderMixin
- Included in:
- Controller
- Defined in:
- merb-core/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_xmlon @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
- TYPES =
Dictionary.new
- MIMES =
{}
- MIME_MUTEX =
Mutex.new
- ACCEPT_RESULTS =
{}
Class Method Summary (collapse)
- + (Object) included(base) private
Instance Method Summary (collapse)
- - (Object) _accept_types
-
- (Object) _perform_content_negotiation
private
Do the content negotiation:.
-
- (Array<Symbol>) _provided_formats
private
The current list of formats provided for this instance of the controller.
-
- (Symbol) content_type(fmt = nil)
Returns the output format for this request, based on the provided formats,
params[:format]and the client's HTTP Accept header. -
- (Symbol) content_type=(type)
Sets the content type of the current response to a value based on a passed in key.
-
- (Array<Symbol>) does_not_provide(*formats)
Removes formats from the list of provided formats for this particular request.
-
- (Array<Symbol>) only_provides(*formats)
Sets list of provided formats for this particular request.
-
- (Array<Symbol>) provides(*formats)
Adds formats to the list of provided formats for this particular request.
Class Method Details
+ (Object) included(base)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
110 111 112 113 114 115 116 117 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 110 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
- (Object) _accept_types
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 245 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 |
- (Object) _perform_content_negotiation
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Do the content negotiation:
- if params[:format] 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
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 286 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 |
- (Array<Symbol>) _provided_formats
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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.
191 192 193 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 191 def _provided_formats @_provided_formats ||= class_provided_formats.dup end |
- (Symbol) content_type(fmt = nil)
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.
329 330 331 332 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 329 def content_type(fmt = nil) self.content_type = (fmt || _perform_content_negotiation) unless @_content_type @_content_type end |
- (Symbol) content_type=(type)
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.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 346 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 |
- (Array<Symbol>) does_not_provide(*formats)
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.
241 242 243 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 241 def does_not_provide(*formats) @_provided_formats -= formats.flatten end |
- (Array<Symbol>) only_provides(*formats)
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.
225 226 227 228 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 225 def only_provides(*formats) @_provided_formats = [] provides(*formats) end |
- (Array<Symbol>) provides(*formats)
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.
208 209 210 211 212 213 |
# File 'merb-core/lib/merb-core/controller/mixins/responder.rb', line 208 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 |