2

I need to check if a variable is an array, and if not convert it into one before proceed with further processing. So, my code looks like this:

class Test < Struct.new(:args)
    def eval
        p "1. #{args}"
        args = (args.instance_of? Array) ? args : [args]
        p "2. #{args}" # woah! [nil]?
        # ...other things, with "args" being an array for sure..or not?!?
    end
end

I am quite new to ruby, so perhaps this is not very idiomatic, but to me looks like this code should at least work. Instead, the second time I print the args variable, it is [nil]. Notice that if I change the method eval slightly:

def eval
    p "1. #{args}"
    a = args
    args = (a.instance_of? Array) ? a : [a]
    p "2. #{args}"
end

everything works as expected. So, is there something very specific to the Struct class that I don't get it, or something fishy is going on here? (using ruby 1.9.3-dev on macosx, using rvm)

cheng81
  • 2,434
  • 2
  • 21
  • 18

1 Answers1

2

Actually there's a Ruby idiom for what you are trying to do: [*args]. * in this context is called the splat operator:

http://raflabs.com/blogs/silence-is-foo/2010/08/07/ruby-idioms-what-is-the-splatunary-operator-useful-for/

If you get passed an array, splat will "flatten" the array into the new one, if you pass a single argument, it will become a one element array.

For the odd behavior: it looks to me like you create a local variable args in your eval method, which gets initialized to nil because it's on the LHS of an assignment. Then the ternary evaluates to false because args is not an array and makes an array of the current value, which is still nil. If args would be an instance variable (@args), things would work the way you expect. In other words, while inheriting from the Struct will give you args and args= methods, it won't give you an @args instance variable.

Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
  • thanks, I'll try the splat operator. Anyway, it's still not clear to me what's going on.. the `args` variable is already instantiated, since I can print it without any problem, and I can reassign it to a local variable (the `a` in the working code). Moreover, I tried using the same code in a top level function, and it works as expected. My suspect is that "args" is defined as some accessors methods that, well, does something that I don't expect :) – cheng81 Aug 17 '11 at 08:58
  • Yes, `args` is an accessor method (as mentioned in the last sentence of my post), so it works inside `p "1. #{args}"`. But then in the next line you define a local variable and when Ruby tries to resolve barewords, it first tries to resolve them as a local variable, not as a method call. – Michael Kohl Aug 17 '11 at 09:01
  • yep, I got confused by the accessor method that looks like a variable. So that means that, once defined, a property in a struct is unmodifiable? Anyway, I think I resolved by overloading the constructor and simply calling `super([*args])`. Thanks for the lighting-speed answers, btw :) – cheng81 Aug 17 '11 at 09:11
  • It's not immutable, you have `args=`. Your problem was that you "shadowed" the accessor with a local variable. Anyway, glad you got it working :-) – Michael Kohl Aug 17 '11 at 09:23