over 1 year ago

Naming your helper methods in Rails can be very difficult. What do you feel when you see this:

auction_title(@auction)
board_title(@board)
category_title(@category)

To avoid method name collision, we usually have to append the model name in front. Helper methods are also global, so you can access all kinds of helpers in one go.

For me personally, I want the following when I create a helper method:

  1. easily know where to put a helper method
  2. easily know how to name the method

Reinventing the wheel

People thought of ways to organize these helper methods, which are now commonly called presenters, decorators or exhibiters.

One school of thought uses decorators to append functionalities on top the ActiveModel, draper, display_case and active_decorator being the three most well-known solutions.

Built-in helper methods all reside within view-context objects. So for presenter to work it needs access to the view-context object. However passing it for every helper call can be tedius. I liked how draper and active_decorator grabs that view_context for you, so you won't have to keep passing it to presenter when calling it.

However I see that its effort in decorating the model object can easily leak. For example would you remember to also decorate the association objects?

I wanted something else instead, a presenter object which contains only the helper methods. I add a presenter() method in the model object to access the presenter object. No decoration and less issues. The presenter() object also takes care of caching the presenter object, so you only instantiate a presenter per model once.

So I made my changes on top the active_decorator, and called it lulalala_presenter (all the good names are taken already).

Usage

First install by putting this in the Gemfile:

gem 'lulalala_presenter'

Say you want to add a title() helper method for auction, which takes care of truncation and html cleansing. I simply create a AuctionPresenter class. Remember to subclas it with LulalalaPresenter::Base:

# app/presenters/auction_presenter.rb
module AuctionPresenter < LulalalaPresenter::Base
  def title
    h.truncate(model.body, length: 40)
  end
end

And then in the view I call it like this

<%= @auction.presenter.title %>

Less magic

I think it is good to start with less magic, but allow user to add magic if they want more convenience.

By default you use h to access all the build-in helper methods, and model to access the model object. If you are lazy you can choose to add stuff in your presenter so you can type less. For example you can delegate a few model attributes:

delegate :body, to: model

You can probably also automatically delegate everything to models using other delegation libraries.

For skipping typing h, you can mix-in active_decorator's helper module so it will redirect calls to view-context using method_missing.

Conclusion

At the end I quite like my results. For the top example, now I have

@auction.presenter.title
@board.presenter.title
@category.presenter.title

Having to type .presenter when calling helpers isn't so bad as I first thought. This makes it very obvious that the method belongs to presenter, so you won't ever mix up a helper methods with model methods.

My two goals are also fulfilled. If I need an og_image_tag helper for my auction, I immediately know I should put it in the AuctionPresenter, and I can name it without worrying about prefixing model names.

← Apply SEO-friendly url only to show path RubyKaigi 2015 感想 →
 
comments powered by Disqus