Work in progress
To try to clean up controllers, keep them lean, handle a variety of rendering variants/complexity, and fit into the larger Rails MVC flow; we should have a template handler for CableReady.
## railtie.rb
initializer("cable_ready.template_renderer") do
ActiveSupport.on_load :action_view do
ActionView::Template.register_template_handler :cablecar, CableCarTemplate
end
end
# cable_car_template.rb
# frozen_string_literal: true
class CableCarTemplate
attr_accessor :source
def initialize(&block)
self.source = block
end
def render_in(view_context)
source.call
view_context.controller.render(
cable_ready: view_context.cable_car
)
end
def format
:json
end
def self.call(template, source = nil)
src = source || template.source
<<~RUBY
__cablecar_template__ = CableCarTemplate.new do
#{src}
end
__cablecar_template__.render_in(self).to_s
RUBY
end
end
CableCarTemplate uses Rails' render_in support to render the object using the given view_context.
render_in is called, we make sure to call the source so that it is evaluated, then we reach back into the view context's controller to use the predefined renderer from CableReady, to keep in line with the main app itselfCableCarTemplate.call method takes in the template/source, coalesces it into a src that we can pass into an instance of CableCarTemplate, which we then call render_in using the ActionView context:cablecar, so the file format would[template].cable_ready.cablecar
jbuilder,# example.cable_ready.cablecar
# frozen_string_literal: true
dialog_id = dom_id(@organization_deal, :new_update)
html_string = controller.render_to_string(ExampleComponent.new(
open: true,
id: dialog_id
))
cable_car.append(
html: html_string,
selector: "#example",
)
With a template renderer like this, we can use Kasper Timm Hansen's UI Context and Rails Variants approach, using the default Rails rendering pipelines:
def example
# ...
respond_to do |format|
format.html
format.cable_ready
end
end
# example.cable_ready.cablecar
# frozen_string_literal: true
dialog_id = dom_id(@organization_deal, :new_update)
html_string = controller.render_to_string(ExampleComponent.new(
open: true,
id: dialog_id
))
cable_car.append(
html: html_string,
selector: "#example",
)
# example.cable_ready+context_menu.cablecar
# frozen_string_literal: true
html_string = controller.render_to_string(ContextMenu::ExampleComponent.new)
cable_car.replace(
html: html_string,
selector: "#context-menu",
)
This means that we can write numerous view approaches without getting bogged down with writing multiple controller actions. Plus, since these are named routes, they're easy to onboard and debug, no hidden incantations!
Extending the approach, forms that need to generate context-specific responses can pass along the variant as a hidden attribute:
<%= hidden_field_tag :variant, request.variant %>