General rule of thumb is if you have a collection of objects, you should build custom iterators so that users could readily use them like they do with other collection such as Array, Hash, File, etc.
The easiest way to do this is to mix-in the Enumerable module. The only requirement is to define an each method in your class which will provide Enumerable with your collection objects.
Let’s take a look at an example. Let’s say you’re a property owner with a few properties in various cities.
class Property
attr_reader :address, :sq_ft, :num_of_rooms
def initialize(address, sq_ft, num_of_rooms)
@address = address
@sq_ft = sq_ft
@num_of_rooms = num_of_rooms
end
def get_info
puts "Property is located at #{address} and is #{sq_fit} sq/ft with #{num_of_rooms} rooms"
end
end
prop1 = Property.new("123 Westington St.", 1000, 2)
prop2 = Property.new("322 Popular St.", 2000, 3)
prop3 = Property.new("932 Horray St.", 1000, 7)
prop4 = Property.new("213 Hello Hat St.", 9000, 9)
prop5 = Property.new("858 Bacon St.", 3000, 3)
prop6 = Property.new("391 Blahestington St.", 10000, 3)
class PropertyList
attr_reader :name, :property_list
def initialize(name, property_list=[])
@name = name
@property_list = property_list
end
def add_property(p)
self.property_list << p
end
end
la_props = PropertyList.new("Los Angeles")
la_props.add_property(prop2)
la_props.add_property(prop4)
la_props.add_property(prop6)
Now, what if we want to iterate through the addresses in LA?
la_props.each { |p| puts p.address }
# undefined method `each' for #<PropertyList:0x007fdbac0ab998> (NoMethodError)
So how do we implement an iterator, much like we’ve seen with other collections such as Arrays and Hashes? We define an each method in our PropertyList class.
def each
self.property_list.each { |p| yield p }
end
# Output:
# 322 Popular St.
# 213 Hello Hat St.
# 391 Blahestington St.
Now what if we want to get the total square footage of all the properties in LA that have at more than 3 rooms? Naturally we think of the select and reduce methods, but those belong to the Enumerable module.
la_sq_ft = la_props.select { |s| s.num_of_rooms > 3 }.map { |s| s.sq_ft }.reduce(0, :+)
puts "Total SQ/FT for LA Properties: #{la_sq_ft}"
# private method `select' called for #<PropertyList:0x007feb0e03a010> (NoMethodError)
Aha! As mentioned, all we have to do is define an each method in the host class to mix in Enumerable. Our final class looks like this:
class PropertyList
include Enumerable
attr_reader :name, :property_list
def initialize(name, property_list=[])
@name = name
@property_list = property_list
end
def add_property(p)
self.property_list << p
end
def each
self.property_list.each { |p| yield p }
end
end
and we get an output of: “Total SQ/FT for LA Properties: 10000” which is correct.
Now let’s level up some more. How about we build our own Enumerable module. We want to get this to work:
big_rooms = la_props.select { |s| s.num_of_rooms > 3 }
only_sq_ft = la_props.map { |s| s.sq_ft }
sum_all_sq_ft = la_props.reduce(:+)
All we have to do is define our own module and include it in the PropertyList class:
module EstateEnumerable
# Here we would expect an each method in the class
def select
puts "Using custom select!"
result = []
each do |s|
result << s if yield s
end
result
end
def map
puts "Using custom map!"
result = []
each do |m|
r = yield m
result << r
end
result
end
def reduce(op)
puts "Using custom reduce!"
case op
when Symbol
curr_proc = op.to_proc
result = 0
each do |o|
result = curr_proc.call(result, o.sq_ft)
end
result
else
puts "Need a symbol"
end
end
end
With this we just “Include EstateEnumerable” instead of “Enumerable” and we’ll get this as the output:
puts "Plenty of rooms: #{big_rooms}"
puts "Filter only for sq ft: #{only_sq_ft}"
puts "Sum of all sq ft: #{sum_all_sq_ft}"
# Plenty of rooms: [#<Property:0x007fa0a9039e08 @address="932 Horray St.", @sq_ft=1000, @num_of_rooms=7>, #<Property:0x007fa0a9039d90 @address="213 Hello Hat St.", @sq_ft=9000, @num_of_rooms=9>]
# Filter only for sq ft: [1000, 2000, 1000, 9000, 3000, 10000]
# Sum of all sq ft: 26000
This hopefully gives you a glimpse of what Ruby blocks are capable of.
Recent Comments