actionpack 1.11.2 → 1.12.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +392 -5
- data/lib/action_controller.rb +8 -4
- data/lib/action_controller/assertions.rb +9 -10
- data/lib/action_controller/base.rb +177 -88
- data/lib/action_controller/benchmarking.rb +5 -5
- data/lib/action_controller/caching.rb +44 -36
- data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
- data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
- data/lib/action_controller/cgi_process.rb +36 -24
- data/lib/action_controller/components.rb +152 -52
- data/lib/action_controller/dependencies.rb +1 -1
- data/lib/action_controller/deprecated_redirects.rb +2 -2
- data/lib/action_controller/deprecated_request_methods.rb +34 -0
- data/lib/action_controller/filters.rb +59 -19
- data/lib/action_controller/flash.rb +53 -47
- data/lib/action_controller/helpers.rb +2 -2
- data/lib/action_controller/integration.rb +524 -0
- data/lib/action_controller/layout.rb +58 -23
- data/lib/action_controller/mime_responds.rb +163 -0
- data/lib/action_controller/mime_type.rb +142 -0
- data/lib/action_controller/pagination.rb +13 -7
- data/lib/action_controller/request.rb +59 -56
- data/lib/action_controller/rescue.rb +1 -1
- data/lib/action_controller/routing.rb +29 -10
- data/lib/action_controller/scaffolding.rb +8 -0
- data/lib/action_controller/session/active_record_store.rb +21 -10
- data/lib/action_controller/session/mem_cache_store.rb +18 -12
- data/lib/action_controller/session_management.rb +30 -11
- data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
- data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +189 -118
- data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
- data/lib/action_controller/vendor/xml_node.rb +97 -0
- data/lib/action_controller/verification.rb +2 -0
- data/lib/action_pack/version.rb +3 -3
- data/lib/action_view.rb +0 -2
- data/lib/action_view/base.rb +109 -36
- data/lib/action_view/compiled_templates.rb +1 -1
- data/lib/action_view/helpers/active_record_helper.rb +4 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
- data/lib/action_view/helpers/capture_helper.rb +49 -12
- data/lib/action_view/helpers/date_helper.rb +14 -4
- data/lib/action_view/helpers/form_helper.rb +136 -20
- data/lib/action_view/helpers/form_options_helper.rb +29 -7
- data/lib/action_view/helpers/form_tag_helper.rb +22 -20
- data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
- data/lib/action_view/helpers/javascript_helper.rb +50 -446
- data/lib/action_view/helpers/javascripts/controls.js +95 -30
- data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
- data/lib/action_view/helpers/javascripts/effects.js +310 -211
- data/lib/action_view/helpers/javascripts/prototype.js +228 -28
- data/lib/action_view/helpers/number_helper.rb +9 -9
- data/lib/action_view/helpers/pagination_helper.rb +1 -1
- data/lib/action_view/helpers/prototype_helper.rb +900 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
- data/lib/action_view/helpers/text_helper.rb +7 -6
- data/lib/action_view/helpers/url_helper.rb +23 -14
- data/lib/action_view/partials.rb +12 -4
- data/rakefile +13 -5
- data/test/abstract_unit.rb +4 -3
- data/test/active_record_unit.rb +88 -0
- data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
- data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
- data/test/activerecord/pagination_test.rb +161 -0
- data/test/controller/action_pack_assertions_test.rb +18 -15
- data/test/controller/base_test.rb +31 -42
- data/test/controller/benchmark_test.rb +8 -11
- data/test/controller/capture_test.rb +33 -1
- data/test/controller/cgi_test.rb +33 -0
- data/test/controller/custom_handler_test.rb +8 -0
- data/test/controller/fake_controllers.rb +9 -17
- data/test/controller/filters_test.rb +32 -3
- data/test/controller/flash_test.rb +26 -41
- data/test/controller/fragment_store_setting_test.rb +1 -1
- data/test/controller/layout_test.rb +73 -0
- data/test/controller/mime_responds_test.rb +257 -0
- data/test/controller/mime_type_test.rb +24 -0
- data/test/controller/new_render_test.rb +157 -1
- data/test/controller/redirect_test.rb +23 -0
- data/test/controller/render_test.rb +54 -56
- data/test/controller/request_test.rb +25 -0
- data/test/controller/routing_test.rb +74 -66
- data/test/controller/test_test.rb +66 -1
- data/test/controller/verification_test.rb +3 -1
- data/test/controller/webservice_test.rb +255 -0
- data/test/fixtures/companies.yml +24 -0
- data/test/fixtures/company.rb +9 -0
- data/test/fixtures/db_definitions/sqlite.sql +42 -0
- data/test/fixtures/developer.rb +7 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +13 -0
- data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
- data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
- data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
- data/test/fixtures/multipart/mona_lisa.jpg +0 -0
- data/test/fixtures/project.rb +3 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/replies.yml +13 -0
- data/test/fixtures/reply.rb +5 -0
- data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
- data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
- data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults.rxml +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
- data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
- data/test/fixtures/test/block_content_for.rhtml +2 -0
- data/test/fixtures/test/delete_with_js.rjs +2 -0
- data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
- data/test/fixtures/test/enum_rjs_test.rjs +6 -0
- data/test/fixtures/test/erb_content_for.rhtml +2 -0
- data/test/fixtures/test/hello_world.rxml +3 -0
- data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
- data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
- data/test/fixtures/topic.rb +3 -0
- data/test/fixtures/topics.yml +22 -0
- data/test/template/active_record_helper_test.rb +4 -0
- data/test/template/asset_tag_helper_test.rb +7 -2
- data/test/template/date_helper_test.rb +39 -2
- data/test/template/form_helper_test.rb +238 -5
- data/test/template/form_options_helper_test.rb +78 -0
- data/test/template/form_tag_helper_test.rb +11 -0
- data/test/template/java_script_macros_helper_test.rb +51 -6
- data/test/template/javascript_helper_test.rb +7 -153
- data/test/template/number_helper_test.rb +14 -13
- data/test/template/prototype_helper_test.rb +423 -0
- data/test/template/scriptaculous_helper_test.rb +90 -0
- data/test/template/text_helper_test.rb +12 -9
- data/test/template/url_helper_test.rb +31 -15
- metadata +291 -246
- data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
- data/lib/action_controller/upload_progress.rb +0 -473
- data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
- data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
- data/lib/action_view/vendor/builder.rb +0 -13
- data/lib/action_view/vendor/builder/blankslate.rb +0 -53
- data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
- data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
- data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
- data/test/controller/multipart_progress_testx.rb +0 -365
- data/test/controller/upload_progress_testx.rb +0 -89
- data/test/template/upload_progress_helper_testx.rb +0 -136
@@ -1,16 +1,16 @@
|
|
1
1
|
module ActionController #:nodoc:
|
2
2
|
module Layout #:nodoc:
|
3
|
-
def self.
|
4
|
-
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
5
|
base.class_eval do
|
6
6
|
alias_method :render_with_no_layout, :render
|
7
7
|
alias_method :render, :render_with_a_layout
|
8
8
|
|
9
9
|
class << self
|
10
10
|
alias_method :inherited_without_layout, :inherited
|
11
|
+
alias_method :inherited, :inherited_with_layout
|
11
12
|
end
|
12
13
|
end
|
13
|
-
base.extend(ClassMethods)
|
14
14
|
end
|
15
15
|
|
16
16
|
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
|
@@ -66,9 +66,11 @@ module ActionController #:nodoc:
|
|
66
66
|
# <tt>app/views/layouts/weblog.rhtml</tt> or <tt>app/views/layouts/weblog.rxml</tt> exists then it will be automatically set as
|
67
67
|
# the layout for your WeblogController. You can create a layout with the name <tt>application.rhtml</tt> or <tt>application.rxml</tt>
|
68
68
|
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
69
|
-
# no layout explicitly assigned with the +layout+ method.
|
70
|
-
#
|
71
|
-
#
|
69
|
+
# no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
|
70
|
+
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.rhtml</tt>.
|
71
|
+
# Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
|
72
|
+
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignement if the child
|
73
|
+
# class has a layout with the same name.
|
72
74
|
#
|
73
75
|
# == Inheritance for layouts
|
74
76
|
#
|
@@ -168,17 +170,23 @@ module ActionController #:nodoc:
|
|
168
170
|
end
|
169
171
|
|
170
172
|
def layout_conditions #:nodoc:
|
171
|
-
read_inheritable_attribute("layout_conditions")
|
173
|
+
@layout_conditions ||= read_inheritable_attribute("layout_conditions")
|
174
|
+
end
|
175
|
+
|
176
|
+
def default_layout #:nodoc:
|
177
|
+
@default_layout ||= read_inheritable_attribute("layout")
|
172
178
|
end
|
173
179
|
|
174
180
|
private
|
175
|
-
def
|
181
|
+
def inherited_with_layout(child)
|
176
182
|
inherited_without_layout(child)
|
177
|
-
child.
|
183
|
+
child.send :include, Reloadable
|
184
|
+
layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
|
185
|
+
child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
|
178
186
|
end
|
179
187
|
|
180
188
|
def layout_list
|
181
|
-
Dir.glob("#{template_root}/layouts
|
189
|
+
Dir.glob("#{template_root}/layouts/**/*")
|
182
190
|
end
|
183
191
|
|
184
192
|
def add_layout_conditions(conditions)
|
@@ -188,6 +196,12 @@ module ActionController #:nodoc:
|
|
188
196
|
def normalize_conditions(conditions)
|
189
197
|
conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
|
190
198
|
end
|
199
|
+
|
200
|
+
def layout_directory_exists_cache
|
201
|
+
@@layout_directory_exists_cache ||= Hash.new do |h, dirname|
|
202
|
+
h[dirname] = File.directory? dirname
|
203
|
+
end
|
204
|
+
end
|
191
205
|
end
|
192
206
|
|
193
207
|
# Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
|
@@ -195,18 +209,27 @@ module ActionController #:nodoc:
|
|
195
209
|
# object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
|
196
210
|
# weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
|
197
211
|
def active_layout(passed_layout = nil)
|
198
|
-
layout = passed_layout || self.class.
|
212
|
+
layout = passed_layout || self.class.default_layout
|
199
213
|
|
200
214
|
active_layout = case layout
|
215
|
+
when String then layout
|
201
216
|
when Symbol then send(layout)
|
202
217
|
when Proc then layout.call(self)
|
203
|
-
when String then layout
|
204
218
|
end
|
205
|
-
|
206
|
-
|
219
|
+
|
220
|
+
# Explicitly passed layout names with slashes are looked up relative to the template root,
|
221
|
+
# but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
|
222
|
+
# to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
|
223
|
+
if active_layout
|
224
|
+
if active_layout.include?('/') && ! layout_directory?(active_layout)
|
225
|
+
active_layout
|
226
|
+
else
|
227
|
+
"layouts/#{active_layout}"
|
228
|
+
end
|
229
|
+
end
|
207
230
|
end
|
208
231
|
|
209
|
-
def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil) #:nodoc:
|
232
|
+
def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
|
210
233
|
template_with_options = options.is_a?(Hash)
|
211
234
|
|
212
235
|
if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
|
@@ -214,10 +237,10 @@ module ActionController #:nodoc:
|
|
214
237
|
logger.info("Rendering #{options} within #{layout}") if logger
|
215
238
|
|
216
239
|
if template_with_options
|
217
|
-
content_for_layout = render_with_no_layout(options)
|
240
|
+
content_for_layout = render_with_no_layout(options, &block)
|
218
241
|
deprecated_status = options[:status] || deprecated_status
|
219
242
|
else
|
220
|
-
content_for_layout = render_with_no_layout(options, deprecated_status)
|
243
|
+
content_for_layout = render_with_no_layout(options, deprecated_status, &block)
|
221
244
|
end
|
222
245
|
|
223
246
|
erase_render_results
|
@@ -225,17 +248,21 @@ module ActionController #:nodoc:
|
|
225
248
|
@template.instance_variable_set("@content_for_layout", content_for_layout)
|
226
249
|
render_text(@template.render_file(layout, true), deprecated_status)
|
227
250
|
else
|
228
|
-
render_with_no_layout(options, deprecated_status)
|
251
|
+
render_with_no_layout(options, deprecated_status, &block)
|
229
252
|
end
|
230
253
|
end
|
231
254
|
|
232
255
|
private
|
256
|
+
|
233
257
|
def apply_layout?(template_with_options, options)
|
234
|
-
if
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
258
|
+
return false if options == :update
|
259
|
+
template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
|
260
|
+
end
|
261
|
+
|
262
|
+
def candidate_for_layout?(options)
|
263
|
+
(options.has_key?(:layout) && options[:layout] != false) ||
|
264
|
+
options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
|
265
|
+
!template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
|
239
266
|
end
|
240
267
|
|
241
268
|
def pick_layout(template_with_options, options, deprecated_layout)
|
@@ -269,5 +296,13 @@ module ActionController #:nodoc:
|
|
269
296
|
true
|
270
297
|
end
|
271
298
|
end
|
299
|
+
|
300
|
+
# Does a layout directory for this class exist?
|
301
|
+
# we cache this info in a class level hash
|
302
|
+
def layout_directory?(layout_name)
|
303
|
+
template_path = File.join(self.class.view_root, 'layouts', layout_name)
|
304
|
+
dirname = File.dirname(template_path)
|
305
|
+
self.class.send(:layout_directory_exists_cache)[dirname]
|
306
|
+
end
|
272
307
|
end
|
273
308
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module ActionController #:nodoc:
|
2
|
+
module MimeResponds #:nodoc:
|
3
|
+
def self.included(base)
|
4
|
+
base.send(:include, ActionController::MimeResponds::InstanceMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module InstanceMethods
|
8
|
+
# Without web-service support, an action which collects the data for displaying a list of people
|
9
|
+
# might look something like this:
|
10
|
+
#
|
11
|
+
# def list
|
12
|
+
# @people = Person.find(:all)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Here's the same action, with web-service support baked in:
|
16
|
+
#
|
17
|
+
# def list
|
18
|
+
# @people = Person.find(:all)
|
19
|
+
#
|
20
|
+
# respond_to do |wants|
|
21
|
+
# wants.html
|
22
|
+
# wants.xml { render :xml => @people.to_xml }
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# What that says is, "if the client wants HTML in response to this action, just respond as we
|
27
|
+
# would have before, but if the client wants XML, return them the list of people in XML format."
|
28
|
+
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
|
29
|
+
#
|
30
|
+
# Supposing you have an action that adds a new person, optionally creating their company
|
31
|
+
# (by name) if it does not already exist, without web-services, it might look like this:
|
32
|
+
#
|
33
|
+
# def add
|
34
|
+
# @company = Company.find_or_create_by_name(params[:company][:name])
|
35
|
+
# @person = @company.people.create(params[:person])
|
36
|
+
#
|
37
|
+
# redirect_to(person_list_url)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Here's the same action, with web-service support baked in:
|
41
|
+
#
|
42
|
+
# def add
|
43
|
+
# company = params[:person].delete(:company)
|
44
|
+
# @company = Company.find_or_create_by_name(company[:name])
|
45
|
+
# @person = @company.people.create(params[:person])
|
46
|
+
#
|
47
|
+
# respond_to do |wants|
|
48
|
+
# wants.html { redirect_to(person_list_url) }
|
49
|
+
# wants.js
|
50
|
+
# wants.xml { render :xml => @person.to_xml(:include => @company) }
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# If the client wants HTML, we just redirect them back to the person list. If they want Javascript
|
55
|
+
# (wants.js), then it is an RJS request and we render the RJS template associated with this action.
|
56
|
+
# Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
|
57
|
+
# include the person’s company in the rendered XML, so you get something like this:
|
58
|
+
#
|
59
|
+
# <person>
|
60
|
+
# <id>...</id>
|
61
|
+
# ...
|
62
|
+
# <company>
|
63
|
+
# <id>...</id>
|
64
|
+
# <name>...</name>
|
65
|
+
# ...
|
66
|
+
# </company>
|
67
|
+
# </person>
|
68
|
+
#
|
69
|
+
# Note, however, the extra bit at the top of that action:
|
70
|
+
#
|
71
|
+
# company = params[:person].delete(:company)
|
72
|
+
# @company = Company.find_or_create_by_name(company[:name])
|
73
|
+
#
|
74
|
+
# This is because the incoming XML document (if a web-service request is in process) can only contain a
|
75
|
+
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
|
76
|
+
#
|
77
|
+
# person[name]=...&person[company][name]=...&...
|
78
|
+
#
|
79
|
+
# And, like this (xml-encoded):
|
80
|
+
#
|
81
|
+
# <person>
|
82
|
+
# <name>...</name>
|
83
|
+
# <company>
|
84
|
+
# <name>...</name>
|
85
|
+
# </company>
|
86
|
+
# </person>
|
87
|
+
#
|
88
|
+
# In other words, we make the request so that it operates on a single entity—a person. Then, in the action,
|
89
|
+
# we extract the company data from the request, find or create the company, and then create the new person
|
90
|
+
# with the remaining data.
|
91
|
+
#
|
92
|
+
# Note that you can define your own XML parameter parser which would allow you to describe multiple entities
|
93
|
+
# in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow
|
94
|
+
# and accept Rails' defaults, life will be much easier.
|
95
|
+
def respond_to(*types, &block)
|
96
|
+
raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block
|
97
|
+
block ||= lambda { |responder| types.each { |type| responder.send(type) } }
|
98
|
+
responder = Responder.new(block.binding)
|
99
|
+
block.call(responder)
|
100
|
+
responder.respond
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class Responder #:nodoc:
|
105
|
+
DEFAULT_BLOCKS = {
|
106
|
+
:html => 'Proc.new { render }',
|
107
|
+
:js => 'Proc.new { render :action => "#{action_name}.rjs" }',
|
108
|
+
:xml => 'Proc.new { render :action => "#{action_name}.rxml" }'
|
109
|
+
}
|
110
|
+
|
111
|
+
def initialize(block_binding)
|
112
|
+
@block_binding = block_binding
|
113
|
+
@mime_type_priority = eval("request.accepts", block_binding)
|
114
|
+
@order = []
|
115
|
+
@responses = {}
|
116
|
+
end
|
117
|
+
|
118
|
+
def custom(mime_type, &block)
|
119
|
+
mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
|
120
|
+
|
121
|
+
@order << mime_type
|
122
|
+
|
123
|
+
if block_given?
|
124
|
+
@responses[mime_type] = block
|
125
|
+
else
|
126
|
+
@responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
for mime_type in %w( all html js xml rss atom yaml )
|
131
|
+
eval <<-EOT
|
132
|
+
def #{mime_type}(&block)
|
133
|
+
custom(Mime::#{mime_type.upcase}, &block)
|
134
|
+
end
|
135
|
+
EOT
|
136
|
+
end
|
137
|
+
|
138
|
+
def any(*args, &block)
|
139
|
+
args.each { |type| send(type, &block) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def respond
|
143
|
+
for priority in @mime_type_priority
|
144
|
+
if priority == Mime::ALL
|
145
|
+
@responses[@order.first].call
|
146
|
+
return
|
147
|
+
else
|
148
|
+
if priority === @order
|
149
|
+
@responses[priority].call
|
150
|
+
return # mime type match found, be happy and return
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if @order.include?(Mime::ALL)
|
156
|
+
@responses[Mime::ALL].call
|
157
|
+
else
|
158
|
+
eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Mime
|
2
|
+
class Type #:nodoc:
|
3
|
+
# A simple helper class used in parsing the accept header
|
4
|
+
class AcceptItem #:nodoc:
|
5
|
+
attr_accessor :order, :name, :q
|
6
|
+
|
7
|
+
def initialize(order, name, q=nil)
|
8
|
+
@order = order
|
9
|
+
@name = name.strip
|
10
|
+
q ||= 0.0 if @name == "*/*" # default "*/*" to end of list
|
11
|
+
@q = ((q || 1.0).to_f * 100).to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@name
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=>(item)
|
19
|
+
result = item.q <=> q
|
20
|
+
result = order <=> item.order if result == 0
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(item)
|
25
|
+
name == (item.respond_to?(:name) ? item.name : item)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def lookup(string)
|
31
|
+
LOOKUP[string]
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse(accept_header)
|
35
|
+
# keep track of creation order to keep the subsequent sort stable
|
36
|
+
index = 0
|
37
|
+
list = accept_header.split(/,/).
|
38
|
+
map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort!
|
39
|
+
|
40
|
+
# Take care of the broken text/xml entry by renaming or deleting it
|
41
|
+
|
42
|
+
text_xml = list.index("text/xml")
|
43
|
+
app_xml = list.index("application/xml")
|
44
|
+
|
45
|
+
if text_xml && app_xml
|
46
|
+
# set the q value to the max of the two
|
47
|
+
list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
|
48
|
+
|
49
|
+
# make sure app_xml is ahead of text_xml in the list
|
50
|
+
if app_xml > text_xml
|
51
|
+
list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
|
52
|
+
app_xml, text_xml = text_xml, app_xml
|
53
|
+
end
|
54
|
+
|
55
|
+
# delete text_xml from the list
|
56
|
+
list.delete_at(text_xml)
|
57
|
+
|
58
|
+
elsif text_xml
|
59
|
+
list[text_xml].name = "application/xml"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Look for more specific xml-based types and sort them ahead of app/xml
|
63
|
+
|
64
|
+
if app_xml
|
65
|
+
idx = app_xml
|
66
|
+
app_xml_type = list[app_xml]
|
67
|
+
|
68
|
+
while(idx < list.length)
|
69
|
+
type = list[idx]
|
70
|
+
break if type.q < app_xml_type.q
|
71
|
+
if type.name =~ /\+xml$/
|
72
|
+
list[app_xml], list[idx] = list[idx], list[app_xml]
|
73
|
+
app_xml = idx
|
74
|
+
end
|
75
|
+
idx += 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
80
|
+
list
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(string, symbol = nil, synonyms = [])
|
85
|
+
@symbol, @synonyms = symbol, synonyms
|
86
|
+
@string = string
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
@string
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_str
|
94
|
+
to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_sym
|
98
|
+
@symbol || @string.to_sym
|
99
|
+
end
|
100
|
+
|
101
|
+
def ===(list)
|
102
|
+
if list.is_a?(Array)
|
103
|
+
(@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(mime_type)
|
110
|
+
(@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
ALL = Type.new "*/*", :all
|
115
|
+
HTML = Type.new "text/html", :html, %w( application/xhtml+xml )
|
116
|
+
JS = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
|
117
|
+
XML = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
|
118
|
+
RSS = Type.new "application/rss+xml", :rss
|
119
|
+
ATOM = Type.new "application/atom+xml", :atom
|
120
|
+
YAML = Type.new "application/x-yaml", :yaml, %w( text/yaml )
|
121
|
+
|
122
|
+
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) }
|
123
|
+
|
124
|
+
LOOKUP["*/*"] = ALL
|
125
|
+
|
126
|
+
LOOKUP["text/html"] = HTML
|
127
|
+
LOOKUP["application/xhtml+xml"] = HTML
|
128
|
+
|
129
|
+
LOOKUP["application/xml"] = XML
|
130
|
+
LOOKUP["text/xml"] = XML
|
131
|
+
LOOKUP["application/x-xml"] = XML
|
132
|
+
|
133
|
+
LOOKUP["text/javascript"] = JS
|
134
|
+
LOOKUP["application/javascript"] = JS
|
135
|
+
LOOKUP["application/x-javascript"] = JS
|
136
|
+
|
137
|
+
LOOKUP["text/yaml"] = YAML
|
138
|
+
LOOKUP["application/x-yaml"] = YAML
|
139
|
+
|
140
|
+
LOOKUP["application/rss+xml"] = RSS
|
141
|
+
LOOKUP["application/atom+xml"] = ATOM
|
142
|
+
end
|