tags:

views:

100

answers:

5

I'm using my MOO project to teach myself Test Driven Design, and it's taking me interesting places. For example, I wrote a test that said an attribute on a particular object should always return an array, so --

t = Thing.new("test")
p t.names  #-> ["test"]

t.names = nil
p t.names #-> []

The code I have for this is okay, but it doesn't seem terribly ruby to me:

class Thing

   def initialize(names)
      self.names = names
   end

   def names=(n)
      n = [] if n.nil?
      n = [n] unless n.instance_of?(Array)

      @names = n
   end

   attr_reader :names
end

Is there a more elegant, Ruby-ish way of doing this?
(NB: if anyone wants to tell me why this is a dumb test to write, that would be interesting too...)

A: 

Use the * to fetch all params in an Array in your initialize method


class Thing
   attr_accessor :names

   def initialize(*names)
      @names = names
   end
end

t = Thing.new("test")
p t.names  #-> ["test"]

t.names = nil
p t.names #-> nil

shingara
The last execution returns `nil`, not `[]`
Simone Carletti
exact your right I propose another solution
shingara
+2  A: 

You can use the getter method to format the value.

class Thing

  def initialize(names)
    self.names = names
  end

  def names
    [*@names].compact
  end

  def names=(values)
    @names = values
  end

end

t = Thing.new("test")
p t.names  #-> ["test"]

t.names = nil
p t.names #-> []
Simone Carletti
`[*@names].compact` is really nice. Seems to me it's more readable than `[*(n||[])]`. Plus, You're right: I should only be concerned about what the public method returns, not how it stores data.
Shadowfirebird
+1  A: 

In other smell like my previous answers :


class Thing
  def initialize(*names)                                                                                                                                                                                                                                                      
    @names = names
  end 
  def names
    @names || []
  end 
  def names=(*names)
    @names = names
  end 
end

t = Thing.new("test")
p t.names  #-> ["test"]

t.names = nil 
p t.names #-> []
shingara
+1, Your code after `t.names=nil` will return `[nil]` for `t.names`. You have to add a compact to `names` method and remove the `||` as `@names` will never be `nil`.
KandadaBoggu
+3  A: 

Try this:

class Thing

   def initialize(names)
      self.names = names
   end

   def names=(n)
     @names= [*(n||[])]
   end

   attr_reader :names
end

Let us test the class:

t = Thing.new("car")
t.names  #-> ["test"]

t.names = nil
t.names  #-> []

t.names = [1, 2, 3]
t.names #-> [1, 2, 3]
KandadaBoggu
I think new version is efficient and it looks good too :-)
KandadaBoggu
+3  A: 

I'd like to point out that there is already a builtin method to do what you want! It's called Array(). The question to ask yourself is: what happens to classes that are convertible to arrays (like 0..42)?

I feel that most Rubyist would expect that they'd be converted. So:

class Thing
  attr_accessor :names

  def initialize(names)
    self.names = names
  end

  def names=(values)
    @names=Array(values)
  end
end

You will get the same results, for example:

t = Thing.new("car")
t.names  #-> ["car"]

t.names = nil
t.names  #-> []

t.names = 42
t.names  #-> [42]

t.names = [1, 2, 3]
t.names #-> [1, 2, 3]

t.names = 1..3
t.names #-> [1, 2, 3]  # Is this what you want, or not?
Marc-André Lafortune
In fact I'm not sure it matters -- although of course I won't know for sure until I write all the code! -- but clearly this is the most elegant solution. I've transferred the tick to you. Sorry, everyone.
Shadowfirebird
+1, For making me read the `Kernel.Array' documentation. (http://www.ruby-doc.org/core/classes/Kernel.html#M005967)
KandadaBoggu