merb 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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