4

I am creating an object that acts like an array, but needs to do some data initialization before items can be added to the array.

class People
  def initialize(data_array)
    @people_array = data_array.map {|data| Person.new(data) }
  end

  def <<(data)
    @people_array << Person.new(data)
  end

  # def map(&block) ...
  # def each(&block) ...
  # etc...
end

I would like to support all the same methods that the array supports and delegate to those methods so that I don't have to rewrite all of them. What is the simplest/cleanest way to achieve this?

2
  • 2
    People#flatten, People#freeze, People#slice, People#fill, and People#pack are quite interesting. Commented Aug 6, 2015 at 17:32
  • LOL, I just used People as an example. It's called something else in my app. But that's funny. =] Commented Aug 6, 2015 at 18:12

3 Answers 3

3

Maybe inheriting the Array class is a good solution:

class People < Array
  def initialize(data_array)
    super(data_array.map {|data| Person.new(data) })
  end

  def <<(item)
    super(Person.new(item))
  end
end

However, it has some disadvantages as mentioned in the comments and in axel's answer.

An alternative solution is composition and mixins:

class People
  include Enumerable
  
  def initialize(data_array)
    @collection = data_array.map {|data| Person.new(data) }
  end

  def each(&block)
    @collection.each(&block)
  end
  
  def <<(data)
    @collection << Person.new(data)
  end
end

However, this solution only gives you methods provided by the Enumerable module, along with the << operator.

To see what methods the Array itself defines, easily do:

(Array.instance_methods - (Enumerable.instance_methods + Object.instance_methods)).sort
 => [:&, :*, :+, :-, :<<, :[], :[]=, :assoc, :at, :bsearch, :clear, :collect!,
 :combination, :compact, :compact!, :concat, :delete, :delete_at, :delete_if, :each,
 :each_index, :empty?, :fetch, :fill, :flatten, :flatten!, :index, :insert, :join,
 :keep_if, :last, :length, :map!, :pack, :permutation, :pop, :product, :push, :rassoc,
 :reject!, :repeated_combination, :repeated_permutation, :replace, :reverse, :reverse!,
 :rindex, :rotate, :rotate!, :sample, :select!, :shift, :shuffle, :shuffle!, :size, :slice,
 :slice!, :sort!, :sort_by!, :to_ary, :transpose, :uniq, :uniq!, :unshift, :values_at, :|]

However I would consider this a dirty solution, and would encourage going with Axel's answer.

Sign up to request clarification or add additional context in comments.

5 Comments

Sadly inheriting from core classes never is. You might end up with an instance of Array instead of People.
@cremno how can that happen if you are initializing an instance of People?
@Stefan: No, I think that works as expected. Try #&, #+, etc. and who knows what else.
peeps = People.new([0, 1]); peeps += People.new([2, 3]); p peeps.class
@cremno well, you have to be aware that these methods return new Array instances, even if both objects are instances of Array subclasses.
1

You can use a Delegator for that

class SimpleDelegator < Delegator
  def initialize(obj)
    super                  # pass obj to Delegator constructor, required
    @delegate_sd_obj = obj # store obj for future use
  end
end

I would discourage from extending Array.

2 Comments

using SimpleDelegator as defined in ruby is easier this would just be class People < SimpleDelegator; end and would function fine. Then the OP can just overwrite the << method as def <<(data); push(Person.new(data)); end
Note that a delegator doesn't solve the problems that were discussed in the other answer. Person.new([0,1]) + Person.new([2,3]) will return an instance of Array.
1

Of course, you can always meta-hack it

 class People
      def model
        @arr ||= []
      end

      def method_missing(method_sym, *arguments, &block)
        define_method method_sym do |*arguments|
          model.send method_sym, *arguments
        end
        send(method_sym, *arguments, &block)      
      end

      #probably should implement respond_to? here
end

I like this approach a little better because you aren't confusing implementing against Array versus extending Array. You can build a custom matcher to only implement particular array methods for example that are available for your People class.

4 Comments

This is basically how Delegator works
My understanding is that Delegator is all or nothing. The advantage of explicitly exposing methods via method_missing + send is that you can restrict what methods get sent via a matcher, which reduces bloat.
I like the direction of this answer, but without method_missing, instead do the meta definitions at initialization based on the inspecting Array's instance methods. Also I'm not sure any of the Delegators will do the trick of making sure the return values of the Array instance methods that return the array instance return the instance of People instead.
Gabe, you can do it directly, or by keeping some sort of filter in place using something like a matcher that evaluates whether the method is in a list of methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.