The concept of decorators and presenters have been talked about ad nauseam in the Ruby and Rails communities, but there are many ways to skin a cat.
Evaluating alternative Decorator implementations in Ruby by ThoughtBot is one of the best articles I've seen in a while on the subject and this post is an attempt to take things a little further.
So, to get started — the options available are a single PORO decorator or a method_missing
implementation of a Ruby decorator. In the context of Rails and the fact that our POROs map to DB records thanks to ActiveRecord, object transparency can be rather paramount.
For example, taking the PORO decorator, suppose we decorate and instance of Listing
— we’re probably reducing the interface by about 300 methods. (see the example in the ThoughtBot article). Therefore, practically I wanted complete transparency — after all I want to decorate my AR object, not lobotomise it!
While ThoughtBot propose using SimpleDelegator
for the decorator, I went with BasicObject instead.
Here's the gist of Josh Clayton's thoughts on BasicObject
where he's referring to a code example dealing with an instance of Coffee — we coders take our espresso seriously, at least I do!
BasicObject was introduced in 1.9 and makes decorators really easy to write. What're the benefits?
The decorated instance of Coffee still behaves exactly like it did before it was decorated. As I would expect, this object responds to the exact same methods as an instance of Coffee. I can decorate as many times as I would like and the object behaves normally. In my opinion, this is the key reason to use a decorator (what would the original "decorator" example be called, a façade? It's only one class, not multiple, so it may not even fall within the concept of a "pattern" even). Anyway, if it fully decorates (all other methods fall through), it presents itself as a principle of least surprise. In a view, I shouldn't care if it's decorated or not; it should behave exactly like the object I expect. By "decorating" it and changing the interface (removing methods), that would throw me for a loop, especially if I'm new to the project.
The fallback is that it's using method_missing. That's it. Because BasicObject has a very limited amount of methods, there's not much work to do in order to have it behave exactly like the component it's decorating.
It's safe to say, I agree with Josh's comments on the subject on how a decorator should operate, i.e. PORO decorators aren't really "decorating" much.
With that out of the way, here's the decorator I'm using
Since my project involved instances of Listing
, I setup a ListingDecorator
.
The draper
gem introduced the concept of a decorate
method — I felt it was a nice distinction and decide to go with it as well. It's the same as calling .new
[4] pry(main)> ListingDecorator.decorate(listing).at_state?('step_final')
=> true
Our decorated method works! What about transparency?
[35] pry(main)> dec = ListingDecorator.decorate(listing)
=> #<Listing id: 3
[36] pry(main)> ListingDecorator.decorate(listing).methods.size
=> 1037
...and here's method_missing
doing it's magic for good measure
[40] pry(main)> dec.foo
NoMethodError: undefined method `foo' for #<Listing:0x007fca704ac558>
Presenters
Before I touch on how I relate to presenters and where the line is drawn between a decorator and presenter, here's my PORO presenter
Why a PORO? Well, the presenter should operate on our decorated object and that's where the transparency stacks in quite nicely. It's evident that our presenter will be used to increase our interface, i.e. add methods.
Josh describes the distinction between the two extremely well
Steve briefly touched on the difference between decorators and presenters, and Sergeui mentioned (maybe)Yehuda saying they're the same thing. This is such a loaded question, but generally (in my opinion) a decorator is used to modify behavior dynamically (without subclassing, as you mentioned above) whereas a presenter is commonly used to decorate by adding methods (not changing behavior). Most times I've seen presenters, they also take in additional context aside from the component (e.g. the user currently logged into the system). As Steve said, presenters are used for the view layer (whereas decorators can be used anywhere in your application to dynamically change the behavior of methods at the instance level). Presenters typically are a form of decorator, as only certain methods are affected while all others fall through to the primary component.
An example of using our presenter stacked on top of the decorated object would look like
@listing = ListingPresenter.new(ListingDecorator.decorate(listing)) # in the controller
@listing.progress_at('start') # in a view template
The advantage here is the first line above would be setup in a controller, and the proceeding line is an example of calling a method presenter method in a view template. If we call any methods not in our presenter, they will fall back to our decorated object first, and right down to our ActiveRecord object.
How have you handled implementing this pattern? Do share your experiences in the comments below.
Here's my gist for this post for added convenience as well.