Changeset 798
- Timestamp:
- 10/30/07 21:27:37 (1 year ago)
- Files:
-
- trunk/autotest/merbsource_rspec.rb (modified) (1 diff)
- trunk/lib/merb/controller.rb (modified) (1 diff)
- trunk/lib/merb/mixins/responder.rb (modified) (4 diffs)
- trunk/spec/merb/controller_spec.rb (modified) (1 diff)
- trunk/spec/merb/responder_spec.rb (modified) (1 diff)
- trunk/tools/allison/cache/BODY (modified) (3 diffs)
- trunk/tools/allison/cache/INDEX (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/autotest/merbsource_rspec.rb
r685 r798 20 20 }, 21 21 %r%^lib/merb/(.*)\.rb$% => kernel.proc { |_, m| 22 ["spec/#{m[ 1]}_spec.rb", "spec/merb/#{m[1]}_spec.rb"]22 ["spec/#{m[0]}_spec.rb", "spec/merb/#{m[0]}_spec.rb"] 23 23 }, 24 24 %r%^lib/merb\.rb$% => kernel.proc { trunk/lib/merb/controller.rb
r775 r798 48 48 cont 49 49 end 50 end51 52 53 class_inheritable_accessor :class_provided_formats54 self.class_provided_formats = [:html]55 56 # set controller-level provides57 def self.provides(*formats)58 formats.each {|fmt|59 self.class_provided_formats << fmt unless class_provided_formats.include?(fmt)60 }61 end62 63 def self.only_provides(*formats)64 self.class_provided_formats = formats65 end66 67 def self.does_not_provide(*formats)68 self.class_provided_formats -= formats69 end70 71 def provided_formats72 @_provided_formats ||= class_provided_formats73 end74 75 def provided_formats=(*formats)76 raise "Cannot modify provided_formats because content_type has already been set" if content_type_set?77 @_provided_formats = formats.flatten78 end79 80 def provides(*formats)81 formats.each {|fmt|82 self.provided_formats += [fmt] unless provided_formats.include?(fmt)83 }84 end85 86 def only_provides(*formats)87 self.provided_formats = formats.flatten88 end89 90 def does_not_provide(*formats)91 self.provided_formats -= formats.flatten92 50 end 93 51 trunk/lib/merb/mixins/responder.rb
r795 r798 8 8 end 9 9 10 # use this in your controllers to switch output based on 11 # the HTTP_ACCEPT header. like so: 12 # respond_to do |type| 13 # type.js { render_js } 14 # type.html { render } 15 # type.xml { @foo.to_xml } 16 # type.yaml { @foo.to_yaml } 17 # end 18 # 19 # Any specific outgoing headers should be included here. These are not the content-type header 20 # but anything in addition to it. 21 # +tranform_method+ should be set to a symbol of the method used to transform a resource into this mime type. 22 # for example for the :xml mime type an object might be transformed by calling :to_xml 23 # or for the :js mime type, :to_json 24 # If there is no transform method, use nil 25 10 # Any specific outgoing headers should be included here. These are not 11 # the content-type header but anything in addition to it. 12 # +tranform_method+ should be set to a symbol of the method used to 13 # transform a resource into this mime type. 14 # For example for the :xml mime type an object might be transformed by 15 # calling :to_xml, or for the :js mime type, :to_json. 16 # If there is no transform method, use nil. 26 17 def self.add_mime_type(key,transform_method, values,new_response_headers = {}) 27 18 raise ArgumentError unless key.is_a?(Symbol) && values.is_a?(Array) … … 75 66 Merb.add_mime_type(:json,:to_json,%w[application/json text/x-json ]) 76 67 end 77 78 68 69 70 # The ResponderMixin adds methods that help you manage what 71 # formats your controllers have available, determine what format(s) 72 # the client requested and is capable of handling, and perform 73 # content negotiation to pick the proper content format to 74 # deliver. 75 # 76 # If you hear someone say "Use provides" they're talking about the 77 # Responder. If you hear someone ask "What happened to respond_to?" 78 # it was replaced by provides and the other Responder methods. 79 # 80 # == A simple example 81 # 82 # The best way to understand how all of these pieces fit together is 83 # with an example. Here's a simple web-service ready resource that 84 # provides a list of all the widgets we know about. The widget list is 85 # available in 3 formats: :html (the default), plus :xml and :text. 86 # 87 # class Widgets < Application 88 # provides :html # This is the default, but you can 89 # # be explicit if you like. 90 # provides :xml, :text 91 # 92 # def index 93 # @widgets = Widget.fetch 94 # render @widgets 95 # end 96 # end 97 # 98 # Let's look at some example requests for this list of widgets. We'll 99 # assume they're all GET requests, but that's only to make the examples 100 # easier; this works for the full set of RESTful methods. 101 # 102 # 1. The simplest case, /widgets.html 103 # Since the request includes a specific format (.html) we know 104 # what format to return. Since :html is in our list of provided 105 # formats, that's what we'll return. +render+ will look 106 # for an index.html.erb (or another template format 107 # like index.html.mab; see the documentation on Template engines) 108 # 109 # 2. Almost as simple, /widgets.xml 110 # This is very similar. They want :xml, we have :xml, so 111 # that's what they get. If +render+ doesn't find an 112 # index.xml.builder or similar template, it will call +to_xml+ 113 # on @widgets. This may or may not do something useful, but you can 114 # see how it works. 115 # 116 # 3. A browser request for /widgets 117 # This time the URL doesn't say what format is being requested, so 118 # we'll look to the HTTP Accept: header. If it's '*/*' (anything), 119 # we'll use the first format on our list, :html by default. 120 # 121 # If it parses to a list of accepted formats, we'll look through 122 # them, in order, until we find one we have available. If we find 123 # one, we'll use that. Otherwise, we can't fulfill the request: 124 # they asked for a format we don't have. So we raise 125 # 406: Not Acceptable. 126 # 127 # == A more complex example 128 # 129 # Sometimes you don't have the same code to handle each available 130 # format. Sometimes you need to load different data to serve 131 # /widgets.xml versus /widgets.txt. In that case, you can use 132 # +content_type+ to determine what format will be delivered. 133 # 134 # class Widgets < Application 135 # def action1 136 # if content_type == :text 137 # Widget.load_text_formatted(params[:id]) 138 # else 139 # render 140 # end 141 # end 142 # 143 # def action2 144 # case content_type 145 # when :html 146 # handle_html() 147 # when :xml 148 # handle_xml() 149 # when :text 150 # handle_text() 151 # else 152 # render 153 # end 154 # end 155 # end 156 # 157 # You can do any standard Ruby flow control using +content_type+. If 158 # you don't call it yourself, it will be called (triggering content 159 # negotiation) by +render+. 160 # 161 # Once +content_type+ has been called, the output format is frozen, 162 # and none of the provides methods can be used. 79 163 module ResponderMixin 80 164 81 # def respond_to(&block) 82 # responder = Rest::Responder.new(request.accept, params) 83 # block.call(responder) 84 # responder.respond(headers) 85 # @_status = responder.status if responder.status 86 # responder.body 87 # end 88 89 def perform_content_negotiation 165 def self.included(base) # :nodoc: 166 base.extend(ClassMethods) 167 base.class_eval "class_inheritable_accessor :class_provided_formats" 168 base.class_provided_formats = [:html] 169 end 170 171 module ClassMethods 172 173 # Adds symbols representing formats to the controller's 174 # default list of provided_formats. These will apply to 175 # every action in the controller, unless modified in the action. 176 def provides(*formats) 177 formats.each {|fmt| 178 self.class_provided_formats << fmt unless class_provided_formats.include?(fmt) 179 } 180 end 181 182 # Overwrites the controller's list of provided_formats. These 183 # will apply to every action in the controller, unless modified 184 # in the action. 185 def only_provides(*formats) 186 self.class_provided_formats = formats 187 end 188 189 # Removes formats from the controller's 190 # default list of provided_formats. These will apply to 191 # every action in the controller, unless modified in the action. 192 def does_not_provide(*formats) 193 self.class_provided_formats -= formats 194 end 195 end 196 197 # Returns the current list of formats provided for this instance 198 # of the controller. It starts with what has been set in the controller 199 # (or :html by default) but can be modifed on a per-action basis. 200 def provided_formats 201 @_provided_formats ||= class_provided_formats 202 end 203 204 # Sets the provided formats for this action. Usually, you would 205 # use a combination of +provides+, +only_provides+ and +does_not_provide+ 206 # to manage this, but you can set it directly. 207 def provided_formats=(*formats) 208 raise "Cannot modify provided_formats because content_type has already been set" if content_type_set? 209 @_provided_formats = formats.flatten 210 end 211 212 # Adds formats to the list of provided formats for this particular 213 # request. Usually used to add formats to a single action. See also 214 # the controller-level provides that affects all actions in a controller. 215 def provides(*formats) 216 formats.each {|fmt| 217 self.provided_formats += [fmt] unless provided_formats.include?(fmt) 218 } 219 end 220 221 # Sets list of provided formats for this particular 222 # request. Usually used to limit formats to a single action. See also 223 # the controller-level provides that affects all actions in a controller. 224 def only_provides(*formats) 225 self.provided_formats = formats.flatten 226 end 227 228 # Removes formats from the list of provided formats for this particular 229 # request. Usually used to remove formats from a single action. See 230 # also the controller-level provides that affects all actions in a 231 # controller. 232 def does_not_provide(*formats) 233 self.provided_formats -= formats.flatten 234 end 235 236 # Do the content negotiation: 237 # 1. if params[:format] is there, and provided, use it 238 # 2. Parse the Accept header 239 # 3. If it's */*, use the first provided format 240 # 4. Look for one that is provided, in order of request 241 # 5. Raise 406 if none found 242 def perform_content_negotiation # :nodoc: 90 243 raise Merb::ControllerExceptions::NotAcceptable if provided_formats.empty? 91 244 if fmt = params[:format] … … 109 262 end 110 263 264 # Checks to see if content negotiation has already been performed. 265 # If it has, you can no longer modify the list of provided formats. 111 266 def content_type_set? 112 267 !@_content_type.nil? 113 268 end 114 269 270 # Returns the output format for this request, based on the 271 # provided formats, <tt>params[:format]</tt> and the client's HTTP 272 # Accept header. 273 # 274 # The first time this is called, it triggers content negotiation 275 # and caches the value. Once you call +content_type+ you can 276 # not set or change the list of provided formats. 277 # 278 # Called automatically by +render+, so you should only call it if 279 # you need the value, not to trigger content negotiation. 115 280 def content_type 116 281 unless content_type_set? … … 123 288 end 124 289 290 # Sets the output content_type for this request. Normally you 291 # should use +provides+, +does_not_provide+ and +only_provides+ 292 # and then let the content negotiation process determine the proper 293 # content_type. However, in some circumstances you may want to 294 # set it directly, or override what content negotiation picks. 125 295 def content_type=(new_type) 126 296 @_content_type = new_type trunk/spec/merb/controller_spec.rb
r775 r798 13 13 c = new_controller 14 14 c._layout.should == :application 15 end16 end17 18 class FormattedBasic < Merb::Controller ; end19 class WithXml < Merb::Controller ; provides :xml ; end20 class XmlOnly < Merb::Controller ; only_provides :xml ; end21 class ProvidesNothing < Merb::Controller ; does_not_provide :html ; end22 class HtmlAgain < Merb::Controller ; provides :html ; end23 24 class ManyProvides < Merb::Controller25 provides :html, :xml, :txt26 def noextra ; end27 def extra ; provides :json, :yaml ; end28 end29 30 describe "Merb::Controller", "provided formats (class)" do31 it "should have the default class_provided_formats of [:html]" do32 c = new_controller("index",FormattedBasic)33 c.class_provided_formats.should == [:html]34 end35 36 it "should add :xml to class_provided_formats when called with provides :xml" do37 c = new_controller("index",WithXml)38 c.class_provided_formats.should == [:html, :xml]39 end40 41 it "should only have :xml in class_provided_formats when called with only_provides :xml" do42 c = new_controller("index",XmlOnly)43 c.class_provided_formats.should == [:xml]44 end45 46 it "should have an empty class_provided_formats when called with does_not_provide :html" do47 c = new_controller("index",ProvidesNothing)48 c.class_provided_formats.should == []49 end50 51 it "should only have :html in class_provided_formats when called with provides :html (after other controllers have set it)" do52 c = new_controller("index",HtmlAgain)53 c.class_provided_formats.should == [:html]54 end55 56 it "should handle multiple provides added via class method" do57 c = new_controller("noextra",ManyProvides)58 c.provided_formats.should == [:html, :xml, :txt]59 end60 end61 62 class ActionProvides < Merb::Controller63 def basic ; end64 def with_xml ; provides :xml ; end65 def xml_only ; only_provides :xml ; end66 def nothing ; does_not_provide :html ; end67 end68 69 describe "Merb::Controller", "provided formats (action)" do70 it "should have the default provided_formats" do71 c = new_controller("basic",ActionProvides)72 c.basic73 c.provided_formats.should == [:html]74 end75 76 it "should add :xml to provided_formats when called with provides :xml" do77 c = new_controller("with_xml",ActionProvides)78 c.with_xml79 c.provided_formats.should == [:html, :xml]80 end81 82 it "should only have :xml in provided_formats when called with only_provides :xml" do83 c = new_controller("xml_only",ActionProvides)84 c.xml_only85 c.provided_formats.should == [:xml]86 end87 88 it "should have an empty provided_formats when called with does_not_provide :html" do89 c = new_controller("nothing",ActionProvides)90 c.nothing91 c.provided_formats.should be_empty92 15 end 93 16 end trunk/spec/merb/responder_spec.rb
r795 r798 479 479 end 480 480 end 481 482 class FormattedBasic < Merb::Controller ; end 483 class WithXml < Merb::Controller ; provides :xml ; end 484 class XmlOnly < Merb::Controller ; only_provides :xml ; end 485 class ProvidesNothing < Merb::Controller ; does_not_provide :html ; end 486 class HtmlAgain < Merb::Controller ; provides :html ; end 487 488 class ManyProvides < Merb::Controller 489 provides :html, :xml, :txt 490 def noextra ; end 491 def extra ; provides :json, :yaml ; end 492 end 493 494 describe "Responder", "managing provided formats (class)" do 495 it "should have the default class_provided_formats of [:html]" do 496 c = new_controller("index",FormattedBasic) 497 c.class_provided_formats.should == [:html] 498 end 499 500 it "should add :xml to class_provided_formats when called with provides :xml" do 501 c = new_controller("index",WithXml) 502 c.class_provided_formats.should == [:html, :xml] 503 end 504 505 it "should only have :xml in class_provided_formats when called with only_provides :xml" do 506 c = new_controller("index",XmlOnly) 507 c.class_provided_formats.should == [:xml] 508 end 509 510 it "should have an empty class_provided_formats when called with does_not_provide :html" do 511 c = new_controller("index",ProvidesNothing) 512 c.class_provided_formats.should == [] 513 end 514 515 it "should only have :html in class_provided_formats when called with provides :html (after other controllers have set it)" do 516 c = new_controller("index",HtmlAgain) 517 c.class_provided_formats.should == [:html] 518 end 519 520 it "should handle multiple provides added via class method" do 521 c = new_controller("noextra",ManyProvides) 522 c.provided_formats.should == [:html, :xml, :txt] 523 end 524 end 525 526 class ActionProvides < Merb::Controller 527 def basic ; end 528 def with_xml ; provides :xml ; end 529 def xml_only ; only_provides :xml ; end 530 def nothing ; does_not_provide :html ; end 531 end 532 533 describe "Responder", "managing provided formats (action)" do 534 it "should have the default provided_formats" do 535 c = new_controller("basic",ActionProvides) 536 c.basic 537 c.provided_formats.should == [:html] 538 end 539 540 it "should add :xml to provided_formats when called with provides :xml" do 541 c = new_controller("with_xml",ActionProvides) 542 c.with_xml 543 c.provided_formats.should == [:html, :xml] 544 end 545 546 it "should only have :xml in provided_formats when called with only_provides :xml" do 547 c = new_controller("xml_only",ActionProvides) 548 c.xml_only 549 c.provided_formats.should == [:xml] 550 end 551 552 it "should have an empty provided_formats when called with does_not_provide :html" do 553 c = new_controller("nothing",ActionProvides) 554 c.nothing 555 c.provided_formats.should be_empty 556 end 557 end 558 trunk/tools/allison/cache/BODY
r598 r798 1 <html xml :lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"/><title>%title%</title><link type="text/css" rel="stylesheet" media="screen" href="%style_url%"/><script type="text/javascript">1 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"/><title>%title%</title><link type="text/css" href="%style_url%" rel="stylesheet" media="screen"/><script type="text/javascript"> 2 2 // Javascript for Allison RDoc template 3 3 // Copyright 2006 Cloudburst LLC … … 402 402 </div> 403 403 ENDIF:methods 404 <div id="spacer"></div><div class="navigation dark index" id="class_wrapper"><div class="list_header"><h3>All classes</h3></div><div class="list_header_link"><a onclick="toggle('class'); toggleText('class_link'); return false;" href="#" id="class_link">Hide...</a></div><div class="clear"></div><div id="class"><form><label for="filter_class">Filter: </label><input type="text" onKeyUp="return filterList('class', this.value, event);" onKeyPress="return disableSubmit(event);" id="filter_class"></input></form></div></div><div class="navigation dark index" id="file_wrapper"><div class="list_header"><h3>All files</h3></div><div class="list_header_link"><a onclick="toggle('file'); toggleText('file_link'); return false;" href="#" id="file_link">Hide...</a></div><div class="clear"></div><div id="file"><form><label for="filter_file">Filter: </label><input type="text" onKeyUp="return filterList('file', this.value, event);" onKeyPress="return disableSubmit(event);" id="filter_file"></input></form></div></div><div class="navigation dark index" id="method_wrapper"><div class="list_header"><h3>All methods</h3></div><div class="list_header_link"><a onclick="toggle('method'); toggleText('method_link'); return false;" href="#" id="method_link">Show...</a></div><div class="clear"></div><div id="method"><form><label for="filter_method">Filter: </label><input type="text" onKeyUp="return filterList('method', this.value, event);" onKeyPress="return disableSubmit(event);" id="filter_method"></input></form></div></div><div class="curve" id="left_curve_0"></div><div class="curve" id="left_curve_1"></div><div class="curve" id="left_curve_2"></div><div class="curve" id="left_curve_3"></div><div class="curve" id="left_curve_4"></div><div class="curve" id="left_curve_5"></div><div class="curve" id="left_curve_6"></div><div class="curve" id="left_curve_7"></div><div class="curve" id="left_curve_8"></div><div class="curve" id="left_curve_9"></div></div><div id="content">404 <div id="spacer"></div><div class="navigation dark index" id="class_wrapper"><div class="list_header"><h3>All classes</h3></div><div class="list_header_link"><a href="#" onclick="toggle('class'); toggleText('class_link'); return false;" id="class_link">Hide...</a></div><div class="clear"></div><div id="class"><form><label for="filter_class">Filter: </label><input type="text" onKeyPress="return disableSubmit(event);" id="filter_class" onKeyUp="return filterList('class', this.value, event);"></input></form></div></div><div class="navigation dark index" id="file_wrapper"><div class="list_header"><h3>All files</h3></div><div class="list_header_link"><a href="#" onclick="toggle('file'); toggleText('file_link'); return false;" id="file_link">Hide...</a></div><div class="clear"></div><div id="file"><form><label for="filter_file">Filter: </label><input type="text" onKeyPress="return disableSubmit(event);" id="filter_file" onKeyUp="return filterList('file', this.value, event);"></input></form></div></div><div class="navigation dark index" id="method_wrapper"><div class="list_header"><h3>All methods</h3></div><div class="list_header_link"><a href="#" onclick="toggle('method'); toggleText('method_link'); return false;" id="method_link">Show...</a></div><div class="clear"></div><div id="method"><form><label for="filter_method">Filter: </label><input type="text" onKeyPress="return disableSubmit(event);" id="filter_method" onKeyUp="return filterList('method', this.value, event);"></input></form></div></div><div class="curve" id="left_curve_0"></div><div class="curve" id="left_curve_1"></div><div class="curve" id="left_curve_2"></div><div class="curve" id="left_curve_3"></div><div class="curve" id="left_curve_4"></div><div class="curve" id="left_curve_5"></div><div class="curve" id="left_curve_6"></div><div class="curve" id="left_curve_7"></div><div class="curve" id="left_curve_8"></div><div class="curve" id="left_curve_9"></div></div><div id="content"> 405 405 IF:title 406 406 <h1 id="item_name">%title%</h1> … … 572 572 573 573 IF:sourcecode 574 <p class="source_link" id="%aref%-show-link"><a onclick="toggle('%aref%-source'); toggleText('%aref%-link'); return false;" href="#" id="%aref%-link">Show source...</a></p><div class="source" id="%aref%-source"><pre>%sourcecode%</pre></div>574 <p class="source_link" id="%aref%-show-link"><a href="#" onclick="toggle('%aref%-source'); toggleText('%aref%-link'); return false;" id="%aref%-link">Show source...</a></p><div class="source" id="%aref%-source"><pre>%sourcecode%</pre></div> 575 575 ENDIF:sourcecode 576 576 </div></div> trunk/tools/allison/cache/INDEX
r598 r798 1 <html xml :lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"/><title>%title%</title><link type="text/css" rel="stylesheet" media="screen" href="rdoc-style.css"/><meta content="0;url=%initial_page%" http-equiv="refresh"/></head><body><div id="container"><div class="curve" id="preheader_curve_0"></div><div class="curve" id="preheader_curve_1"></div><div class="curve" id="preheader_curve_2"></div><div class="curve" id="preheader_curve_3"></div><div class="curve" id="preheader_curve_4"></div><div class="curve" id="preheader_curve_5"></div><div class="curve" id="preheader_curve_6"></div><div class="curve" id="preheader_curve_7"></div><div class="curve" id="preheader_curve_8"></div><div class="curve" id="preheader_curve_9"></div><div id="header"><span id="title"><p> </p><h1>Ruby Documentation</h1></span></div><div class="clear"></div><div id="redirect"><a href="%initial_page%"><h1>Redirect</h1></a></div></div></body></html>1 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"/><title>%title%</title><link type="text/css" href="rdoc-style.css" rel="stylesheet" media="screen"/><meta content="0;url=%initial_page%" http-equiv="refresh"/></head><body><div id="container"><div class="curve" id="preheader_curve_0"></div><div class="curve" id="preheader_curve_1"></div><div class="curve" id="preheader_curve_2"></div><div class="curve" id="preheader_curve_3"></div><div class="curve" id="preheader_curve_4"></div><div class="curve" id="preheader_curve_5"></div><div class="curve" id="preheader_curve_6"></div><div class="curve" id="preheader_curve_7"></div><div class="curve" id="preheader_curve_8"></div><div class="curve" id="preheader_curve_9"></div><div id="header"><span id="title"><p> </p><h1>Ruby Documentation</h1></span></div><div class="clear"></div><div id="redirect"><a href="%initial_page%"><h1>Redirect</h1></a></div></div></body></html>
