On 16.04.2011 16:51, Iñáki Baz Castillo wrote: > 2011/4/15 Kevin Mahler<kevin.mahler / yahoo.com>: >> He has a key. It contains some >> data. It's not necessarily true that he should duplicate that data in >> the mapped-to values. > > To clarify, my exact case is the following: Now it gets interesting. :-) > I've coded a parser for SIP (similar to HTTP). The parser generates a > Request object which inherits from Hash, Usually it's better to use composition instead of inheritance to achieve his. Now your SipRequest inherits *all* methods from Hash including some that you might not want users to be able to invoke. > and each SIP request header > (i.e. "From: sip:alice / example.org") becomes an entry of the hash > (Request object) as follows: > > - The key is "FROM" (capitalized). > - The value is an Array of strings (a s header can have multiple values). > > I need to store the key capitalized for fastest lookup, but I also > want to store the original header name (which could be "from", "From", > "frOM" and so). So, to sum it up: you want to have a class for SIP request which allows (efficient) header field access through [] using header name in any case pelling. > So my parser adds an instance variable @real_name within the header > name string ("FROM"). > > When I do the lookup of a header in the Request object, I would like > also to retrieve the key's @real_name, but I've already understood > that this is only possible if taint the key string before inserting it > in the hash and use Hash#assoc. This solution is not good for > performance. > > The solution suggested by Robert is adding such information (the > header original name) as a field in the hash entry value, so instead > of having: > > request["FROM"] > => [ "sip:alice / xample.org ] > > I would end with something like: > > request["FROM"] > => Struct ( "From", [ "sip:alice / xample.org ] ) > > The problem this last suggestion introduces is that it breaks the > existing API and makes more complext for a developer to handle the > Request class (which should be as easy as handling a Hash). Here's how I'd do it. First, I would start with the interface, maybe something like this module SIP class Request def self.parse(io) # ... end # get a header field by symbol def [](header_name_sym) end # return the real name used def header_name(header_name_sym) end end end Then I'd think how I could make that API work properly. For example two ariants, error and default value: module SIP class Request HdrInfo = Struct.new name, values DUMMY = HdrInfo[nil, [].freeze].freeze LT = "\r\n".freeze def self.parse(io) hdr = {} io.each_line LT do |l| case l when /^([^:]+:\s*(.*)$/ # too simplistic parsing! hdr[$1] = $2.split(/,/).each(&:strip!) when /^$/ break else raise "Not a header line: %p" % l end end new(hdr) end def initialize(headers) @hdr = {} # assume hdr is String and values is parsed headers.each do |hdr, values| @hdr[normalize(hdr)] = HdrInfo[hdr, values] end end # get a header field by symbol def [](header_name_sym) @hdr.fetch(normalize(header_name_sym)) do |k| DUMMY end.values end # return the real name used def header_name(header_name_sym) @hdr.fetch(normalize(header_name_sym)).do |k| raise ArgumentError, "Header not found %p" % header_name_sym end.name end private def normalize(h) /[A-Z]/ =~ h ? h.downcase : h).to_sym end end end Of course we could build the internal hash straight away during parsing. he main focus of the example was how to use the header once parsed. > Thanks to both for your comments. You're welcome. Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/