My notes on Day 3 of Ruby from Seven Languages in Seven Weeks by Bruce Tate follow.
My knee-jerk reaction to the power of Ruby modules to change the behavior of classes at run-time is probably the typical response of programmers of my generation: self-modifying code is bad. Now that I’ve got that out of my system, let’s be open-minded and consider what we have.
Ruby modules remind me a lot of aspect-oriented programming in the sense that you can add behavior to a class (or not) at runtime. I don’t see a way right now to do truly aspect-oriented programming with it (i.e. wrapping methods with logging or transaction boundaries) but then I’m only on my third day with Ruby, I imagine someone clever has already worked it out.
Pondering the idea a little more, it seems that if, for example, method renaming was viable then one could write a module that renames all (or a targeted subset) of the existing methods then use method_missing to provide the desired aspect. That might get funky when trying to apply multiple aspects but that could likely be worked out.
I can see the appeal of creating simple data holders at runtime to correspond to tables. We actually considered doing something like that in Java many moons ago (before Hibernate) using a custom ClassLoader but ultimately decided in was too complicated for the relative benefit. Had we been using Ruby we definitely would have done it.
Only one exercise this time and it is not too bad. I did get tripped up briefly not realizing that the argument to method_missing is a Symbol and not a String. I also briefly thought I would need to create an Enumerator but quickly realized code blocks would come to the rescue. Here’s the pieces I added to Tate’s acts_as_csv_module.rb:
module ActsAsCsv # snip... module InstanceMethods def read # snip... file.each do |row| @csv_contents << CsvRow.new(headers, row.chomp.split(', ')) end end def each(&block) csv_contents.each(&block) # <1> end # snip... end end class CsvRow def initialize(headers,data) @headers = headers @data = data end def method_missing(name) index = @headers.index(name.to_s) # <2> return @data[index] if index end end
- Code blocks to the rescue.
- Don’t forget to use name.to_s
Nice and compact but powerful.