merb 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/README +138 -56
  2. data/Rakefile +23 -8
  3. data/app_generators/merb/templates/Rakefile +13 -0
  4. data/app_generators/merb/templates/app/helpers/global_helper.rb +1 -1
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +12 -3
  6. data/app_generators/merb/templates/config/merb.yml +14 -1
  7. data/app_generators/merb/templates/spec/spec_helper.rb +6 -0
  8. data/app_generators/merb/templates/test/test_helper.rb +1 -0
  9. data/lib/merb.rb +27 -7
  10. data/lib/merb/abstract_controller.rb +76 -36
  11. data/lib/merb/caching/store/memcache.rb +20 -0
  12. data/lib/merb/constants.rb +2 -4
  13. data/lib/merb/controller.rb +44 -2
  14. data/lib/merb/core_ext/get_args.rb +23 -4
  15. data/lib/merb/core_ext/hash.rb +16 -11
  16. data/lib/merb/core_ext/inflections.rb +1 -1
  17. data/lib/merb/core_ext/kernel.rb +106 -26
  18. data/lib/merb/core_ext/numeric.rb +1 -1
  19. data/lib/merb/core_ext/string.rb +10 -13
  20. data/lib/merb/dispatcher.rb +2 -2
  21. data/lib/merb/exceptions.rb +3 -1
  22. data/lib/merb/logger.rb +15 -6
  23. data/lib/merb/mail_controller.rb +18 -2
  24. data/lib/merb/mailer.rb +1 -1
  25. data/lib/merb/mixins/controller.rb +64 -228
  26. data/lib/merb/mixins/erubis_capture.rb +1 -1
  27. data/lib/merb/mixins/general_controller.rb +258 -0
  28. data/lib/merb/mixins/render.rb +45 -24
  29. data/lib/merb/mixins/responder.rb +89 -18
  30. data/lib/merb/mixins/view_context.rb +32 -5
  31. data/lib/merb/mixins/web_controller.rb +8 -1
  32. data/lib/merb/mongrel_handler.rb +27 -17
  33. data/lib/merb/part_controller.rb +10 -0
  34. data/lib/merb/request.rb +34 -14
  35. data/lib/merb/router.rb +77 -45
  36. data/lib/merb/server.rb +116 -72
  37. data/lib/merb/session/cookie_store.rb +14 -22
  38. data/lib/merb/session/mem_cache_session.rb +2 -2
  39. data/lib/merb/session/memory_session.rb +12 -1
  40. data/lib/merb/template/erubis.rb +31 -0
  41. data/lib/merb/template/haml.rb +4 -14
  42. data/lib/merb/template/xml_builder.rb +1 -1
  43. data/lib/merb/test/helper.rb +90 -18
  44. data/lib/merb/test/rspec.rb +145 -74
  45. data/lib/merb/version.rb +11 -0
  46. data/lib/merb/view_context.rb +3 -6
  47. data/lib/patch +69 -0
  48. data/lib/tasks/merb.rake +1 -1
  49. data/spec/fixtures/config/environments/environment_config_test.yml +1 -0
  50. data/spec/fixtures/controllers/render_spec_controllers.rb +63 -4
  51. data/spec/fixtures/views/examples/template_throw_content_without_block.html.erb +3 -0
  52. data/spec/fixtures/views/partials/_erubis.html.erb +1 -1
  53. data/spec/merb/abstract_controller_spec.rb +1 -0
  54. data/spec/merb/controller_filters_spec.rb +68 -3
  55. data/spec/merb/controller_spec.rb +35 -68
  56. data/spec/merb/cookie_store_spec.rb +7 -20
  57. data/spec/merb/core_ext_spec.rb +35 -1
  58. data/spec/merb/dispatch_spec.rb +8 -2
  59. data/spec/merb/generator_spec.rb +12 -4
  60. data/spec/merb/mail_controller_spec.rb +33 -0
  61. data/spec/merb/part_controller_spec.rb +33 -1
  62. data/spec/merb/render_spec.rb +74 -0
  63. data/spec/merb/request_spec.rb +43 -0
  64. data/spec/merb/responder_spec.rb +1 -0
  65. data/spec/merb/router_spec.rb +118 -13
  66. data/spec/merb/server_spec.rb +19 -0
  67. data/spec/merb/view_context_spec.rb +31 -3
  68. data/spec/spec_helper.rb +8 -0
  69. data/spec/spec_helpers/url_shared_behaviour.rb +112 -0
  70. metadata +124 -87
