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 this. 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 spelling.
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 variants, 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. The 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
···
On 16.04.2011 16:51, Iñaki Baz Castillo wrote:
2011/4/15 Kevin Mahler<kevin.mahler@yahoo.com>:
--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/