On 16.04.2011 16:51, Iki 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/