@@ -6,7 +6,7 @@ module Merb
6
6
 
7
7
  # Provides direct acccess to the buffer for this view context
8
8
  def _buffer( the_binding )
9
- @_buffer ||= eval( "_buf", the_binding )
9
+ @_buffer = eval( "_buf", the_binding )
10
10
  end
11
11
 
12
12
  # Capture allows you to extract a part of the template into an
@@ -0,0 +1,258 @@
1
+ # This module provides helper style functionality to all controllers.
2
+ module Merb
3
+ module GeneralControllerMixin
4
+ include Merb::ControllerExceptions
5
+
6
+ # Returns a URL according to the defined route. Accepts the path and
7
+ # an options hash. The path specifies the route requested. The options
8
+ # hash fills in the dynamic parts of the route.
9
+ #
10
+ # Merb routes can often be one-way; if they use a regex to define
11
+ # the route, then knowing the controller & action won't be enough
12
+ # to reverse-generate the route. However, if you use the default
13
+ # /controller/action/id?query route, +default_route+ can generate
14
+ # it for you.
15
+ #
16
+ # For easy reverse-routes that use a Regex, be sure to also add
17
+ # a name to the route, so +url+ can find it.
18
+ #
19
+ # Nested resources such as:
20
+ #
21
+ # r.resources :blogposts do |post|
22
+ # post.resources :comments
23
+ # end
24
+ #
25
+ # Provide the following routes:
26
+ #
27
+ # [:blogposts, "/blogposts"]
28
+ # [:blogpost, "/blogposts/:id"]
29
+ # [:edit_blogpost, "/blogposts/:id/edit"]
30
+ # [:new_blogpost, "/blogposts/new"]
31
+ # [:custom_new_blogpost, "/blogposts/new/:action"]
32
+ # [:comments, "/blogposts/:blogpost_id/comments"]
33
+ # [:comment, "/blogposts/:blogpost_id/comments/:id"]
34
+ # [:edit_comment, "/blogposts/:blogpost_id/comments/:id/edit"]
35
+ # [:new_comment, "/blogposts/:blogpost_id/comments/new"]
36
+ # [:custom_new_comment, "/blogposts/:blogpost_id/comments/new/:action"]
37
+ #
38
+ #
39
+ # ==== Parameters
40
+ #
41
+ # :route_name: - Symbol that represents a named route that you want to use, such as +:edit_post+.
42
+ # :new_params: - Parameters to be passed to the generated URL, such as the +id+ for a record to edit.
43
+ #
44
+ # ==== Examples
45
+ #
46
+ # @post = Post.find(1)
47
+ # @comment = @post.comments.find(1)
48
+ #
49
+ # url(:blogposts) # => /blogposts
50
+ # url(:new_post) # => /blogposts/new
51
+ # url(:blogpost, @post) # => /blogposts/1
52
+ # url(:edit_blogpost, @post) # => /blogposts/1/edit
53
+ # url(:custom_new_blogpost, :action => 'alternate') # => /blogposts/new/alternate
54
+ #
55
+ # url(:comments, :blogpost => @post) # => /blogposts/1/comments
56
+ # url(:new_comment, :blogpost => @post) # => /blogposts/1/comments/new
57
+ # url(:comment, @comment) # => /blogposts/1/comments/1
58
+ # url(:edit_comment, @comment) # => /blogposts/1/comments/1/edit
59
+ # url(:custom_new_comment, :blogpost => @post)
60
+ #
61
+ # url(:page => 2) # => /posts/show/1?page=2
62
+ # url(:new_post, :page => 3) # => /posts/new?page=3
63
+ # url('/go/here', :page => 3) # => /go/here?page=3
64
+ #
65
+ # url(:controller => "welcome") # => /welcome
66
+ # url(:controller => "welcome", :action => "greet")
67
+ # # => /welcome/greet
68
+ #
69
+ def url(route_name = nil, new_params = {})
70
+ if route_name.is_a?(Hash)
71
+ new_params = route_name
72
+ route_name = nil
73
+ end
74
+
75
+ url = if new_params.respond_to?(:keys) && route_name.nil? &&
76
+ !(new_params.keys & [:controller, :action, :id]).empty?
77
+ url_from_default_route(new_params)
78
+ elsif route_name.nil? && !route.regexp?
79
+ url_from_route(route, new_params)
80
+ elsif route_name.nil?
81
+ request.path + (new_params.empty? ? "" : "?" + params_to_query_string(new_params))
82
+ elsif route_name.is_a?(Symbol)
83
+ url_from_route(route_name, new_params)
84
+ elsif route_name.is_a?(String)
85
+ route_name + (new_params.empty? ? "" : "?" + params_to_query_string(new_params))
86
+ else
87
+ raise "URL not generated: #{route_name.inspect}, #{new_params.inspect}"
88
+ end
89
+ url = Merb::Server.config[:path_prefix] + url if Merb::Server.config[:path_prefix]
90
+ url
91
+ end
92
+
93
+ def url_from_route(symbol, new_params = {})
94
+ if new_params.respond_to?(:new_record?) && new_params.new_record?
95
+ symbol = "#{symbol}".singularize.to_sym
96
+ new_params = {}
97
+ end
98
+ route = symbol.is_a?(Symbol) ? Merb::Router.named_routes[symbol] : symbol
99
+ unless route
100
+ raise InternalServerError, "URL could not be constructed. Route symbol not found: #{symbol.inspect}"
101
+ end
102
+ path = route.generate(new_params, params)
103
+ keys = route.symbol_segments
104
+ if new_params.is_a? Hash
105
+ if ext = format_extension(new_params)
106
+ new_params.delete(:format)
107
+ path += "." + ext
108
+ end
109
+ extras = new_params.reject{ |k, v| keys.include?(k) }
110
+ path += "?" + params_to_query_string(extras) unless extras.empty?
111
+ end
112
+ path
113
+ end
114
+
115
+ # this is pretty ugly, but it works. TODO: make this cleaner
116
+ def url_from_default_route(new_params)
117
+ query_params = new_params.reject do |k,v|
118
+ [:controller, :action, :id, :format].include?(k)
119
+ end
120
+ controller = get_controller_for_url_generation(new_params)
121
+ url = "/#{controller}"
122
+ if new_params[:action] || new_params[:id] ||
123
+ new_params[:format] || !query_params.empty?
124
+ action = new_params[:action] || params[:action]
125
+ url += "/#{action}"
126
+ end
127
+ if new_params[:id]
128
+ url += "/#{new_params[:id]}"
129
+ end
130
+ if format = new_params[:format]
131
+ format = params[:format] if format == :current
132
+ url += ".#{format}"
133
+ end
134
+ unless query_params.empty?
135
+ url += "?" + params_to_query_string(query_params)
136
+ end
137
+ url
138
+ end
139
+
140
+ protected
141
+
142
+ # Creates query string from params, supporting nested arrays and hashes.
143
+ # ==== Example
144
+ # params_to_query_string(:user => {:filter => {:name => "quux*"}, :order => ["name"]})
145
+ # # => user[filter][name]=quux%2A&user[order][]=name
146
+ def params_to_query_string(value, prefix = nil)
147
+ case value
148
+ when Array
149
+ value.map { |v|
150
+ params_to_query_string(v, "#{prefix}[]")
151
+ } * "&"
152
+ when Hash
153
+ value.map { |k, v|
154
+ params_to_query_string(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
155
+ } * "&"
156
+ else
157
+ "#{prefix}=#{escape(value)}"
158
+ end
159
+ end
160
+
161
+ # +format_extension+ dictates when named route URLs generated by the url
162
+ # method will have a file extension. It will return either false or the format
163
+ # extension to append.
164
+ #
165
+ # ==== Configuration Options
166
+ #
167
+ # By default, non-HTML URLs will be given an extension. It is posible
168
+ # to override this behaviour by setting +:use_format_in_urls+ in your
169
+ # Merb config (merb.yml) to either true/false.
170
+ #
171
+ # +true+ Results in all URLs (even HTML) being given extensions.
172
+ # This effect is often desirable when you have many formats and dont
173
+ # wish to treat .html any differently than any other format.
174
+ # +false+ Results in no URLs being given extensions and +format+
175
+ # gets treated just like any other param (default).
176
+ #
177
+ # ==== Method parameters
178
+ #
179
+ # +new_params+ - New parameters to be appended to the URL
180
+ #
181
+ # ==== Examples
182
+ #
183
+ # url(:post, :id => post, :format => 'xml')
184
+ # # => /posts/34.xml
185
+ #
186
+ # url(:accounts, :format => 'yml')
187
+ # # => /accounts.yml
188
+ #
189
+ # url(:edit_product, :id => 3, :format => 'html')
190
+ # # => /products/3
191
+ #
192
+ def format_extension(new_params={})
193
+ use_format = Merb::Server.config[:use_format_in_urls]
194
+ if use_format.nil?
195
+ prms = params.merge(new_params)
196
+ use_format = prms[:format] != 'html' && prms[:format]
197
+ end
198
+ use_format
199
+ end
200
+
201
+
202
+ # Creates an MD5 hashed token based on the current time.
203
+ #
204
+ # ==== Example
205
+ # make_token
206
+ # # => "b9a82e011694cc13a4249731b9e83cea"
207
+ #
208
+ def make_token
209
+ require 'digest/md5'
210
+ Digest::MD5.hexdigest("#{inspect}#{Time.now}#{rand}")
211
+ end
212
+
213
+ # Escapes the string representation of +obj+ and escapes
214
+ # it for use in XML.
215
+ #
216
+ # ==== Parameter
217
+ #
218
+ # +obj+ - The object to escape for use in XML.
219
+ #
220
+ def escape_xml(obj)
221
+ obj.to_s.gsub(/[&<>"']/) { |s| Merb::Const::ESCAPE_TABLE[s] }
222
+ end
223
+ alias h escape_xml
224
+ alias html_escape escape_xml
225
+
226
+ # Escapes +s+ for use in a URL.
227
+ #
228
+ # ==== Parameter
229
+ #
230
+ # +s+ - String to URL escape.
231
+ #
232
+ def escape(s)
233
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
234
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
235
+ }.tr(' ', '+')
236
+ end
237
+
238
+ # Unescapes a string (i.e., reverse URL escaping).
239
+ #
240
+ # ==== Parameter
241
+ #
242
+ # +s+ - String to unescape.
243
+ #
244
+ def unescape(s)
245
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
246
+ [$1.delete('%')].pack('H*')
247
+ }
248
+ end
249
+
250
+ private
251
+ # Used for sepccing
252
+ def get_controller_for_url_generation(options)
253
+ raise "Controller Not Specified" unless options[:controller]
254
+ options[:controller]
255
+ end
256
+
257
+ end
258
+ end
@@ -5,7 +5,7 @@ module Merb
5
5
  include Merb::ControllerExceptions
6
6
 
7
7
  def self.included(base)
8
- base.class_eval {
8
+ base.class_eval {
9
9
  class_inheritable_accessor :_template_root,
10
10
  :_layout,
11
11
  :_templates,
@@ -16,7 +16,7 @@ module Merb
16
16
  self._templates = {}
17
17
  self._cached_partials = {}
18
18
 
19
- attr_accessor :template
19
+ attr_accessor :template
20
20
  }
21
21
  end
22
22
 
@@ -131,7 +131,7 @@ module Merb
131
131
  # This will ensure that all instance variable are up to date in your views.
132
132
  #
133
133
  def render(*args,&blk)
134
- opts = Hash === args.last ? args.pop : {}
134
+ opts = (Hash === args.last) ? args.pop : {}
135
135
 
136
136
  action = opts[:action] || params[:action]
137
137
  opts[:layout] ||= _layout
@@ -145,7 +145,19 @@ module Merb
145
145
  fmt = content_type
146
146
  if transform_method = Merb.mime_transform_method(fmt)
147
147
  set_response_headers fmt
148
- return obj.send(transform_method)
148
+ transform_args = provided_format_arguments_for(fmt)
149
+ return case transform_args
150
+ when Hash then obj.send(transform_method, transform_args)
151
+ when Array then obj.send(transform_method, *transform_args)
152
+ when Proc then
153
+ case transform_args.arity
154
+ when 3 then transform_args.call(obj, self, transform_method)
155
+ when 2 then transform_args.call(obj, self)
156
+ when 1 then transform_args.call(obj)
157
+ else transform_args.call
158
+ end
159
+ else obj.send(transform_method)
160
+ end
149
161
  end
150
162
  end
151
163
  end
@@ -255,9 +267,13 @@ module Merb
255
267
  return " "
256
268
  end
257
269
 
270
+ # Sets the response's status to the specified value. Use either an
271
+ # integer (200, 201, 302, etc.), or a Symbol as defined in
272
+ # Merb::ControllerExceptions::RESPONSE_CODES, such as :not_found,
273
+ # :created or :see_other.
258
274
  def set_status(status)
259
275
  if status.kind_of?(Symbol)
260
- status = Merb::ControllerExceptions::RESPONSE_CODES(status)
276
+ status = Merb::ControllerExceptions::STATUS_CODES[status]
261
277
  status || raise("Can't find a response code with that name")
262
278
  end
263
279
  @_status = status
@@ -286,7 +302,7 @@ module Merb
286
302
  opts[:as] ||= template[(template.rindex('/_') + 2)..-1].split('.').first
287
303
 
288
304
  if opts[:with] # Render a collection or an object
289
- partial_for_collection(template, opts[:with], opts)
305
+ partial_for_collection(template, opts.delete(:with), opts)
290
306
  else # Just render a partial
291
307
  engine = Template.engine_for(template)
292
308
  render_partial(template, engine, opts || {})
@@ -303,29 +319,34 @@ module Merb
303
319
  private
304
320
 
305
321
  def render_partial(template, engine, locals={})
306
- @_merb_partial_locals = locals
307
- options = {
308
- :file => template,
309
- :view_context => clean_view_context(engine),
310
- :opts => { :locals => locals }
311
- }
312
- engine.transform(options)
322
+ @_merb_partial_locals = locals
323
+ options = {
324
+ :file => template,
325
+ :view_context => clean_view_context(engine),
326
+ :opts => { :locals => locals }
327
+ }
328
+ engine.transform(options)
313
329
  end
314
330
 
315
331
  def partial_for_collection(template, collection, opts={})
316
- # Delete the internal keys, so that everything else is considered
317
- # a local declaration in the partial
318
- local_name = opts.delete(:as)
319
- opts.delete(:with)
332
+ # Delete the internal keys, so that everything else is considered
333
+ # a local declaration in the partial
334
+ local_name = opts.delete(:as)
320
335
 
321
- engine = Template.engine_for(template)
336
+ engine = Template.engine_for(template)
337
+
338
+ buffer = []
339
+
340
+ collection = [collection].flatten
341
+ collection.each_with_index do |object, count|
342
+ opts.merge!({
343
+ local_name.to_sym => object,
344
+ :count => count
345
+ })
346
+ buffer << render_partial(template, engine, opts)
347
+ end
322
348
 
323
- ret = ''
324
- [ collection ].flatten.each do |object|
325
- opts.merge!({local_name.to_sym => object})
326
- ret << render_partial(template, engine, opts)
327
- end
328
- ret
349
+ buffer.join
329
350
  end
330
351
 
331
352
  # this returns a ViewContext object populated with all
@@ -18,17 +18,29 @@ module Merb
18
18
  raise ArgumentError unless key.is_a?(Symbol) && values.is_a?(Array)
19
19
  ResponderMixin::Rest::TYPES.update(key => values)
20
20
  add_response_headers!(key, new_response_headers)
21
- ResponderMixin::Rest::TRANSFORM_METHODS.merge!(key => transform_method)
21
+ ResponderMixin::Rest::TRANSFORM_METHODS.merge!(key => transform_method)
22
22
  end
23
23
 
24
24
  def remove_mime_type(key)
25
25
  key == :all ? false : ResponderMixin::Rest::TYPES.delete(key)
26
26
  end
27
27
 
28
+ # Return the method name (if any) for the mimetype
28
29
  def mime_transform_method(key)
29
30
  ResponderMixin::Rest::TRANSFORM_METHODS[key]
30
31
  end
31
32
 
33
+ # Return default arguments for transform method (if any)
34
+ def mime_transform_method_defaults(key)
35
+ ResponderMixin::Rest::TRANSFORM_METHOD_DEFAULTS[key]
36
+ end
37
+
38
+ # Set default arguments/proc for a format transform method
39
+ def set_mime_transform_method_defaults(key, *args, &block)
40
+ raise "Unknown mimetype #{key}" unless ResponderMixin::Rest::TRANSFORM_METHODS[key]
41
+ args = block if block_given?
42
+ ResponderMixin::Rest::TRANSFORM_METHOD_DEFAULTS[key] = args unless args.empty?
43
+ end
32
44
 
33
45
  # Adds outgoing headers to a mime type. This can be done with the Merb.add_mime_type method
34
46
  # or directly here.
@@ -165,8 +177,11 @@ module Merb
165
177
 
166
178
  def self.included(base) # :nodoc:
167
179
  base.extend(ClassMethods)
168
- base.class_eval { class_inheritable_accessor :class_provided_formats }
169
- base.class_provided_formats = [:html]
180
+ base.class_eval do
181
+ class_inheritable_accessor :class_provided_formats
182
+ class_inheritable_accessor :class_provided_format_arguments
183
+ end
184
+ base.reset_provides
170
185
  end
171
186
 
172
187
  module ClassMethods
@@ -174,56 +189,103 @@ module Merb
174
189
  # Adds symbols representing formats to the controller's
175
190
  # default list of provided_formats. These will apply to
176
191
  # every action in the controller, unless modified in the action.
177
- def provides(*formats)
178
- formats.each {|fmt|
192
+ # If the last argument is a Hash or an Array, these are regarded
193
+ # as arguments to pass to the to_<mime_type> method as needed.
194
+ def provides(*formats, &block)
195
+ options = extract_provides_options(formats, &block)
196
+ formats.each do |fmt|
179
197
  self.class_provided_formats << fmt unless class_provided_formats.include?(fmt)
180
- }
198
+ self.class_provided_format_arguments[fmt] = options unless options.nil?
199
+ end
181
200
  end
182
201
 
183
202
  # Overwrites the controller's list of provided_formats. These
184
203
  # will apply to every action in the controller, unless modified
185
204
  # in the action.
186
205
  def only_provides(*formats)
187
- self.class_provided_formats = formats
206
+ clear_provides
207
+ provides(*formats)
188
208
  end
189
209
 
190
210
  # Removes formats from the controller's
191
- # default list of provided_formats. These will apply to
211
+ # default list of provided_formats. These will apply to
192
212
  # every action in the controller, unless modified in the action.
193
213
  def does_not_provide(*formats)
194
214
  self.class_provided_formats -= formats
215
+ formats.each { |fmt| self.class_provided_format_arguments.delete(fmt) }
195
216
  end
217
+
218
+ # Clear any formats and their options
219
+ def clear_provides
220
+ self.class_provided_formats = []
221
+ self.class_provided_format_arguments = {}
222
+ end
223
+
224
+ # Reset to the default list of formats
225
+ def reset_provides
226
+ only_provides(:html)
227
+ end
228
+
229
+ # Extract arguments for provided format methods
230
+ def extract_provides_options(args, &block)
231
+ return block if block_given?
232
+ case args.last
233
+ when Hash then [args.pop]
234
+ when Array then args.pop
235
+ when Proc then args.pop
236
+ else nil
237
+ end
238
+ end
239
+
196
240
  end
197
241
 
198
242
  # Returns the current list of formats provided for this instance
199
243
  # of the controller. It starts with what has been set in the controller
200
244
  # (or :html by default) but can be modifed on a per-action basis.
201
245
  def provided_formats
202
- @_provided_formats ||= class_provided_formats
246
+ @_provided_formats ||= class_provided_formats.dup
203
247
  end
204
248
 
205
249
  # Sets the provided formats for this action. Usually, you would
206
250
  # use a combination of +provides+, +only_provides+ and +does_not_provide+
207
251
  # to manage this, but you can set it directly.
208
- def provided_formats=(*formats)
209
- raise "Cannot modify provided_formats because content_type has already been set" if content_type_set?
210
- @_provided_formats = formats.flatten
252
+ # If the last argument is a Hash or an Array, these are regarded
253
+ # as arguments to pass to the to_<mime_type> method as needed.
254
+ def set_provided_formats(*formats, &block)
255
+ raise_if_content_type_already_set!
256
+ @_provided_formats = []
257
+ @_provided_format_arguments = {}
258
+ provides(*formats.flatten, &block)
259
+ end
260
+ alias :provided_formats= :set_provided_formats
261
+
262
+ # Returns a Hash of arguments for format methods
263
+ def provided_format_arguments
264
+ @_provided_format_arguments ||= Hash.new.replace(class_provided_format_arguments)
265
+ end
266
+
267
+ # Returns the arguments (if any) for the mime_transform_method call
268
+ def provided_format_arguments_for(fmt)
269
+ self.provided_format_arguments[fmt] || Merb.mime_transform_method_defaults(fmt)
211
270
  end
212
271
 
213
272
  # Adds formats to the list of provided formats for this particular
214
273
  # request. Usually used to add formats to a single action. See also
215
274
  # the controller-level provides that affects all actions in a controller.
216
- def provides(*formats)
217
- formats.each {|fmt|
218
- self.provided_formats += [fmt] unless provided_formats.include?(fmt)
219
- }
275
+ def provides(*formats, &block)
276
+ raise_if_content_type_already_set!
277
+ options = self.class.extract_provides_options(formats, &block)
278
+ formats.each do |fmt|
279
+ self.provided_formats << fmt unless provided_formats.include?(fmt)
280
+ self.provided_format_arguments[fmt] = options unless options.nil?
281
+ end
220
282
  end
221
283
 
222
284
  # Sets list of provided formats for this particular
223
285
  # request. Usually used to limit formats to a single action. See also
224
286
  # the controller-level provides that affects all actions in a controller.
225
287
  def only_provides(*formats)
226
- self.provided_formats = formats.flatten
288
+ self.set_provided_formats(*formats)
227
289
  end
228
290
 
229
291
  # Removes formats from the list of provided formats for this particular
@@ -231,7 +293,9 @@ module Merb
231
293
  # also the controller-level provides that affects all actions in a
232
294
  # controller.
233
295
  def does_not_provide(*formats)
234
- self.provided_formats -= formats.flatten
296
+ formats.flatten!
297
+ self.provided_formats -= formats
298
+ formats.each { |fmt| self.provided_format_arguments.delete(fmt) }
235
299
  end
236
300
 
237
301
  # Do the content negotiation:
@@ -297,11 +361,18 @@ module Merb
297
361
  @_content_type = new_type
298
362
  end
299
363
 
364
+ private
365
+
366
+ def raise_if_content_type_already_set!
367
+ raise "Cannot modify provided_formats because content_type has already been set" if content_type_set?
368
+ end
369
+
300
370
  module Rest
301
371
 
302
372
  TYPES = {}
303
373
  RESPONSE_HEADERS = {}
304
374
  TRANSFORM_METHODS = {}
375
+ TRANSFORM_METHOD_DEFAULTS = {}
305
376
 
306
377
  class Responder
307
378