Is there a better way to do this?

P

Paul Mckibbin

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

[email protected]

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
 
R

Robert Dober

=A0class Hash
=A0 =A0def 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


--=20
Si tu veux construire un bateau ...
Ne rassemble pas des hommes pour aller chercher du bois, pr=E9parer des
outils, r=E9partir les t=E2ches, all=E9ger le travail=85 mais enseigne aux
gens la nostalgie de l=92infini de la mer.

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

matt neuburg

Paul Mckibbin said:
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

[email protected]

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.

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.
 
P

Paul Mckibbin

Robert said:
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
 
P

Paul Mckibbin

matt said:
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
 
R

Robert Klemme

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.
i.e. in one case I had

[email protected]

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

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
 
R

Robert Dober

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 sam= e
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 sam= e
data. =A0If you have classes already for storing all this, I'd probably w= rite
bit of code that builds the structure using an XML push or pull parser.
i.e. in one case I had

[email protected]

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:

=A0class Hash
=A0 =A0def method_missing(sym,*args,&blk)
=A0 =A0 return self[sym] if self.keys.include?(sym)
=A0 =A0 return self[sym.to_s] if self.keys.include?(sym.to_s)
=A0 =A0 super
=A0 =A0end
=A0end

Usually nil is returned for absent keys so you can do

def method_missing(sym,*args,&blk)
=A0self[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
 
P

Paul Mckibbin

Robert said:
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. :(
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[:eek:ne] => "oneoneone"
test[:two] => "twotwotwo"
test.three
NoMethodError: undefined method `three' for {:eek:ne=>"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 {:eek:ne=>"oneoneone",
:two=>"twotwotwo"}:Hash
from (irb):20:in `method_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
 
P

Paul Mckibbin

Robert said:
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
 
R

Robert Klemme

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
 
R

Robert Dober

Robert said:
data. =EF=BF=BDIf you have classes already for storing all this, I'd pr= obably write
or alternatively

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

=C2=A0 =C2=A0 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
=C2=A0fetch( sym ) { fetch( sym.to_s ) { super }}
rescue
=C2=A0raise 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



--=20
Si tu veux construire un bateau ...
Ne rassemble pas des hommes pour aller chercher du bois, pr=C3=A9parer des
outils, r=C3=A9partir les t=C3=A2ches, all=C3=A9ger le travail=E2=80=A6 mai=
s enseigne aux
gens la nostalgie de l=E2=80=99infini de la mer.

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

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top