Is there a better way to do this?


#1

I recently had code which needed to work with the same structure twice,
once using the . nomenclature and once with an index into a hash.
i.e. in one case I had

entries=@log.entry

which I would interrogate with:

entries.timings.connect.first_byte

or alternatively

entries.XmlSimple(in_file)

which I would interrogate with:

entries[‘timings’][‘connect’][‘first_byte’].

Rather than write two blocks for code, one for the in-memory, I decided
to override the base structure that XmlSimple used, but I found out that
it was Hash. So the following was what I came up with:

class Hash
def method_missing(sym,*args,&blk)
return self[sym] if self.keys.include?(sym)
return self[sym.to_s] if self.keys.include?(sym.to_s)
super
end
end

which works for me, but I was wondering if there is a better way to do
this than calling self.keys.include? to determine if the key exists
without triggering any unexpected side effects.

Mac
http://pqmf.com


#2

On Tue, Apr 21, 2009 at 9:01 PM, Paul M. removed_email_address@domain.invalid
wrote:

class Hash
def method_missing(sym,*args,&blk)
A time bomb!
But I might do the same and …
… pay the same price debugging.

Just my 0.02$

Cheers
Robert


Si tu veux construire un bateau …
Ne rassemble pas des hommes pour aller chercher du bois, préparer des
outils, répartir les tâches, alléger le travail… mais enseigne aux
gens la nostalgie de l’infini de la mer.

If you want to build a ship, don’t herd people together to collect
wood and don’t assign them tasks and work, but rather teach them to
long for the endless immensity of the sea.


#3

Robert D. wrote:

A time bomb!
But I might do the same and …
… pay the same price debugging.

Just my 0.02$

Thanks Robert,

I agree it is, but I didn’t have the time to spend working out a more
elegant way of handling the metaprogramming goodness that would be
required to perform the same function in a safer way. I think this
protects me well enough from Hashes with block initialization, and
extant methods.

Waiting for the ba-boom

Mac


#4

matt neuburg wrote:

I would have thought that key? is the basic form here.

absolutely right. I’d forgotten about that method.

Also, just a thought, you might look at ostruct which plays off the very
same impulse you’re having (i.e. to talk to hash as if it were a struct
with accessors). The code might give you some ideas…

thanks matt, I’ll take a look

m.


#5

Paul M. removed_email_address@domain.invalid wrote:

or alternatively

without triggering any unexpected side effects.
I would have thought that key? is the basic form here.

Also, just a thought, you might look at ostruct which plays off the very
same impulse you’re having (i.e. to talk to hash as if it were a struct
with accessors). The code might give you some ideas…

m.


#6

On 21.04.2009 21:01, Paul M. wrote:

I recently had code which needed to work with the same structure twice,
once using the . nomenclature and once with an index into a hash.

If I understand the rest of your post correctly it is not exactly the
same structure but rather similarly structured data in two different
data structures (custom classes and XML DOM).

The fix that I propose is to not have two data structures storing the
same data. If you have classes already for storing all this, I’d
probably write bit of code that builds the structure using an XML push
or pull parser.

entries.XmlSimple(in_file)
def method_missing(sym,*args,&blk)
return self[sym] if self.keys.include?(sym)
return self[sym.to_s] if self.keys.include?(sym.to_s)
super
end
end

Usually nil is returned for absent keys so you can do

def method_missing(sym,*args,&blk)
self[sym] || self[sym.to_s] || super
end

which works for me, but I was wondering if there is a better way to do
this than calling self.keys.include? to determine if the key exists
without triggering any unexpected side effects.

Kind regards

robert


#7

On Tue, Apr 21, 2009 at 11:40 PM, Robert K.
removed_email_address@domain.invalid wrote:

data. If you have classes already for storing all this, I’d probably write

or alternatively

def method_missing(sym,*args,&blk)
self[sym] || self[sym.to_s] || super
the following line will work for all potential hash values

fetch( sym ) { fetch( sym.to_s ) { super } }

HTH
Robert


#8

Robert K. wrote:

If I understand the rest of your post correctly it is not exactly the
same structure but rather similarly structured data in two different
data structures (custom classes and XML DOM).

True, but it is a 3rd party object library that I don’t have access to
other than through OLE calls or file storage.

The fix that I propose is to not have two data structures storing the
same data. If you have classes already for storing all this, I’d
probably write bit of code that builds the structure using an XML push
or pull parser.

I wish that were the case, but 3rd party non-GPL’d code. :frowning:

Usually nil is returned for absent keys so you can do

