merb 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +138 -56
- data/Rakefile +23 -8
- data/app_generators/merb/templates/Rakefile +13 -0
- data/app_generators/merb/templates/app/helpers/global_helper.rb +1 -1
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +12 -3
- data/app_generators/merb/templates/config/merb.yml +14 -1
- data/app_generators/merb/templates/spec/spec_helper.rb +6 -0
- data/app_generators/merb/templates/test/test_helper.rb +1 -0
- data/lib/merb.rb +27 -7
- data/lib/merb/abstract_controller.rb +76 -36
- data/lib/merb/caching/store/memcache.rb +20 -0
- data/lib/merb/constants.rb +2 -4
- data/lib/merb/controller.rb +44 -2
- data/lib/merb/core_ext/get_args.rb +23 -4
- data/lib/merb/core_ext/hash.rb +16 -11
- data/lib/merb/core_ext/inflections.rb +1 -1
- data/lib/merb/core_ext/kernel.rb +106 -26
- data/lib/merb/core_ext/numeric.rb +1 -1
- data/lib/merb/core_ext/string.rb +10 -13
- data/lib/merb/dispatcher.rb +2 -2
- data/lib/merb/exceptions.rb +3 -1
- data/lib/merb/logger.rb +15 -6
- data/lib/merb/mail_controller.rb +18 -2
- data/lib/merb/mailer.rb +1 -1
- data/lib/merb/mixins/controller.rb +64 -228
- data/lib/merb/mixins/erubis_capture.rb +1 -1
- data/lib/merb/mixins/general_controller.rb +258 -0
- data/lib/merb/mixins/render.rb +45 -24
- data/lib/merb/mixins/responder.rb +89 -18
- data/lib/merb/mixins/view_context.rb +32 -5
- data/lib/merb/mixins/web_controller.rb +8 -1
- data/lib/merb/mongrel_handler.rb +27 -17
- data/lib/merb/part_controller.rb +10 -0
- data/lib/merb/request.rb +34 -14
- data/lib/merb/router.rb +77 -45
- data/lib/merb/server.rb +116 -72
- data/lib/merb/session/cookie_store.rb +14 -22
- data/lib/merb/session/mem_cache_session.rb +2 -2
- data/lib/merb/session/memory_session.rb +12 -1
- data/lib/merb/template/erubis.rb +31 -0
- data/lib/merb/template/haml.rb +4 -14
- data/lib/merb/template/xml_builder.rb +1 -1
- data/lib/merb/test/helper.rb +90 -18
- data/lib/merb/test/rspec.rb +145 -74
- data/lib/merb/version.rb +11 -0
- data/lib/merb/view_context.rb +3 -6
- data/lib/patch +69 -0
- data/lib/tasks/merb.rake +1 -1
- data/spec/fixtures/config/environments/environment_config_test.yml +1 -0
- data/spec/fixtures/controllers/render_spec_controllers.rb +63 -4
- data/spec/fixtures/views/examples/template_throw_content_without_block.html.erb +3 -0
- data/spec/fixtures/views/partials/_erubis.html.erb +1 -1
- data/spec/merb/abstract_controller_spec.rb +1 -0
- data/spec/merb/controller_filters_spec.rb +68 -3
- data/spec/merb/controller_spec.rb +35 -68
- data/spec/merb/cookie_store_spec.rb +7 -20
- data/spec/merb/core_ext_spec.rb +35 -1
- data/spec/merb/dispatch_spec.rb +8 -2
- data/spec/merb/generator_spec.rb +12 -4
- data/spec/merb/mail_controller_spec.rb +33 -0
- data/spec/merb/part_controller_spec.rb +33 -1
- data/spec/merb/render_spec.rb +74 -0
- data/spec/merb/request_spec.rb +43 -0
- data/spec/merb/responder_spec.rb +1 -0
- data/spec/merb/router_spec.rb +118 -13
- data/spec/merb/server_spec.rb +19 -0
- data/spec/merb/view_context_spec.rb +31 -3
- data/spec/spec_helper.rb +8 -0
- data/spec/spec_helpers/url_shared_behaviour.rb +112 -0
- 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
|
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
|
data/lib/merb/mixins/render.rb
CHANGED
@@ -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
|
-
|
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::
|
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
|
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
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
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
|
-
|
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
|
169
|
-
|
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
|
-
|
178
|
-
|
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
|
-
|
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.
|
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
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
218
|
-
|
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.
|
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
|
-
|
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
|
|