Учебник по расширению «скрытых» моделей драгоценных камней
Задний план
Мы уже много лет используем удивительную жемчужину форума Thredded на Notebook.ai. Это полнофункциональный встроенный движок Rails, который мгновенно активирует форум в вашем приложении Rails, и мы видели огромное его использование в нашем маленьком мире писателей: было создано более 20 000 тем и более двух миллионов ответов, размещенных в этих темах.
В следующем выпуске Notebook.ai мы добавим общедоступный поток событий для пользователей. Пользователи могут подписываться на других пользователей и делиться персонажами, локациями, предметами и вселенными, которые они создали, со своими подписчиками.

Чтобы держать пользователей в курсе обсуждений Thredded, которые создают их друзья, мы также добавляем в ленту новые темы Thredded. Для этого мы расширяем нативную модель Thredded::Topic обратным вызовом after_create, который создает потоковые события.
Выполнение
Чтобы добавить обратный вызов after_create в модель, к которой у нас нет прямого доступа (например, в геме), нам нужны две вещи.
- Нам нужно расширение модели, которое мы поместим в
lib/extensions/thredded/topic.rb, и - Нам нужно расширить модель этим расширением, что мы и сделаем в
config/initializers/thredded.rb.
Расширение
Мы используем способ Rails 6 для создания Concern, с помощью которого мы можем расширить нашу модель. Это просто определяет триггер after_create и определение функции. В приведенном ниже коде также добавлено несколько других методов, которые я использую в приложении (icon и color), но вы можете добавить все, что будет включать типичный Concern.
Как видите, у нас есть полный доступ ко всем атрибутам модели в этом after_create, что упрощает создание события потока с нужной информацией!
После написания расширения нам также необходимо расширить с его помощью модель Thredded::Topic.
Мы делаем это в конце инициализатора config/initializers/thredded.rb, который мы уже используем, со следующим кодом:
Код довольно прост, но имеет несколько нюансов, которые Виктор Леонг очень хорошо объясняет в сообщении в блоге, из которого взят этот код.
И это все! Каждый раз, когда создается новый Thredded::Topic, он запускает after_create и создает ContentPageShare, который наш поток перечисляет для отображения.
Интеграция прямых ссылок с потоком
Это все, и хорошо, но у нашего варианта использования есть еще одна проблема, которую нужно решить: полиморфное связывание с темами Thredded из потока.
Прямо сейчас мы используем полиморфное поле content (в частности, поля content_type и content_id), чтобы использовать встроенный помощник Rails polymorphic_path(content) для всех страниц нашей записной книжки; это позволяет нам иметь единую ссылку в представлении, которая может вести к персонажам, локациям, зданиям, существам и т. д., даже если все они являются отдельными моделями.
Это становится неуместным, когда модель Thredded::Topic, по двум причинам:
- Ссылка, на которую мы хотим отправить пользователей, находится внутри именованного движка (например, вместо
main_app.characters_path(character)нам нужно использовать что-то вродеthredded.topics_path(topic). - Фактический путь Thredded, который мы используем, требует больше информации для его построения, чем просто идентификатор темы. (В частности, маршрут
messageboard_topicимеет следующий формат:GET /:messageboard_id/:id(/page-:page)(.:format)
Я не смог найти «волшебный» метод, который можно было бы определить в самой модели Topic, чтобы вручную установить маршрут к экземплярам этой модели (для использования polymorphic_path). Посещение страницы дало мне NoMethodError для сгенерированного полиморфного маршрута.

Поэтому я сделал кое-что немного странное.
Это сработало, но я хотел бы услышать от читателей, есть ли здесь лучший подход.
Я заметил, что маршрут, который Rails использует по умолчанию для Thredded::Topic, переданного в polymorphic_path, не привязан к thredded — он неожиданно ищет topic_path в пространстве имен main_app вместо движка, в котором определена модель. (Примечание: это пахнет ошибками коллизий; я я рад, что у меня еще не было моей собственной модели с именем Topic!).
Поэтому я создал ThreddedProxyController, который принимает слаг темы, ищет в нем фактическую модель Thredded::Topic, а затем строит полный маршрут Thredded, чтобы перенаправить пользователя.
Я добавил его в config/routes.rb и обязательно назвал маршрутtopic, чтобы маршрут по умолчанию, который Rails использовал до — main_app.topic_path — направлялся к нему.
get '/topic/:slug', to: 'thredded_proxy#topic', as: :topic
Это определяет topic_path в основном пространстве имен приложения, которое успешно маршрутизируется из маршрута по умолчанию, созданного Rails из исходного вызова polymorphic_path выше: main_app.topic_path .
Вот как выглядит прокси-контроллер:
И теперь все просто работает!
Полиморфный столбец content на ContentPageShares может продолжать функционировать независимо от того, указывает ли он на один из многих типов страниц в Notebook.ai или на Thredded::Topic, и маршруты для любого из них строятся без проблем.
Это позволяет нам добавлять новые темы в ленту активности, при нажатии на которую пользователи переходят непосредственно к рассматриваемой теме!
