At first, it was pretty confusing, since I came from a C++ background and was use to the idea of design patterns. Well, there are patterns, it's just that in dynamic languages, much of the traditional Gang of Four design patterns goes away.
I'll outline the meat of the talk. Rails doesn't use the Decorator Pattern. Instead it basically renames methods in calls to inject additional functionality that wraps around the original functionality. How does it do this? It uses something called alias_method_chain, detailed here and here.
And inside alias_method_chain, is a native method called alias_method. Since everything in Ruby is an object, and changeable, that means methods of modules and objects are also changeable. It basically makes a copy of the old method and calls it something new.
I made pictures to show the progression. Let's say we have a method called "save" that we want to enhance with "validation".
We start with ActiveRecord and the Validation module. So in our validation module, we make a method called "save_with_validation"(purple heart) that calls a method called "save_without_validation" inside it. "save_without_validation" doesn't exist yet, because we haven't called alias_method_chain().
Then our first step is to include the Validation module inside of ActiveRecord.
Then when we call alias_method_chain(:save, :validation), using alias_method, it'll make a copy of the original save method, and call it "save_without_validation".
Then, it'll rename the method inside of validation from "save_with_validation" to "save". This way, any code calling save() on ActiveRecord will execute the new save, which does validation first, and then turns around to call the original save (green sun). The client won't know any different, but in fact, code was injected between the original save (green sun), and the caller inside of the new "save" (purple heart). And the original save doesn't know any different either, since nothing's changed from its point of view.
In code, it'll look something like this:
class ActiveRecord
def save
# do saving stuff
end
end
module Validation
def save_with_validation
# do validation
save_without_validation
end
end
class ActiveRecord
include Validation
alias_method_chain :save, :validation
end
Which if we 'executed' the above as we did in pictures, it ends up being equivalent to:
class ActiveRecord
def save_without_validation
# do saving stuff
end
def save
# do validation
save_without_validation
end
end
I'm not sure how I feel about it as of yet, but at first glance, it's a nice pattern once you know what's going on. At first, I saw all these methods being called in the Rails source which aren't actually defined anywhere. It ends up it's because of metaprogramming stuff like this going on. I think other people have said this is bad idea because it's hard to inject functionality in the middle of chains that already exist. If you pick up one, you pick up everything before it. One might argue the same is true of Decorator patterns.
In any case, you'll see this repeated over and over again in the Rails source, so hopefully, this'll give you some idea of what's going on if you ever decide to go Rails splunking.
Wil- I thought your talk on Saturday was outstanding, and this article helps to clarify some of the points that eluded me. My favorite line from this post: "...you'll see this repeated over and over again in the Rails source." So once we master the paradigm you've outlined here, we'll have a deeper understanding of the Rails source overall.
ReplyDeleteI'm glad. The hype of Rails tempts one into cargo culting, with unknown (and possibly disastrous) long-term results. We're engineers, and we should know better! Imagine a civil engineer building a bridge with off-the-shelf components that he/she knows nothing about. It would never happen. A good civil engineer will understand the stress characteristics of the relevant materials: concrete, steel, etc. The same is true for a good software engineer.
Thanks for shedding some light on our "base materials", and for helping me to become a better engineer.
Thanks.
ReplyDeleteWell, it's a fine line. I generally don't dig deeper until I've reached a certain level of understanding or I run into edge cases. There's simply way too much to know and keep in your head if you go all the way down.
But I think there's value to be gained by reading Rails source, as it has attracted some of the best developers to work on it. So reading their work will give a sense of what's good code smells like.
neat
ReplyDeletevery enlightening with the graphic examples:) thanks:)
ReplyDelete