I’m building a gem that intercepts Rails’ default render behavior. Instead of rendering a view, it looks for a helper method that corresponds to the current controller’s action name; it then uses the helper method’s return value to construct an HTML response.
For instance, when the show
action of the ProductsController
is called, the gem will call the show
method on the ProductsHelper
. It then turns that method’s return value into HTML and renders it.
Here’s the relevant code from my gem. It runs inside a controller context:
def render_helper_method action_name, options = {}
helper_module = "#{self.class.name.gsub('Controller', '')}Helper".constantize
if helper_module.instance_methods(false).include?(action_name.to_sym)
content = helper_module.instance_method(action_name).bind(view_context).call
original_render({ html: Hiccdown::to_html(content).html_safe, layout: true }.merge(options))
else
original_render({ action: action_name }.merge(options))
end
end
The problem occurs in the first line of the conditional.
As you can see, I bind the helper method to the view_context
. Again, this code runs in a controller, so this is the controller’s view_context
. The view’s view_context
is different, however (I have compared their object_id
s). I understand this is an expected difference in any Rails app – apparently, the controller and view are not supposed to share the same view_context
. (I have verified that my gem does not introduce this difference.)
While controllers and views are supposed to have different view_context
s, helpers and views are supposed to share the same view_context
. They require the same context so that they have the shared state needed to support functionality such as content_for
.
Therefore, I think the helper method should be bound to the view_context
that is eventually used to render the page, application layout and all. (I suppose it’s a bit of a chicken-and-egg problem since I need a reference to that context before I call render
!)
I’m looking for a way to bind the helper method to the correct view_context
. I’m also open to achieving the same functionality in a different way that doesn’t cause this problem in the first place.
Best Answer
I see two solutions, make a component or make a template handler.
Component
This component will receive
view_context
instance when rendering:Update your render method:
https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-objects
Template handler
Since you're making a template language, this would be nice to have:
Example:
or create a template: