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”.
(tldr? If you just want the code without all the background, see: https://gist.github.com/4342817 and https://gist.github.com/4342928)
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'delegate' | |
class Base | |
def foo | |
"foo" | |
end | |
def bar | |
"bar" | |
end | |
end | |
class MyDecorator < SimpleDelegator | |
def new_method | |
"new_method" | |
end | |
def foo | |
"Overridden #{super}" | |
end | |
end | |
class DecoratorTest < Test::Unit::TestCase | |
def setup | |
@base = Base.new | |
@decorated = MyDecorator.new(@base) | |
end | |
def test_pass_through_methods | |
assert_equal "bar", @decorated.bar | |
end | |
def test_decorator_can_add_method | |
assert_equal "new_method", @decorated.new_method | |
end | |
def test_override_with_super | |
assert_equal "Overridden foo", @decorated.foo | |
end | |
end |
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 current_user
.
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
accessor. h.content_tag
, or even h.url_for whatever
or h.whatever_path(id)
.
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?
So Draper’s 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'delegate' | |
class Base | |
def foo | |
"foo" | |
end | |
def bar | |
"bar" | |
end | |
end | |
class BaseDecorator < SimpleDelegator | |
def initialize(base, view_context) | |
super(base) | |
@view_context = view_context | |
end | |
def _h | |
@view_context | |
end | |
end | |
class MyDecorator < BaseDecorator | |
def new_method | |
"new_method" | |
end | |
def foo | |
"Overridden #{super}" | |
end | |
def a_rails_helper | |
_h.tag("br") | |
end | |
end | |
class DecoratorTest < ActionView::TestCase | |
def setup | |
@base = Base.new | |
# Hey, ActionView::TestCase gives us a `view` method that | |
# returns an ActionView::Context, pretty convenient | |
# for our tests. | |
@decorated = MyDecorator.new(@base, view) | |
end | |
def test_pass_through_methods | |
assert_equal "bar", @decorated.bar | |
end | |
def test_decorator_can_add_method | |
assert_equal "new_method", @decorated.new_method | |
end | |
def test_override_with_super | |
assert_equal "Overridden foo", @decorated.foo | |
end | |
def test_can_access_view_context_method | |
assert_equal tag("br"), @decorated.a_rails_helper | |
end | |
end |
(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 self.view_context
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# some ./app/helpers/some_helper.rb, in my case actually in an engine gem | |
module SomeGemHelper | |
def my_gem_decorate(model) | |
# Here I'm hard-coding to always decorate with MyDecorator, | |
# but it could also be passed in as a method arg, or guessed | |
# from the model.class name, or from a differnet model attribute | |
# like model.presenter_class, or taken from configuration, or | |
# some combination — whatever meets the needs of your design. | |
decorated = MyDecorator.new(model, self) | |
yield if block_given? | |
decorated | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Now in any old view template, check this out: | |
<% my_gem_decorate(@model_obj) do |model_obj| %> | |
<p><%= model_obj.decorator_method %></p> | |
<%# Pass it to a partial, the partial gets the decorated | |
# object, great. %> | |
<%= render "some_partial", :object => model_obj %> | |
<% end %> |
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.
yes, i read all of these. unfortunately this is well over my head, but i am hoping that through osmosis this will all make sense in a year or so ;)
Really interesting post, it’s great to see an alternative solution without involving any hidden magic and illustrating methods of testing it too. Will definitely be following this pattern in my own apps.
This is the first well reasoned arguments I’ve heard in the community for Decorators. I actaully might want to use them now! Please keep us up to date on your progress :)
Note that if you use DelegateClass (also provided by the stdlib delegator), you can specify the type of the underlying object. This has some nice benefits if you have expectations of what it will do.
Anon, I don’t understand what benefits you get from that?
I think anon might be refering to using a decorator in form_for where Rails will use the objects class as part of some convension. In which case the class reported by the decorated object needs to be that of the original object. I’ve never used DelegateClass, but have used to DumbDelegator (https://github.com/highgroove/dumb_delegator) to do this.
Ah, interesting, thanks Kris. I just tested it, and it doens’t seem that `< DelegateClass(YourClass)` changes #class either. And don't those kinds of rails helpers generally use #model_name, not #class.name? I confess I mostly don't use those aspects of Rails though.
(Those parts of Rails that _do_ depend on #class should probably be considered bad designs and patched, sez me — that’s not duck typing. But yeah, those tend to be the parts I consider ‘too much magic’ getting in the way of me doing what I need in the first place.)
Hey, Jonathan, very interesting post, I definitely appreciated it. Keep em coming.
Great post, looking at using this.
I’m confused by the way you set up your collection of decorated objects. Are you passing the view context in to a new decorator for each object? Will this be expensive if done this way?
michael: Yes, I am passing the view_context to each decorator. The way this kind of decorator works, every individual decorator needs a view_context, yep.
I can’t think of any reason that ought to be expensive. It’s just passing a reference around, not making a copy of anything. I don’t think it should be expensive to have 10, 20, or even 100 or probably even 1000 references to the view_context. But I have not benchmarked it.
(Making 1000 decorator objects might be expensive itself if you have a collection that large, whcih would be unusual. But giving each one a reference to the view_context should not, even in the case of a 1000 item collection, be any significant added expense for any reason I can think of.)
Thanks!
Or you can just `include Rails.application.routes.url_helpers` into your presenter and be done with it ;)
At points in the past, route helpers didn’t work without access to a request context. Do they now? Convenient, if so.
Vitaly: Now try to figure out how to get access to polymorphic route helpers. :)