I have a class game
which contains some arrays of custom objects (dinosaurs, cacemen etc.), that are returned by different accessors, such as game.dinosaurs
, game.cavemen
etc.
At present, all these accessors just return the internally stored arrays. But now I'd like to add some custom iteration methods to these arrays returned by those accessors, to be able to write code such as game.dinosaurs.each_carnivore { ... }
etc. similarly to each_element
and each_attr
iterators in LibXML::XML::Node
. But the objects returned from my accessors game.dinosaurs
and game.cavemen
have to behave like arrays still.
How are things like that usually done in Ruby?
Should I make the objects returned from my accessors to be some custom classes derived from Ruby's Array
class? Or maybe should I just create a custom class with Enumerable
mixed in?
I know I can use map
or select
externally on my collections, but I wanted to encapsulate these iterations internally that my class's users won't need to bother how to set up an iteration to select only carnivore dinosaurs from the internal array.
Edit: I'm not asking about how to use iterators or how to implement them, but how to add just some custom iterators to object which previously were just plain arrays (and still need to be).
It depends (as always). You could use an array subclass and you you could build a custom class and use composition and delegation. Here's a simple example with an array subclass:
class DinosaurArray < Array
def carnivores
select { |dinosaur| dinosaur.type == :carnivore }
end
def herbivores
select { |dinosaur| dinosaur.type == :herbivore }
end
def each_carnivore(&block)
carnivores.each(&block)
end
def each_herbivore(&block)
herbivores.each(&block)
end
end
And here's a simple one with composition and delegation:
class DinosaurArray
def initialize
@array = []
end
def <<(dinosaur)
@array << dinosaur
end
def carnivores
@array.select { |dinosaur| dinosaur.type == :carnivore }
end
def herbivores
@array.select { |dinosaur| dinosaur.type == :herbivore }
end
def each(&block)
@array.each(&block)
end
def each_carnivore(&block)
carnivores.each(&block)
end
def each_herbivore(&block)
herbivores.each(&block)
end
end
Both implementation can be used like this:
require 'ostruct'
dinosaurs = DinosaurArray.new
dinosaurs << OpenStruct.new(type: :carnivore, name: "Tyrannosaurus")
dinosaurs << OpenStruct.new(type: :carnivore, name: "Allosaurus")
dinosaurs << OpenStruct.new(type: :herbivore, name: "Apatosaurus")
puts "Dinosaurs:"
dinosaurs.each.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts
But also has custom iterators:
puts "Carnivores:"
dinosaurs.each_carnivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts
puts "Herbivores:"
dinosaurs.each_herbivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
Output:
Dinosaurs:
1. Tyrannosaurus
2. Allosaurus
3. Apatosaurus
Carnivores:
1. Tyrannosaurus
2. Allosaurus
Herbivores:
1. Apatosaurus