Is there a better way to do this?

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

···

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

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

Just my 0.02$

Cheers
Robert

···

On Tue, Apr 21, 2009 at 9:01 PM, Paul Mckibbin <pmckibbin@gmail.com> wrote:

class Hash
def method_missing(sym,*args,&blk)

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

--
Antoine de Saint-Exupéry

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.

···

Paul Mckibbin <pmckibbin@gmail.com> 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.
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.

--
matt neuburg, phd = matt@tidbits.com, Matt Neuburg’s Home Page
Leopard - http://www.takecontrolbooks.com/leopard-customizing.html
AppleScript - http://www.amazon.com/gp/product/0596102119
Read TidBITS! It's free and smart. http://www.tidbits.com

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

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

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

···

On 21.04.2009 21:01, Paul Mckibbin wrote:

Robert Dober 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

···

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

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.

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

the following line will work for all potential hash values

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

HTH
Robert

···

On Tue, Apr 21, 2009 at 11:40 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

On 21.04.2009 21:01, Paul Mckibbin 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.

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

Usually nil is returned for absent keys so you can do

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

Robert Klemme 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: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

···

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

Robert Dober 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

···

On Tue, Apr 21, 2009 at 11:40 PM, Robert Klemme > <shortcutter@googlemail.com> wrote:

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

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

···

On 22.04.2009 00:26, Paul Mckibbin wrote:

Robert Klemme 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.

Robert Dober 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

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

···

On Wed, Apr 22, 2009 at 12:57 AM, Paul Mckibbin <pmckibbin@gmail.com> wrote:

On Tue, Apr 21, 2009 at 11:40 PM, Robert Klemme >> <shortcutter@googlemail.com> wrote:

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.

--
Antoine de Saint-Exupéry