Как установить контекст просмотра в Rails?

Я создаю драгоценный камень, который перехватывает поведение рендеринга 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. Я также открыт для достижения той же функциональности другим способом, который вообще не вызывает этой проблемы.

Не было бы проще определить новый обработчик шаблона (т. е. отличный от ERB, HTML или HAML), который вызывает вашего помощника, а не перезаписывать/обезьянничать, как работает фактический рендеринг в Rails? На самом деле это поддерживается Rails через ActionView::Template::Handlers#register_template_handler Аналогичным примером может быть работа шаблона Builder.

Holger Just 23.07.2024 11:03

@HolgerJust Мне пришло в голову определить новый обработчик шаблона, но необходимость вызова render partial: ... вместо вызова вспомогательного метода противоречит цели.

Dennis Hackethal 24.07.2024 04:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
109
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я вижу два решения: создать компонент или сделать обработчик шаблона.

Компонент

Этот компонент получит экземпляр 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).

Dennis Hackethal 24.07.2024 04:47

@DennisHackethal да, это может сработать. я исправил свой пример, чтобы он работал с content_for. %{ Hiccdown.to_html(#{source}.compact) } и оберните шаблон в массив.

Alex 24.07.2024 05:36

Другие вопросы по теме