Drop the Superclass
I’ve worked with Java to Ruby converts for the past year and the #1 thing that comes up over and over is the excessive use of inheritance. I can understand why, I was taught Object Oriented Programming in C++. We spent a week on encapsulation (that was the end of “basic” programming) and en entire semester on inheritance and templating (that was the “advanced” class). Inheritance was driven into so many of our heads that we have a hard time not using it. We were told that this is where the power of OOP lies - this is how you get the most code reuse.
Unfortunately it’s just not true. The use of inheritance results in very strong coupling of your code. Any class is dependent on all of its superclasses. You can’t isolate this class without taking all of the superclasses with it. You can’t isolate the classes’ siblings without doing the same. You either have to make several copies of these classes (don’t do that…) or you have to move the entire chunk of code from project to project as a single unit. The latter is the only practical choice you have - but what if you just want to use a few pieces of functionality found in this code? Tough luck - it’s all or nothing. This leads to bloated code, poor code re-use, and hard to use APIs. Ironically this is what most of us are used to - so it seems normal to us when we come across it.
Since publishing is a large part of the industry I work in, there are often “publishing” methods that need to be added to our models. Every implementation I’ve seen so far injects a Publish class in between ActiveRecord and the model. This isn’t necessarily a bad thing on its own. With just one level of inheritance we can still achieve good code reuse. But where do go from here? If we want to add additional functionality at this stage we either need to add it all to Publish (even if it’s not in the publishing domain) or we need to add additional levels of inheritance. All of the sudden our code reuse has gone out the window. We’re going to have a hard time injecting this functionality into other code bases.
Using composite objects instead of inheritance can help in many cases. We can add the desired functionality into a separate class and then instantiate that class in the class we want to “extend.” Then you can call (or delegate to) these methods.
def Publish < ActiveRecord::Base
belongs_to :article
end
def Article < ActiveRecord::Base has_one :publish
def initialize @publish = Publish.new end
def before_save raise Publish::NotPublished unless @publish.is_published? end end
Wow this still sucks! We either have to use two tables in the database or initialize publish from data in the article table. Either way really sucks. We don’t want to require an additional table and relationship. We also don’t want data for the Publish model in the Article table. Composition will often work just fine in a class that isn’t doing any object-relation mapping - but in this case it causes more problems than it solves.
In the land of dynamic languages (like Ruby) we can get around this. Using a run-time patch that injects a decorator method into a class can provide a way to build up functionality on the fly. It can also be more descriptive. It’s not always clear what superclasses Publish is being built from. If you break all of this functionality out into modules that get mixed in via decorator methods - then it’s easy to see what the behavior of the class will be by just looking at the class definition.
This functionality is broken out into a Rails plugin that allows runtime changes to ActiveRecord (or ActiveResource). Here is what the (simplified) patch looks like:
module ActiveResource
module Acts
module Publishable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_publishable(options={})
include ActiveResource::Acts::Publishable::InstanceMethods
end
def publish(id, options = {})
end
end
module InstanceMethods
def is_published?
(status == "published" || status == "edited")
end
end
end
end end
ActiveResource::Base.send(:include, ActiveResource::Acts::Publishable)
Now here is what the model will look like:
class Article < ActiveResource::Base
acts_as_publishable
end
Now we can mix this functionality into any code base. If you have database fields that relate to publishing they can be mixed in via migrations when the plugin is installed.