Rails 3.1 hash each gives TypeError

2019-06-12 02:52发布

问题:

I have hash(Hash.from_xml) which looks like this (from inspect):

{
"FileName"=>"hofplayers.xml",
"Version"=>"1.0",
"UserID"=>"3955847", 
"FetchedDate"=>"2011-08-16 00:41:02", 
"PlayerList"=>{
    "Player"=>{
        "PlayerId"=>"92121587", 
        "FirstName"=>"Gennady", 
        "NickName"=>nil, 
        "LastName"=>"Buzykin", 
        "Age"=>"45", 
        "NextBirthday"=>"2011-09-24 22:10:00", 
        "ArrivalDate"=>"2008-03-08 16:37:00", 
        "ExpertType"=>"15", 
        "HofDate"=>"2010-01-23 16:10:00", 
        "HofAge"=>"40"
        }
    }
}

Then I'm iterating with each as there can be more then one player:

<% @hof['PlayerList']['Player'].each do |player| %>
    <%= player['NickName']%>
<% end %>

And it fails with TypeError: can't convert String into Integer. But it works like I want when there is more than one player. The problem seems to be that when there is single player each makes arrays instead of hash, player.inspect gives:

 ["PlayerId", "92121587"]
 ["FirstName", "Gennady"]
 ["NickName", nil]
 ["LastName", "Buzykin"]
 ["Age", "45"]
 ["NextBirthday", "2011-09-24 22:10:00"]
 ["ArrivalDate", "2008-03-08 16:37:00"]
 ["ExpertType", "15"]
 ["HofDate", "2010-01-23 16:10:00"]
 ["HofAge", "40"]

Instead of

 {
     "PlayerId"=>"25787535", 
     "FirstName"=>"Rico", 
     "NickName"=>nil, 
     "LastName"=>"van Oostveen", 
     "Age"=>"42", 
     "NextBirthday"=>"2011-10-23 22:18:00", 
     "ArrivalDate"=>"2006-02-11 18:43:00", 
     "ExpertType"=>"2", 
     "HofDate"=>"2010-04-25 22:01:00", 
     "HofAge"=>"38"
 }

So what I'm doing wrong?

回答1:

When you do each on a Hash, you get a list of key/value pairs, then you try to index those pairs with a string and that gives you your error. There's not much you can do to change what from_xml does but you can work around it.

You could patch the Hash in your controller:

if(@hof['PlayerList']['Player'].is_a?(Hash))
    @hof['PlayerList']['Player'] = [ @hof['PlayerList']['Player'] ]
end
# or
if(!@hof['PlayerList']['Player'].is_a?(Array))
    @hof['PlayerList']['Player'] = [ @hof['PlayerList']['Player'] ]
end

If you don't have what you're expecting, you'd just make it an array with one element; which test you choose depends on what sorts of things you're expecting to get from from_xml but I'd lean towards the "not array" check since that exactly matches what you're trying to do.

Or you could try sorting it out in your view:

<% [ @hof['PlayerList']['Player'] ].flatten(1).each do |player| %>

That basically wraps whatever you have in an array and then flattens it one level, the flatten(1) call is needed in case you already have an array. If you start off with an array then the [ ].flatten(1) is a no-op (the flatten call will undo what [] does) that won't have any noticeable effect other than a bit of wasted CPU time; if you start with a hash, then [ ... ] will turn it into an array and flatten won't do anything (but waste a bit of CPU).



回答2:

one thing that comes to mind is that this:

<% @hof['PlayerList']['Player'].each do |player| %>
  <%= player['NickName']%>
<% end %>

does not loop through the players. It loops through the name/value pairs of the 'Player' hash. EDIT. By this I mean that it will loop through key1, value1, key2, value2, key3,...etc. One for each iteration of the loop...

I would think you'd want something like this:

<% @hof['PlayerList'].each_pair do |player, player_data| %>
  <%= player_data['NickName']%>
<% end %>