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.

Files changed (149) hide show
  1. data/CHANGELOG +392 -5
  2. data/lib/action_controller.rb +8 -4
  3. data/lib/action_controller/assertions.rb +9 -10
  4. data/lib/action_controller/base.rb +177 -88
  5. data/lib/action_controller/benchmarking.rb +5 -5
  6. data/lib/action_controller/caching.rb +44 -36
  7. data/lib/action_controller/cgi_ext/cgi_methods.rb +71 -6
  8. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +1 -1
  9. data/lib/action_controller/cgi_process.rb +36 -24
  10. data/lib/action_controller/components.rb +152 -52
  11. data/lib/action_controller/dependencies.rb +1 -1
  12. data/lib/action_controller/deprecated_redirects.rb +2 -2
  13. data/lib/action_controller/deprecated_request_methods.rb +34 -0
  14. data/lib/action_controller/filters.rb +59 -19
  15. data/lib/action_controller/flash.rb +53 -47
  16. data/lib/action_controller/helpers.rb +2 -2
  17. data/lib/action_controller/integration.rb +524 -0
  18. data/lib/action_controller/layout.rb +58 -23
  19. data/lib/action_controller/mime_responds.rb +163 -0
  20. data/lib/action_controller/mime_type.rb +142 -0
  21. data/lib/action_controller/pagination.rb +13 -7
  22. data/lib/action_controller/request.rb +59 -56
  23. data/lib/action_controller/rescue.rb +1 -1
  24. data/lib/action_controller/routing.rb +29 -10
  25. data/lib/action_controller/scaffolding.rb +8 -0
  26. data/lib/action_controller/session/active_record_store.rb +21 -10
  27. data/lib/action_controller/session/mem_cache_store.rb +18 -12
  28. data/lib/action_controller/session_management.rb +30 -11
  29. data/lib/action_controller/templates/rescues/_trace.rhtml +1 -1
  30. data/lib/action_controller/templates/scaffolds/layout.rhtml +4 -4
  31. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  32. data/lib/action_controller/test_process.rb +189 -118
  33. data/lib/action_controller/vendor/html-scanner/html/node.rb +20 -1
  34. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +3 -0
  35. data/lib/action_controller/vendor/html-scanner/html/version.rb +1 -1
  36. data/lib/action_controller/vendor/xml_node.rb +97 -0
  37. data/lib/action_controller/verification.rb +2 -0
  38. data/lib/action_pack/version.rb +3 -3
  39. data/lib/action_view.rb +0 -2
  40. data/lib/action_view/base.rb +109 -36
  41. data/lib/action_view/compiled_templates.rb +1 -1
  42. data/lib/action_view/helpers/active_record_helper.rb +4 -2
  43. data/lib/action_view/helpers/asset_tag_helper.rb +6 -7
  44. data/lib/action_view/helpers/capture_helper.rb +49 -12
  45. data/lib/action_view/helpers/date_helper.rb +14 -4
  46. data/lib/action_view/helpers/form_helper.rb +136 -20
  47. data/lib/action_view/helpers/form_options_helper.rb +29 -7
  48. data/lib/action_view/helpers/form_tag_helper.rb +22 -20
  49. data/lib/action_view/helpers/java_script_macros_helper.rb +29 -9
  50. data/lib/action_view/helpers/javascript_helper.rb +50 -446
  51. data/lib/action_view/helpers/javascripts/controls.js +95 -30
  52. data/lib/action_view/helpers/javascripts/dragdrop.js +161 -21
  53. data/lib/action_view/helpers/javascripts/effects.js +310 -211
  54. data/lib/action_view/helpers/javascripts/prototype.js +228 -28
  55. data/lib/action_view/helpers/number_helper.rb +9 -9
  56. data/lib/action_view/helpers/pagination_helper.rb +1 -1
  57. data/lib/action_view/helpers/prototype_helper.rb +900 -0
  58. data/lib/action_view/helpers/scriptaculous_helper.rb +135 -0
  59. data/lib/action_view/helpers/text_helper.rb +7 -6
  60. data/lib/action_view/helpers/url_helper.rb +23 -14
  61. data/lib/action_view/partials.rb +12 -4
  62. data/rakefile +13 -5
  63. data/test/abstract_unit.rb +4 -3
  64. data/test/active_record_unit.rb +88 -0
  65. data/test/{controller → activerecord}/active_record_assertions_test.rb +7 -50
  66. data/test/{controller → activerecord}/active_record_store_test.rb +27 -4
  67. data/test/activerecord/pagination_test.rb +161 -0
  68. data/test/controller/action_pack_assertions_test.rb +18 -15
  69. data/test/controller/base_test.rb +31 -42
  70. data/test/controller/benchmark_test.rb +8 -11
  71. data/test/controller/capture_test.rb +33 -1
  72. data/test/controller/cgi_test.rb +33 -0
  73. data/test/controller/custom_handler_test.rb +8 -0
  74. data/test/controller/fake_controllers.rb +9 -17
  75. data/test/controller/filters_test.rb +32 -3
  76. data/test/controller/flash_test.rb +26 -41
  77. data/test/controller/fragment_store_setting_test.rb +1 -1
  78. data/test/controller/layout_test.rb +73 -0
  79. data/test/controller/mime_responds_test.rb +257 -0
  80. data/test/controller/mime_type_test.rb +24 -0
  81. data/test/controller/new_render_test.rb +157 -1
  82. data/test/controller/redirect_test.rb +23 -0
  83. data/test/controller/render_test.rb +54 -56
  84. data/test/controller/request_test.rb +25 -0
  85. data/test/controller/routing_test.rb +74 -66
  86. data/test/controller/test_test.rb +66 -1
  87. data/test/controller/verification_test.rb +3 -1
  88. data/test/controller/webservice_test.rb +255 -0
  89. data/test/fixtures/companies.yml +24 -0
  90. data/test/fixtures/company.rb +9 -0
  91. data/test/fixtures/db_definitions/sqlite.sql +42 -0
  92. data/test/fixtures/developer.rb +7 -0
  93. data/test/fixtures/developers.yml +21 -0
  94. data/test/fixtures/developers_projects.yml +13 -0
  95. data/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml +1 -0
  96. data/test/fixtures/layout_tests/layouts/item.rhtml +1 -0
  97. data/test/fixtures/layout_tests/layouts/layout_test.rhtml +1 -0
  98. data/test/fixtures/layout_tests/layouts/third_party_template_library.mab +1 -0
  99. data/test/fixtures/layout_tests/views/hello.rhtml +1 -0
  100. data/test/fixtures/multipart/mona_lisa.jpg +0 -0
  101. data/test/fixtures/project.rb +3 -0
  102. data/test/fixtures/projects.yml +7 -0
  103. data/test/fixtures/replies.yml +13 -0
  104. data/test/fixtures/reply.rb +5 -0
  105. data/test/fixtures/respond_to/all_types_with_layout.rhtml +1 -0
  106. data/test/fixtures/respond_to/all_types_with_layout.rjs +1 -0
  107. data/test/fixtures/respond_to/layouts/standard.rhtml +1 -0
  108. data/test/fixtures/respond_to/using_defaults.rhtml +1 -0
  109. data/test/fixtures/respond_to/using_defaults.rjs +1 -0
  110. data/test/fixtures/respond_to/using_defaults.rxml +1 -0
  111. data/test/fixtures/respond_to/using_defaults_with_type_list.rhtml +1 -0
  112. data/test/fixtures/respond_to/using_defaults_with_type_list.rjs +1 -0
  113. data/test/fixtures/respond_to/using_defaults_with_type_list.rxml +1 -0
  114. data/test/fixtures/test/block_content_for.rhtml +2 -0
  115. data/test/fixtures/test/delete_with_js.rjs +2 -0
  116. data/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml +1 -0
  117. data/test/fixtures/test/enum_rjs_test.rjs +6 -0
  118. data/test/fixtures/test/erb_content_for.rhtml +2 -0
  119. data/test/fixtures/test/hello_world.rxml +3 -0
  120. data/test/fixtures/test/hello_world_with_layout_false.rhtml +1 -0
  121. data/test/fixtures/test/non_erb_block_content_for.rxml +4 -0
  122. data/test/fixtures/topic.rb +3 -0
  123. data/test/fixtures/topics.yml +22 -0
  124. data/test/template/active_record_helper_test.rb +4 -0
  125. data/test/template/asset_tag_helper_test.rb +7 -2
  126. data/test/template/date_helper_test.rb +39 -2
  127. data/test/template/form_helper_test.rb +238 -5
  128. data/test/template/form_options_helper_test.rb +78 -0
  129. data/test/template/form_tag_helper_test.rb +11 -0
  130. data/test/template/java_script_macros_helper_test.rb +51 -6
  131. data/test/template/javascript_helper_test.rb +7 -153
  132. data/test/template/number_helper_test.rb +14 -13
  133. data/test/template/prototype_helper_test.rb +423 -0
  134. data/test/template/scriptaculous_helper_test.rb +90 -0
  135. data/test/template/text_helper_test.rb +12 -9
  136. data/test/template/url_helper_test.rb +31 -15
  137. metadata +291 -246
  138. data/lib/action_controller/cgi_ext/multipart_progress.rb +0 -169
  139. data/lib/action_controller/upload_progress.rb +0 -473
  140. data/lib/action_controller/vendor/html-scanner/html/node.rb.rej +0 -17
  141. data/lib/action_view/helpers/upload_progress_helper.rb +0 -433
  142. data/lib/action_view/vendor/builder.rb +0 -13
  143. data/lib/action_view/vendor/builder/blankslate.rb +0 -53
  144. data/lib/action_view/vendor/builder/xmlbase.rb +0 -143
  145. data/lib/action_view/vendor/builder/xmlevents.rb +0 -63
  146. data/lib/action_view/vendor/builder/xmlmarkup.rb +0 -308
  147. data/test/controller/multipart_progress_testx.rb +0 -365
  148. data/test/controller/upload_progress_testx.rb +0 -89
  149. 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.append_features(base)
4
- super
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. Setting a layout explicitly will always override the automatic behaviour
70
- # for the controller where the layout is set. Explicitly setting the layout in a parent class, though, will not override the
71
- # child class's layout assignement if the child class has a layout with the same name.
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 inherited(child)
181
+ def inherited_with_layout(child)
176
182
  inherited_without_layout(child)
177
- child.layout(child.controller_name) unless layout_list.grep(/^#{child.controller_name}\.r(?:x|ht)ml$/).empty?
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/*.r{x,ht}ml").map { |layout| File.basename(layout) }
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.read_inheritable_attribute("layout")
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
- active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
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 template_with_options
235
- (options.has_key?(:layout) && options[:layout]!=false) || options.values_at(:text, :file, :inline, :partial, :nothing).compact.empty?
236
- else
237
- true
238
- end
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