Building arrays of similar objects in Ruby

I often find myself having to build arrays of similar objects in my tests. For example:

def create_user
  :some_user
end

users = [create_user, create_user, create_user]

It seems wasteful to repeat the same method call three times. One solution is to use the #times method to append to an array, like this:

users = []
3.times { users << create_user }

This just doesn't seem very elegant, though. It feels like I should be able to collect the results from each method call with a single line of code. I experimented with the multiplication operator to see if it might be able to do what I wanted, but didn't get far.

Here's one possible solution I came up with:

class Integer
  def of
    result = []
    times { result << yield }
    result
  end
end

users = 3.of { create_user }
# returns [:some_user, :some_user, :some_user]

The #of method can now be called on an integer. It uses #times to call the given block three times and appends the result to an array which is then returned. Much more elegant. Another way to achieve this on a single line is:

users = (0..2).to_a.collect { create_user }

This doesn't feel as nice to me. It's visually complex and, at first glance, the zero-basing of the range hides the fact that we're collecting three items, not two. The benefit is that no reopening of the Integer class is required.

How would you solve this problem?

8 thoughts on “Building arrays of similar objects in Ruby

  1. Hi Matt. In the style of Python, I can think of two ways to do this:

    users = (0..2).map { create_user }

    and

    users = Array.new(3).map! { create_user }

  2. One other thought; I just noticed your comment regarding the zero-basing of the range. Incorporating that with my first suggestion yields:

    users = (1..3).map { create_user }

  3. One more. 🙂 Perusing the documentation for Array.new, I see that it accepts a block. So we can also trim my second example as follows:

    users = Array.new(3) { create_user }

  4. hongli’s solution doesn’t accomplish what Matt intends. Matt wants to create multiple distinct objects, whereas hongli’s solution creates multiple duplicate copies of a single object.

  5. Just for the sake of being different:
    users = ([method(:create_user)]*3).map(&:call)
    But seriously, for three items, I’d stick with:
    users = [create_user, create_user, create_user]
    Otherwise, I think Array.new with a block is the clearest.

Comments are closed.