tags:

views:

175

answers:

4

I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this

 def <<(val)
    @result_array << val
 end

This feels very c-like and I know Ruby has better way.

I'd also like to be able to do this

 Results.each do |result|   
    result.do_stuff   
 end

but am not sure what the each method is really doing under the hood.

Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.

Any help would be appreciated.

+3  A: 

This feels very c-like and I know Ruby has better way.

If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.

but am not sure what the each method is really doing under the hood.

The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:

def each
  0.upto(@result_array.length - 1) do |x|
    yield @result_array[x]
  end
end
+2  A: 

each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.

class Result
    include Enumerable

    def initialize
        @results_array = []
    end

    def <<(val)
        @results_array << val
    end

    def each(&block)
        @results_array.each(&block)
    end
end

r = Result.new

r << 1
r << 2

r.each { |v|
   p v
}

#print:
# 1
# 2

Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.

BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.

vava
Doesn't actually explain an implementation of the 'each' method though, and taht was the question.
Ed Swangren
@Ed: Sure it does. You have the each method do whatever is necessary to iterate over the object's elements, which depends on the class's actual implementation. In this case, the obvious choice is to have `each` iterate over the array's elements.
Chuck
Seems like a simple wrapper method to me.
Ed Swangren
Yes, it is a simple wrapper, that's aggregation pattern, which is preferred over inheritance whenever possible.
vava
I don't think there is an "aggregation pattern", or at least not one that's commonly known. I think you want inheritance vs composition: http://stackoverflow.com/questions/760473/inheritance-vs-composition-for-testability
klochner
+2  A: 

If you create a class Results that inherit from Array, you will inherit all the functionality.

You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.

For example:

class Results < Array
  # Additional functionality
  def best
    find {|result| result.is_really_good? }
  end

  # Array functionality that needs change
  def compact
    delete(ininteresting_result)
    super
  end
end

Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:

require 'forwardable'
class Results
  extend Forwardable
  def_delegator :@result_array, :<<, :each, :concat # etc...

  def best
    @result_array.find {|result| result.is_really_good? }
  end

  # Array functionality that needs change
  def compact
    @result_array.delete(ininteresting_result)
    @result_array.compact
    self
  end
end

In both of these forms, you can use it as you want:

r = Results.new
r << some_result
r.each do |result|
  # ...
end
r.compact
puts "Best result: #{r.best}"
Marc-André Lafortune
+5  A: 

For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.

class Results
  include Enumerable, Forwardable
  def_delegators :@result_array, :each, :<<
end

This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.

Chuck
I knew there should something like that in ruby library. Now, if only I could star this answer to find it later...
vava
You need to `extend Forwardable`, not `include` it.
Andrew Grimm