Forum: Ruby Defining a method to select values from a hash.

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Danny T. (Guest)
on 2009-05-29 04:29
I'm terribly confused with this, and can't seem to figure it out (or
explain it).

Using HTTParty, I have a method that grabs some XML and returns it as a
hash:

  def check(data)
    @results = self.class.post('/checkDocument', :query => {:key =>
@key,
                                                :data => data})
  end

  def errors
    errors = @results['results']['error']
  end

An example of the hash it returns:
>> atd.check("I'm realy hating this wether")
=> [{"suggestions"=>{"option"=>["really", "ready", "real", "relay",
"realty"]}, "precontext"=>"I'm", "type"=>"spelling", "string"=>"realy",
"description"=>"Spelling"}, {"suggestions"=>{"option"=>["weather",
"whether"]},
"url"=>"http://service.afterthedeadline.com/info.slp?text=...,
"precontext"=>"this", "type"=>"spelling", "string"=>"wether",
"description"=>"Did you mean..."}]

Now calling the .errors method, I can get the string from each one like
this:
>> atd.errors.each { |error| p error['string'] }
"realy"
"wether"

What I'd like to do is write a method for string, so that
atd.errors.each { |error| p.error.string } would result in the same
thing.  For the life of me, I can't figure out how to do that. :/

The other problem I'm having with the way I'm doing it now is that
calling .errors.each when there's only one error sent back gives me a
"can't convert String into Integer" error.

So, if anyone could point me in the right direction, I'd appreciate it.
:)
Brian C. (Guest)
on 2009-05-29 12:35
You want foo['bar'] and foo.bar to be the same thing, like in
Javascript? In that case you need to modify Hash, not String. Below is
some code which does that.

---- 8< ----
# Extend a Hash with this module to get semantics of a Javascript
object:
# me.foo is the same as me['foo']
module JSObjectMixin
  def method_missing(meth,*rest,&blk)
    key = meth.to_s
    if key[-1] == ?=
      self[key[0..-2]] = rest.first
    else
      self[key]
    end
  end
end

def extend_jsobject(tree)
  case tree
  when Hash
    tree.extend JSObjectMixin
    tree.each do |k,v|
      extend_jsobject(v)
    end
  when Array
    tree.each do |v|
      extend_jsobject(v)
    end
  end
end

errors = [{"suggestions"=>{"option"=>["really", "ready", "real",
"relay",
  "realty"]}, "precontext"=>"I'm", "type"=>"spelling",
"string"=>"realy",
  "description"=>"Spelling"}, {"suggestions"=>{"option"=>["weather",
  "whether"]},
  "url"=>"http://service.afterthedeadline.com/info.slp?text=...,
  "precontext"=>"this", "type"=>"spelling", "string"=>"wether",
  "description"=>"Did you mean..."}]
extend_jsobject(errors)

errors.each { |error| p error['string'] }
errors.each { |error| p error.string }
---- 8< ----

Another option would be to walk your results structure and replace each
Hash with an OpenStruct (see ostruct.rb in the standard library).
However, that would _only_ allow error.string, not error['string']

> The other problem I'm having with the way I'm doing it now is that
> calling .errors.each when there's only one error sent back gives me a
> "can't convert String into Integer" error.

Simple solution:

   Array(atd.errors).each { ... }

If atd.errors is already an Array this does nothing; if it isn't, then
it is wrapped in a one-element array.

Better solution: If this is using XML::Simple internally, there's an
option you can give it to return arrays always, even when there's only a
single instance (or I believe you can tell it to do this for specific
tags). I can't remember offhand what that option is, but you should be
able to find it. Then see if the library you're using allows that option
to be passed through.

HTH,

Brian.
This topic is locked and can not be replied to.