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