tags:

views:

134

answers:

9

I have an each method that is run on some user-submitted data.

Sometimes it will be an array, other times it won't be.

Example submission:

<numbers>
    <number>12345</number>
</numbers>

Another example:

<numbers>
    <number>12345</number>
    <number>09876</number>
</numbers>

I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.

+3  A: 

A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.

irb(main):002:0> def foo x
irb(main):003:1>    if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
Mark Rushakoff
+1  A: 

I sometimes use this cheap little trick:

[might_be_an_array].flatten.each { |x| .... }
jeem
+2  A: 

It looks like the problem you want to solve is not the problem you are having.

TypeError (Symbol as array index)

That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.

Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.

In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.

def foo(obj)
  obj = [obj] unless obj.is_a?(Array)
  do_something_with(obj)
end

Or something a bit cleaner but more cryptic

def foo(obj)
  obj = [*obj]
  do_something_with(obj)
end

This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.

Squeegy
I really like the [splat] thing - once you've learned it you're more likely to remember splats and apply them in other cool ways.
Mike Woodhouse
A: 

Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:

foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);

What are you trying to do with each number?

Coderer
A: 

You should try to avoid using respond_to? message as it is not a very object oriented aproach. Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array. Maybe it is a complex task, but I would try to do this in order to get a better OO design.

Koder_
+7  A: 

I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.

p Array([1,2,3]) #-> [1,2,3]
p Array(123)     #-> [123]

Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.

Shadowfirebird
+1. I'm no Ruby expert, but I believe this is the most proper way to coerce to an array. (Maybe next time I'll think of it before you :P)
Mark Rushakoff
It seems rather unfair that I am getting so many upvotes for an answer which was so recently given me by Marc-Andre LaFortune (http://stackoverflow.com/users/8279/marc-andre-lafortune) -- credit where credit is due...
Shadowfirebird
A: 

If your input is x, use x.to_a to convert your input into an array.

[1,2,3].to_a
  => [1, 2, 3]
1.to_a
  => [1]
"sample string".to_a
  => ["sample string"]

Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.

bta
I like this answer better than mine, except... Ruby 1.8.7 says `warning: default `to_a' will be obsolete` and 1.9.1 says `NoMethodError: undefined method `to_a' for 1:Fixnum`.
Mark Rushakoff
@Mark Rushakoff: Thanks for catching that, I have updated my answer with more information.
bta
A: 

I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.

Have you tried casting it?

Cam
+1  A: 

Use the splat operator:

[*1]     # => [1]
[*[1,2]] # => [1,2]
severin