Я создаю драгоценный камень, который перехватывает поведение рендеринга Rails по умолчанию. Вместо визуализации представления он ищет вспомогательный метод, соответствующий имени действия текущего контроллера; затем он использует возвращаемое значение вспомогательного метода для создания ответа HTML.
Например, когда вызывается действие show
объекта ProductsController
, драгоценный камень вызовет метод show
объекта ProductsHelper
. Затем он преобразует возвращаемое значение этого метода в HTML и отображает его.
Вот соответствующий код из моего гема. Он работает внутри контекста контроллера:
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
Проблема возникает в первой строке условного оператора.
Как видите, я привязываю вспомогательный метод к view_context
. Опять же, этот код выполняется в контроллере, так что это view_context
контроллера. Однако вид view_context
у них другой (я сравнил их object_id
). Я понимаю, что это ожидаемая разница в любом приложении Rails — видимо, контроллер и представление не должны быть одинаковыми view_context
. (Я убедился, что мой драгоценный камень не вносит такой разницы.)
Хотя контроллеры и представления должны иметь разные view_context
, хелперы и представления должны иметь одинаковые view_context
. Им требуется один и тот же контекст, чтобы иметь общее состояние, необходимое для поддержки таких функций, как content_for
.
Поэтому я считаю, что вспомогательный метод должен быть привязан к view_context
, который в конечном итоге будет использоваться для рендеринга страницы, макета приложения и всего остального. (Полагаю, это что-то вроде проблемы с курицей и яйцом, поскольку мне нужна ссылка на этот контекст, прежде чем я позвоню render
!)
Я ищу способ привязать вспомогательный метод к правильному view_context
. Я также открыт для достижения той же функциональности другим способом, который вообще не вызывает этой проблемы.
@HolgerJust Мне пришло в голову определить новый обработчик шаблона, но необходимость вызова render partial: ...
вместо вызова вспомогательного метода противоречит цели.
Я вижу два решения: создать компонент или сделать обработчик шаблона.
Этот компонент получит экземпляр view_context
при рендеринге:
class Hiccdown::Component
def initialize(helper_module, action_name)
@helper_module = helper_module
@action_name = action_name
end
def render_in(view_context)
Hiccdown.to_html(
@helper_module.instance_method(@action_name).bind_call(view_context)
)
end
def format
:html
end
end
Обновите метод рендеринга:
def render_helper_method action_name, options = {}
helper_module = "#{self.class.name.gsub('Controller', '')}Helper".constantize
if helper_module.method_defined? action_name
original_render(Hiccdown::Component.new(helper_module, action_name), options)
else
original_render({ action: action_name }.merge(options))
end
end
# app/helpers/home_helper.rb
module HomeHelper
def show
# this should work now
content_for :header do
"header"
end
[:p, "content"]
end
end
https://guides.rubyonrails.org/layouts_and_rendering.html#rendering-objects
Поскольку вы создаете язык шаблонов, было бы неплохо иметь:
# config/initializers/hiccdown.rb
# render hiccdown: [:p, "content"]
ActiveSupport.on_load(:action_controller) do
ActionController::Renderers.add :hiccdown do |source, options|
render html: Hiccdown.to_html(source).html_safe, layout: options[:layout]
end
end
# render .hiccdown templates
class HiccdownHandler
def self.call(template, source)
%{ Hiccdown.to_html(#{source}.compact) }
end
end
ActiveSupport.on_load(:action_view) do
ActionView::Template.register_template_handler :hiccdown, HiccdownHandler
end
Пример:
class HomeController < ApplicationController
def show
render hiccdown: [:h1, "title"]
end
end
или создайте шаблон:
# app/views/home/show.html.hiccdown
[
content_for(:title, "foo"),
[:h1, "title"]
]
1. Предложенный вами компонент решает проблему, спасибо. 2. Я не собираюсь создавать новый обработчик шаблонов (см. мой ответ Хольгеру). Но, как вы знаете, в вашем обработчике шаблона отсутствует способ заставить content_for
работать. Например, шаблон hiccdown может содержать: content_for(:title, 'foo'); [:h1, 'heading']
Я думаю, запуск instance_eval
на source
перед обработкой результата, поскольку Hiccdown должен работать (все в пределах heredoc).
@DennisHackethal да, это может сработать. я исправил свой пример, чтобы он работал с content_for
. %{ Hiccdown.to_html(#{source}.compact) }
и оберните шаблон в массив.
Не было бы проще определить новый обработчик шаблона (т. е. отличный от ERB, HTML или HAML), который вызывает вашего помощника, а не перезаписывать/обезьянничать, как работает фактический рендеринг в Rails? На самом деле это поддерживается Rails через ActionView::Template::Handlers#register_template_handler Аналогичным примером может быть работа шаблона Builder.