Rendering Collections of Heterogeneous Objects in Rails
AKA the "Activity Stream Problem"
This is not meant to be a comprehensive post on the subject. (However, I plan to cover this topic extensively in my next book.) For now, I simply want to point out a hack that may alleviate some of the pain experienced when trying to use Rails' ability to render heterogeneous collections of objects by auto-magically determing which partial template to use.
Let's say you're trying to implement an activity stream, such as in the following screenshot (from my new startup RightBonus). If you look closely you'll notice that there are three different types of events portrayed. There's the core award nomination event at the top, but also an event logging Johanna's new profile picture and the join/starter pack event at the bottom.
I've chosen to implement my activity stream as a collection of event objects, all derived from a STI base Event class. Eventually I might transition to a more complex implementation involving message queues for scaling reasons, but for now one of the nicest aspects of doing it this way is how clean the view code can be. The company's dashboard view has something along the lines of (in HAML):
%ul.events
= render company.events
The events method is an association which returns an assortment of different subclass types depending on the kind of event logged. There are a couple of problems with this approach and Rails' standard behavior:
1) The auto-magically determined partial paths suck.
NominationEvent.model_name.partial_path => "nomination_events/nomination_event"
ChangedProfileImageEvent.model_name.partial_path => "changed_profile_image_events/changed_profile_image_event"
Ugh! Ugly and long. And every event partial would need its own subdirectory, for no good reason.
2) The partial rendering code prepends view.controller_path to the partial path, which means that this approach will not work cleanly across namespaces.
The best solution would be to make the Rails code that determines partial names smarter. If someone wants to help me determine what that might look like I'm happy to collaborate on a proposed patch.
Until then, what I've done is to monkey-patch ActionView::Partials::PartialRenderer in an initializer so that it does my bidding when I give it event objects. Evil? Certainly on some levels, but after going on 6+ years of programming in Ruby open classes is still one of the reasons that I love her so much.