I was attempting to avoid the case when it isn’t…

def method_missing(sym,*args,&blk)
self[sym] || self[sym.to_s] || super
end

… this will trigger the creation and return of a memoized object,
which is precisely a side-effect I want to avoid.

For example:

test=Hash.new {|k,v| k[v]=v.to_s*3} => {}
test[:one] => “oneoneone”
test[:two] => “twotwotwo”
test.three
NoMethodError: undefined method `three’ for {:one=>“oneoneone”,
:two=>“twotwotwo”}:Hash
from (irb):15

cool and desired

class Hash
def method_missing(sym,*args,&blk)
return self[sym] if self.key?(sym)
return self[sym.to_s] if self.key?(sym.to_s)
super
end
end
=> nil

test.one => “oneoneone”
test.two => “twotwotwo”
test.three
NoMethodError: undefined method three' for {:one=>"oneoneone", :two=>"twotwotwo"}:Hash from (irb):20:inmethod_missing’
from (irb):25

#Same error message also cool and desired

test[:three] => “threethreethree”
test.three => “threethreethree”

#Redefine with your more compact code, which isn’t performing the key?
check

class Hash
def method_missing(sym,*args,&blk)
self[sym] || self[sym.to_s] || super
end
end
=> nil

test.four => “fourfourfour”

#Not my expected result. I would expect an error from that method call,
but thanks for the effort.

Mac
http://pqmf.com


#9

Robert D. wrote:

On Tue, Apr 21, 2009 at 11:40 PM, Robert K.
removed_email_address@domain.invalid wrote:

data. �If you have classes already for storing all this, I’d probably write

or alternatively

def method_missing(sym,*args,&blk)
�self[sym] || self[sym.to_s] || super
the following line will work for all potential hash values

fetch( sym ) { fetch( sym.to_s ) { super } }

HTH
Robert

Cool and will generate the same error type, I’ve used a modification of
it to make the stack look the same, and give a debugging hint if it does
go wrong…

begin
fetch( sym ) { fetch( sym.to_s ) { super }}
rescue
raise NoMethodError,“undefined method #{sym} for
#{self.inspect}:#{self.class} overridden by Mac’s redefinition of
Hash::method_missing in #{FILE}”
end

Thanks again,

Mac


#10

On 22.04.2009 00:26, Paul M. wrote:

Robert K. wrote:

If I understand the rest of your post correctly it is not exactly the
same structure but rather similarly structured data in two different
data structures (custom classes and XML DOM).

True, but it is a 3rd party object library that I don’t have access to
other than through OLE calls or file storage.

I see. But: if file storage means XML then you can still create a data
structure which responds to the same set of methods as the OLE version.

An alternative approach to modifying Hash is to wrap the whole beast in
something that exhibits the same interface as the OLE version.

require ‘delegate’

class HashWrap < SimpleDelegator
def method_missing(s,*a,&b)
key = s.to_s

 if a.empty? and __getobj__.key? key
   res = __getobj__[key]
   res = self.class.new(res) if Hash === res
   res
 else
   super
 end

end
end

h = {“foo” => {“x”=>456}, “bar” => 2}
s = HashWrap.new h

p s.foo
p s.foo.x
p s.bar
p s.not_there

Cheers

robert


#11

On Wed, Apr 22, 2009 at 12:57 AM, Paul M. removed_email_address@domain.invalid
wrote:

  fetch( sym ) { fetch( sym.to_s ) { super } }
 fetch( sym ) { fetch( sym.to_s ) { super }}
rescue
 raise NoMethodError,“undefined method #{sym} for
#{self.inspect}:#{self.class} overridden by Mac’s redefinition of
Hash::method_missing in #{FILE}”
end

Hmm unless I am missing something here there is no need to raise
NoMethodError with super just to
rescue it immediately
What about

def method_missing sym
fetch( sym ) do
fetch( sym.to_s) do
raise NoMethodError,“undefined method #{sym} for
#{self.inspect}:#{self.class} overridden by Mac’s redefinition
of
Hash::method_missing in #{FILE}”
end
end
end

Robert

Thanks again,

Mac

Posted via http://www.ruby-forum.com/.


Si tu veux construire un bateau …
Ne rassemble pas des hommes pour aller chercher du bois, préparer des
outils, répartir les tâches, alléger le travail… mais enseigne aux
gens la nostalgie de l’infini de la mer.

If you want to build a ship, don’t herd people together to collect
wood and don’t assign them tasks and work, but rather teach them to
long for the endless immensity of the sea.