makes lulalala a bluff boy

Magics that Decorator/Presenter Gems Do to Make Your Type Less

Some of you Rails developers probably have used a 'decorator' or 'presenter' library. These libraries aim to bridge between Rails model and view layers. If I am to define it, a presenter allows developers to group helper methods related to a model to be under a namespace related to that model, instead of the current global space.

But would you believe it? There are actually a dozen or more decorator/presenter gems out there. Why do we reinvent the wheels? The first reason is that there is really a demand, because keeping large amount of helper methods under the same namespace is just unrealistic. The second reason is that, these gem owners have different views on this philosophical question: should the interface be implicit (things are done for the user under the hood) or explicit (user has to type more).

As a fun exercise I will compare 6 of these gems and explain how the general concept works. I have not used some of the gems, but only read the readme and some of the code, so if there are mistakes please let me know.

*Disclaimer, I am the owner of LulalalaPresenter gem. And if you have never used a presenter/decorator before, read this post to know why it is useful.

The following table represents the spectrum of these gems. On the left end, we see gems favoring implicity more. On the right end, gems are more explicit in nature.

(I find many more gems after writing this. A complete list can be found here. If you want to add your gem to it, let me know~)

"I hate magic" camp

The conservative rubyists prefer to avoid magic. They don't like to override too much things, and they write PORO instead of meta-programming.

The most simple example can be seen from Ryan Bate's RailsCast: "Presenters from Scratch". In the video he explains step by step how to make a simple presenter.

The only meta-programming used is how it infers the presenter class from the model. All other interfaces are simple object passing method calls. Only 38 lines are required to achieve this.

Since by default it does not act like a model, Ryan calls it a presenter instead of decorator. You can still delegate calls to model if you wish, but the readme indicates that presenter object should not be mixed up with model object.

I'll talk about LulalalaPresenter later after ActiveDecorator, because it is its fork.

Decorator

According to Design Patterns in Ruby, the decorator pattern is a wrapper that "supports the same core interface, but adds its own twist on that interface." In this case we add view related functionality around the model. A decorator can act as if it is the model, which means less view changes are required.

ActiveDecorator, oprah and display-case all position themselves as decorators. Draper on the other hand gives the user freedom to choose if the wrapper should become a decorator or not.

One association further

Associations are part of the ActiveModel interface too, and some gems can decorate associations for you to save some key strokes (example). This is no small task, as there are multiple ways to trigger associations. It is therefore more possible to break across Rails versions.

Directly call helper methods within decorator

Normally for a model decorator to call helper methods within it, it needs to call via view_context (often alias as h), e.g. h.url_for(). Both ActiveDecorator and Draper offers a way to save key strokes so you can call url_for directly. This is done by a simple trick: if the model does not support a method, we retry it on view context again (example).

The implications are: 1. this is a wee bit slower because method_missing is utilized. 2. if same method name exists on model and view_context, model's method takes precedence. This is usually not a problem.

Globally accessible view_context

Draper, ActiveDecorator and LulalalaPresenter all keeps the view_context in a globally accessible place (example). This is done for two reasons:

Give an OO-esque feel to the decoration method:

Draper decorates by calling model.decorate
LulalalaPresenter presents by calling model.presenter
The design saves you from passing view_context, otherwise one will need to do model.decorate(view_context) all the time.

To allow automatic decoration (see below).

The Holy Grail of Implicity

We have reached the end of implicity. To automatically decorate things, ActiveDecorator hooks into the render call, and decorates instance variables when applicable (example).

The gem walked the extra miles for you, so you don't have to do anything beside writing the decorator class. In some way, this feels like Rails philosophy, where we just write the controller/model/view, and things will just hook up perfectly without you knowing what's happening under the hood.

My Own Presenter and Conclusion

As you can see from the table, most are decorators. They behave like models, delegating calls to the inside. However if we treat presenter as a separate object, we can reduce a lot of the delegation complexities.

I was looking for a presenter which does not involve decorating ActiveModel, but I couldn't find one. Ryan's solution was the closest I could find. So I thought I can make my own.

I personally hate typing parenthesis, because my left little finger aches when holding shift key. Instead of present(model).foo, I prefer model.present.foo. If we are to do this, how can presenter get hold of view_context object? We can pass it in everytime like this model.present.foo(vc), but that's definitely too much too type. In the end, I found out ActiveDecorator's globally accessible view_context, so I used it to make my own. Hopefully this does not offend any one, I feel guilty but I want to please my little finger.

Do we need presenters/decorators pattern? Some may argue this is an offense to the MVC architecture, and some may think it introduces extra complexity. Again we will probably never get a consensus on this, but I use it because it made coding Rails more pleasant. So if you have large number of helper methods in your codebase, I recommend you to just pick a gem and try it out :)