Decorators/Presenters are (or were?) kind of a hot topic in Railslandia. (If you already have no idea what I’m talking about, I do recommend the Giant Robots post linked above as background. ).
I’m going to stick to the word ‘decorator’, because I think the approach described the the old school Decorator (or here) pattern is the most interesting/useful one, especially expressed as: “a class that surrounds a given class, adds new capabilities to it, and passes all the unchanged methods to the underlying class”.
I think interest in Decorators in Rails is largely due to some architectural problems of Rails helpers. Your helpers can turn into a big pile of non-OO functions, all sharing a namespace and stepping on each other. But much worse is that there’s no great way to use any kind of polymorphism with Rails helpers — providing different implementations for different contexts or types, over-riding or sub-classing helpers.
Still, I think your first question should be: Do I really need Decorators at all?
In general, I’d rather avoid em — I’d rather take the pain of a bit of pile-of-mud chaos in my helpers (and in a typical app, while it offends one aesthetics it’s not really that much pain) — over dealing with Yet Another Architectural Element that I and future readers of my code will have to understand (you don’t get out of having to understand rails helpers, you’ve just got one more), complexity, code interacting with Rails in mysterious ‘magic’ ways that might break in future versions of Rails, etc.
Why I decided I needed Decorators anyway
I’m developing a rails ‘engine’ gem, reusable code that will give you some presentational elements users can use in their rails apps. It’s got a fair amount of presentational logic in it — and while the logic needs to give you a decent prototyping environment out of the box, it also needs to be locally over-rideable and customizable — AND the user needs the ability to change the presentational logic differently in different contexts (trying not to get into details not because it’s secret (it’s open source), but just because it’ll turn into a confusing sidetrack).
I resisted Decorators, but everything else I tried ended up decidedly not simple, as my gem grew.
- Tried putting it all in Rails helpers — I was polluting the helper namespace of the host app using my gem, and it’s very confusing to over-ride helpers provided by a gem in a local app (let alone try to over-ride such logic differently in different contexts).
- Tried actually jamming all the logic into the view templates, which made me ashamed from the start, but at least was workable at the start when the logic was trivial, but got untenable as the gem grew non-trivial.
- Tried putting the presentational logic directly into the models (which are NOT ActiveRecord in this case, but relatively simple plain old ruby objects), and letting local implementers over-ride it with a chain of run-time specified mixins added into objects with “extend”. It started to get as complicated as that sounds, without actually being as flexible as one would like anyway (and no good way to have presentational logic call Rails helpers — more on that later).
And wound up with a hodge podge mix of all three techniques.
So I bit the bullet and after thinking about it and re-reading various blog posts on Decorators figured, okay, Decorator really is what I want.
But what’s the Simplest Possible Decorator Implementation That Just Might Work? If I have to give in and use a Decorator, let’s at least keep it as transparent, unmagical, few lines of code, not over-engineered as possible. I suspected I could keep it pretty darn short, and so far it seems to be working.
Start With: plain old ruby, delegating via SimpleDelegator
The straightforward description of a Decorator is relatively easy to write in plain old ruby: “a class that surrounds a given class, adds new capabilities to it, and passes all the unchanged methods to the underlying class”
Various blog posts discuss using plain old ruby objects one way or another implement a Decorator — most of them use method_missing. The examples may or may not correctly fix respond_to to match, which is important so as to have objects that behave like ruby objects should and don’t create surprises for other code. Oh, and then there’s respond_to_missing to get right too.
Why try to do all this yourself and get it right, when ruby already provides something in the stdlib that can do it all for you just fine? Maybe because SimpleDelegator’s documentation doesn’t clearly tell you about the simplest way to use it to do this kind of delegation: Just sub-class SimpleDelegator, and call it’s initializer with the base object, that’s it. (This morning I read the first comment you’ll find at the ruby-doc.org link above, and thought, gee, that’s helpful — before noticing I had authored it myself a year ago, hah.)
Check it out, super simple ruby Decorator/Delegator:
Uh oh, but what about Rails helpers
Okay, that’s great, if all you need is a plain old ruby decorator with a pass-through interface, I don’t know why you wouldn’t do that with SimpleDelegator and call it a day.
But, see, remember our motivation here was cleaning up our Rails helpers. And local Rails helpers often need to be defined in terms of other Rails helpers (from the framework or local). Which aren’t available in the decorator.
You can start out trying to manually include Rails helper modules, or local helper modules.
include ActionView::Helpers::TagHelper in your decorator, no problem!
But the most useful/interesting helpers end up needing access to the local request context, or they won’t work:
url_for, a local
Draper is a popular rails Decorator gem, whose power is precisely in letting your decorators use Rails helpers from the current context, using the
h.content_tag, or even
h.url_for whatever or
Draper tries to hide how it provides this ‘h’ object. Which it does by kind of hooking into ActionController to keep the ‘current controller’ in what’s effectively a global variable.
I’m a bit scared of this kind of magic (might i have been burned in the past by Rails gems doing behind-the-scenes magic with Rails, which stop working, and I’m not capable of debugging my dependencies cause I never understood them? I might have). And it’s the sort of design that may or may not be thread-safe under various multi-threading scenarios. I’m not saying it’s not — I’m saying it’s just barely complicatd enough that I’d have to do some serious thinking/analysis to know if it was or not, and I hate thinking.
Draper is possibly quite great, lots of people are using it and happy with it and you should check it out. (I have not used it myself, just looked at the docs and code :) ). But I was curious to see how much trouble Draper’s “magic” really saved me — can we do much of the same thing, with a simpler and more transparent implementation, without it being too ugly?
h, that’s pretty much just what ‘self’ is when you’re in a view render or existing helper, right? (Pretty much, Draper wraps it in a proxy to take care of some edge cases and oddities). What if we just manually pass that to the Decorator? But what if you’re in a controller? Turns out that you can call
view_context in a controller too… to give you, oh, maybe it’s an ActionView::Base, or some kind of ActionView::Context, I’m honestly not sure exactly what it is, this aspect of Rails architecture isn’t documented super well and the code is a bit convoluted… but it pretty much just works anyway, or seems to to me.
And this is just barely complicated enough that I feel like encapsulating it in a BaseDecorator class, so I can create many of my own decorators on top. I’m going to use
_h with an underscore, instead of Draper’s
h, just because it seems safer to me and not that much worse to write.
(Where to put your decorator classes? Did you know that Rails for a while now has allowed you to create arbitrary directories of whatever name you choose under
./app, and they’ll Just Plain Work, same as app/models, app/controllers? Just create a directory app/decorators if you like, stick things in there, they’ll be on the load path and just work, no extra config or code required to setup.)
How to use it?
We’ve got a model object or a collection of model objects — we don’t even care if they’re ActiveRecord or not — mine actually aren’t, but it doesn’t matter, this method is entirely agnostic about that, which is a nice thing about it. But we want to wrap them our decorators.
From a controller action method:
@some_obj = MyDecorator.new(@some_obj, view_context) @collection_of_objs = @collection_of_objs.collect do |o| MyDecorator.new(@some_obj, o) } end
Or from a view template or helper, just use
self instead of
@some_obj = MyDecorator.new(@some_obj, self) @collection_of_objs = @collection_of_objs.collect do |o| MyDecorator.new(@some_obj, o) end
Having to explicitly pass in the view_context or self arg aint’ so bad, is it? Especially when it means our complete Decorator implementation can still be only a few dozen lines!
More convenient use in Views
Draper’s docs seem to suggest that decorating in the controller is the best way to go.
But for my own uses, it was actually looking more convenient to sometimes be able to decorate at the view layer (template or helper), perhaps deciding what Decorators to use based on helper methods, or even using different decorators in different partials. At first, not being able to figure out a good way to do this was keeping me from pursuing this whole design.
I didn’t want to end up with ugly view code like this:
## BAD, what I did NOT want my template: <% decorated = SomeDecorator.new(@model_obj, self) %> <p> <%= decorated.something %>
I mean, really, that’s terrible.
But then while continuing to read up on Decorators, I noticed Ryan Bates “Presenters From Scratch” RailsCast episode, and the freely available sourcecode accompanying the episode had a nice solution.
Much better! And only a few more lines of implementation! Oh yeah!
One tweak: html_escape is weird
Oops, as i started to use this, ran into a problem. Inside a decorator, the code ought to be able to call
_h.any_helper to call any Rails helper (from the framework, from the local app, from another gem, whatever) in the current context. And it mostly works.
It doesn’t work with the html_escape. Because for some reason html_escape is declared as ruby private method access. I have no idea why ; I don’t even know how, it doesn’t look like it’s being declared private in what looks like it’s source code, but if you try to call
_h.html_escape you get a “can’t call private method on another object” exception.
Draper actually makes note of this in a comment too, and makes some architectural designs to try and accomodate that, involving another layer of proxy indirection. Me? Trying to keep it simple, I’d rather treat
html_escape just a bit special rather than write a lot of architecture to pretend it ain’t. I just give my BaseDecorator it’s own
html_escape, and decorator code calls
self.html_escape directly instead of
_h.html_escape like the other helpers. Not great, but not worth more than these three lines of extra code.
class BaseDecorator < SimpleDelegator # ..... def html_escape(*args) ERB::Util.html_escape(*args) end end
So how’s it working out?
Design-wise, I’m very happy with it. My gem provides it’s own standard decorator for it’s model objects, but provides ways for local host application to specify it’s own decorators (and what contexts they should be used in) — which turns out to be a pretty nice way to allow the local app to over-ride presentational behavior, in nice context-specific polymorphic ways. (The local app will generally subclass the standard decorator and then over-ride some methods, or even add new methods).
And I’m pretty happy with the simplicity of this implementation — I was scared of adding complicated ‘rails magic’, I was scared of ‘too much architecture’, or adding more dependencies to my gem. But I’m pretty happy that I ended up with what seems to be just enough decorator implementation to make my code nice and clean how I wanted, in only a few dozen lines of code!
But is it really enough to work? Am I missing important things to work around weird edge cases that, say, Draper might have, being a much more mature and robust solution? I’m not sure, I very well might be — this code hasn’t even made it into my master branch yet, and the gem it’s going to be a part of isn’t even 1.0 yet, check back in a couple months and see how it went. But I’m feeling optimistic.
I don’t really mind the few compromises made so far, having to explicitly provide the
view_context argument, having to treat
html_escape differently, having to spend an extra line manually wrapping things in the decorators. I’d almost rather have the transparency, then have a bunch of lines of ‘magic’ code saving me a line or two of fairly readable code here or there.
Does anyone read this far?
I like to think I don’t use too many more words than required,but I tend to write a lot, I like to cover everything.
Is there actually an audience for this kind of long-form writing on Rails tricks, on the web? If you appreciated this article, please let me know in comments.