Reputation: 2346
I've been researching the 'recommended' way to use Rails view helpers (e.g. link_to
, content_tag
) in a plain ruby class, such as a presenter. It seems there's very little information on this front and I wanted to get an idea of what the Stack community thought.
So, the options we have are.. (note I'm using Rails 4, and am less concerned about older versions)
Include the required modules manually
This is probably the cleanest way, since only the helpers needed are included. However I have found this method to not work in some cases, as the usual view context provided in plain Rails helpers is configured for the current request. url_for
wouldn't know about the current request for example, so the host might not match.
class MyPresenter
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::CaptureHelper
def wrapped_link
content_tag :div, link_to('My link', root_url)
end
end
Use ActionController::Base.helpers
Since Rails 3, ActionController::Base
has included a helpers
method to access the current view context. I believe the view context provided by this method is configured as it would be in a rails helper, but I might be wrong. There's not really any documentation about this which seems worrying, but it does work quite well in practice.
class MyPresenter
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
protected
def h
ActionController::Base.helpers
end
end
I believe this view context can also be mixed in with include
, but the rails view helpers have hundreds of methods and it feels dirty to include them all indiscriminately.
Inject the view context when calling the presenter
Finally, we could just pass the view context to the class when it's initialized (or alternatively in a render
method)
class MyPresenter
attr_accessor :context
alias_method :h, :context
def initialize(context)
@context = context
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
end
class MyController < ApplicationController
def show
# WARNING - `view_context` in a controller creates an object
@presenter = MyPresenter.new(view_context)
end
end
Personally I tend to lean towards the latter two options, but with no definitive answer from the Rails team (that I've been able to find) I felt a bit unsure. Who better to ask than Stack!
Upvotes: 28
Views: 6404
Reputation: 178
I would go with the mix of the second and third option, something like:
class MyPresenter
def initialize(helpers)
@h = helpers
end
def wrapped_link
h.content_tag :div, h.link_to('My link', h.root_url)
end
private
attr_reader :h
end
Your second option require all your unit tests to be stubbed as ActionController::Base.helpers
which maybe isn't a good option and your third option you're using a huge context to access just some methods.
Upvotes: 7
Reputation: 1886
I would really make that dependent on what kind of methods you use. If it's just the basics like content_tag
etc. I would go for the ActionController::Base.helpers
way. It is also possible to call some helpers directly, e.g. for paths inside models I almost always use something along the lines of Rails.application.routes.url_helpers.comment_path
.
For controller-specific stuff the third option might be useful, but personally the "pure" way seems nicer. Draper has an interesting approach too: They save the view_context
for the current request and then delegate the calls to h
-helpers to it: https://github.com/drapergem/draper/blob/master/lib/draper/view_context.rb
It really is just a matter of preference. I would never include all helpers at once, as you already said. But the second option is quite nice if you want to build the presentation layer yourself without using a gem like Draper or Cells.
Upvotes: 2