Patterns in Ruby: Decorator Pattern
I've been a fan of the work that was done by the Gang of Four on Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Professional Computing Series), Martin Fowler's Refactoring: Improving the Design of Existing Code
and the bridge book by Joshua Kerievsky, Refactoring to Patterns
.
I haven't seen a lot of information out there on how Ruby changes the game: ways to apply these patterns using Ruby idioms, new patterns that show up, patterns that fall away. So I've decided that as I go along I'll try and document the new twists as I see them.
Today's article is the twist on the Decorator pattern. The Decorator pattern wraps the original object in a new one which will add functionality to some of the methods and then delegate to the original object. The prototypical example is decorating a window object.
A Decorator Example
public interface Window {
public void draw();
}In Java, we'd probably use a decorator to add scroll bars.
public Class VerticalScrollWindow implements Window {
private Window window;
public VerticalScrollWindow(Window window) {
this.window = window;
}
public void draw() {
drawScrollBar();
window.draw();
}
}The basic concept is that we'll usually want to be adding some behavior around a particular method call to extend behavior, while retaining the same interface.
Ruby
In ruby, we have a number of options to achieve this pattern. First, let's define our original Window in Ruby code:
class Window
def draw
# do some drawing here...
end
endTranslating the pattern literally
Given ruby's duck-typing nature, we could easily create a VerticalScrollWindow that wraps the original Window when we create the original window object, and pass that around. In fact we could patch only the single method and add a method_missing implementation that always delegated to the original Window.
class VerticalScrollWindow
def initialize(window)
@window = window
end
def draw
draw_vertical_scrollbar
window.draw
end
def method_missing(method, *args, &block)
@window.send(method, *args, &block)
end
endThis approach could be evolved to a much higher level using method_missing tricks and dynamic modifications. One could create a generic Proxy class which took a target class and intercepted all method calls, executing pre- and post- method blocks for specific methods. I'll leave that as an exercise for the reader for now...
Using Alias
Another option we have is to "monkeypatch" the original class (or specific instances of it). The idea here is to rename the old implementation of the method, insert a new implementation and have that refer to the renamed original.
class Window
def draw
# do some drawing here...
end
# some code...
alias :original_draw :draw
def draw
draw_vertical_scrollbar
original_draw
end
endBuilding pre and post hooks
Our last option is to build pre and post method hooks into the original class definition. Obviously, this approach requires the original class' author to explicitly build in callback hooks. This approach can be found in Capistrano (you can add tasks which get executed before or after well known tasks), or in ActiveRecord (lifecycle type callbacks - i.e. before_save, after_destroy).
This option is a bit more advanced and differs in the approach taken. To learn how Capistrano does it, dive into capistrano/actor.rb, line 118. Each task is defined as a method which explicitly calls before and after methods if they exist.
For ActiveRecord, please refer to activerecord/callback.rb. ActiveRecord goes a little further by allowing class level methods to add pre- and post- code blocks to be executed which will be inherited down the class hierarchy. They also allow instance level methods to be defined for each hook which would not be inherited.
Update: Francisco points us to a great article on Decorator showing some other possible Ruby implementations.
Getting Capistrano to play with Oracle on Red Hat Enterprise Linux
I've been working on an internal project that connects to a legacy Oracle database. The process of getting the driver and client set up was a bit annoying, but I'd done it before and I managed to get through it again. (For those of you braving it, be sure you don't include a trailing slash on your ORACLE_HOME environment variable).
The Oracle driver and client are like most Oracle products - unnecessarily complex. One of its requirements is that you set an ORACLE_HOME environment variable. I'd managed to do this and add it to my .bashrc file so that when I manually logged into the app server via SSH I could get the rails app up and running. The problems was that remotely deploying via Capistrano was causing errors - anything that loaded the rails framework, and therefore the Oracle driver, would die with a stack trace about an undefined method. Luckily in my past troubles with the driver, I knew that meant that it didn't have the ORACLE_HOME environment variable set.
In case anyone else is running into this (or I do again), here's a patch:
First, add the following to your deploy.rb recipe:
set :use_sudo, false
Next, modify your capistrano/actor.rb file to force all run commands through bash with BASH_ENV set:
alias :run_without_env :run
def run(cmd, options={}, &block)
cmd = <<-CMD
BASH_ENV=.bashrc /bin/bash -c -- "#{cmd}"
CMD
run_without_env cmd, options, &block
end
These two changes will force the environment to be loaded and also make all command be run as the original user (which should have rights to do so).
You can read more about it on the Google group for Capistrano
Google Summer of Code Swag
Hmm what is this mysterious package?
It's my Google Summer of Code T-Shirt! That feeling you're having - pure jealousy.
Note to Subaru Marketing: Part Two
Dear Subaru Marketing:
Hello again! It's been two weeks since we last spoke, and I miss you already. As I mentioned then, I appreciate your efforts to market your vehicles in my area and your attempts at regional advertisements. Since that time you've introduced a new ad referring to I-95. While I'm no expert on geography, I'm quite sure that I-95 runs by New York City and just barely touches a corner of our state - the opposite corner from where you are advertising. Here, I've provided a helpful illustration:

I-95 is the red winding line. Rochester is located at the tip of the arrow.
Perhaps you are trying to save money by referring to a 1,927 mile long interstate highway that spans the entire east coast and running the ad in all those markets as "local". While this is ingenius, to reach Western New York you would likely want to reference I-90, or as we New Yorkers call it, the Thruway. It has the added bonus of stretching the entire continental US's northern states.
But those are really just semantics, after all you've discovered a way to make "regional" ads that span a huge swath of the country. Maybe you can subdivide the audience into similar niches beyond region by referencing very specific interests. Targetting such a niche would make the listener feel unique and passionate about your product. I suggest using in-jokes about Seinfeld, or maybe sharply dividing the audience and creating passionate responses by professing a love of puppies and babies.
Sincerely,
A member of your target audience

