Wednesday, February 19, 2014

Week 3 Day 3: Mixin 'n' Match

Major Activities of the Day: Lecture and lab to introduce modules, and more work with ORMs (the latter being the "Match" in the title, because they match up all sorts of data).  Modules are Ruby's way to gain the functionality of multiple inheritance without some of the problems that come along with it.  A class can inherit from only one superclass, but it can mix in as many modules as you want, to add shared functionality.  Although we didn't discuss it yet, my favorite examples are Enumerable and Comparable, which add lots of functionality to an object as long as you define a few extra methods.  We really focused on writing our own modules, though.  The homework was our most difficult challenge yet; we had to put together a students/courses/departments/registrations model using databases, and I decided to be super-cool and write a metaprogrammed module that would take care of all the database-y stuff.  This includes a method_missing method to handle queries like Department.find_by_name or Student.find_all_by_course.  It was really hard, but it made the rest of the assignment way easier to do.

One second, though - what's metaprogramming, and what is method_missing?

Metaprogramming is writing code that writes code.  In other words, you tell the interpreter to compose methods or do other code-writing-y things on the fly.  The celebrated 'attr_accessor' method does just that; it takes one or more Symbols as an argument, and writes a method for each Symbol to assign and query an instance variable with the name defined by the Symbol.  So `attr_accessor :name` is the same as:

def name
  @name
end

def name=(name)
  @name = name
end


Pretty neat, huh?

method_missing is, in my opinion, one of the coolest - yet most dangerous - features of Ruby.  It is a method defined on every object that specifies the handling of methods whose name it doesn't recognize.  The default handling is to raise a NoMethodError.  But you can do this:

def method_missing(method_name, *args)
  7
end


and then every unknown method will instead return "7" instead of raising an error.  Why would you want to do this?  I don't know.  But maybe you would do this:

def method_missing(method_name, *args)
  if method_name.to_s.start_with?("find_by_")
    attribute = method_name.to_s[8..-1]
    @players.find{ |player| player.send(attribute) == args[0] }
  else
    super
  end
end


so you could do baseball_card_collection.find_by_homeruns(319) and, despite the method not being defined anywhere in your code, it will find the player with 319 homeruns.  By the way, player.send(attribute) in this example is also an example of metaprogramming; it sends the method that shares the name of whatever attribute is passed in.  This creates a lot of possibilities!  Supposedly, however, objects with method_missing can present really annoying behavior and be notoriously difficult to debug, so it's often ill-advised to write a method_missing method.

P.S. Today I presented a technical blog post from my other blog; you can see it at http://amcaplan.wordpress.com/2014/02/19/lessons-from-a-failed-successful-gem/ (it's all about reflections on the experience of writing a gem, what I learned, what I could have done better, etc.).

Skills developed: Module writing, more ORMs, metaprogramming

No comments:

Post a Comment