コンテントネゴシエーション

accept = ENV["HTTP_ACCEPT"]

def accept.to_a
  split(/\s*,\s*/).inject([]) do |ret, value|
    media_type, qs = value.split(/\s*;\s*q=/)
    ret << [media_type, qs ? qs.to_f : 1.to_f]
  end
end

def accept.negotiate(servable)
  # servable= [{:media_type => "application/xhtml+xml", :q => 1.0, :flavour => "xhtml"}, ...]
  media_types = to_a.collect {|i| i[0] }

  servable.collect {|i|
    [i[:media_type], i[:q], i[:flavour]]
  }.select {|i|
    media_types.include?(i[0])
  }.sort_by {|i|
    (i[1] * 1000) - i[0].count("*") + i[0].count(";") + i[0].count("+")
  }.pop

こんなの書いた。なにがなんでもメソッドチェーンしたかったので、特異メソッドで。普通に関数(的メソッド)でいい気もする。
accept#negotiateはコメントにあるようなハッシュを要素にもつ配列を引数に受け取り、それとUAのAcceptの値を比較して、UAが要求するメディアタイプとサーバが提供できるメディアタイプの積集合を得る。それをqvalueの値でソートして最大になったメディアタイプ(とqvalueとflavourの組)を返す。みたいなかんじ。

あ、qvalueをかけあわせてない…。だめじゃん。要再考。

def accept.negotiate(servable)
  servable.map {|i|
    [i[:media_type], i[:q], i[:flavour]]
  }.sort_by {|i|
    ((to_a.assoc(i[0]) || 0) * i[1] * 1000) - i[0].count("*") + i[0].count("+") + i[0].count(";")
  }.pop
end

実質、Enumerable#sort_byだけで実装できた。なんという…。