I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
@list = list
end
def [](index)
@list[index]
end
def each(...)
@list.each(...)
end
def reverse
Peeps.new(@list.reverse)
end
def last
@list.last
end
def join(...)
@list.join(...)
end
def from_csv(csv_table)
@list = []
csv_table.each { |row| @list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
@list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(@list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each
method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size
. I would also suggest not overriding the behavior of select
as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset
provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :@list, :each, :size
def initialize(list = [])
@list = list
end
def subset(&block)
selected = @list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2