Всем привет,
Я разрабатываю плагин для лотереи на платформе Discourse, где пользователи могут участвовать, отвечая на тему лотереи. Полное раскрытие информации: я не программист — я создаю этот плагин полностью с помощью ИИ (Claude Code), поэтому, возможно, упускаю какие-то фундаментальные концепции. Базовая функциональность работает, но у меня возникают проблемы с обновлениями в реальном времени через MessageBus. Буду благодарен за любые советы по правильному подходу.
Чего я пытаюсь достичь
- Создать пост лотереи с использованием BBCode
[lottery]...[/lottery] - Отображать карточку лотереи в посте с количеством участников
- При ответах пользователей для участия все зрители должны видеть обновление количества участников в реальном времени без перезагрузки страницы
- Аналогично тому, как плагин discourse-calendar обновляет информацию о событиях
Текущая реализация
Бэкенд (Ruby)
Модель Lottery (app/models/lottery.rb):
def publish_update!
channel = "/lottery/#{self.post.topic_id}"
message = { id: self.id }
MessageBus.publish(channel, message)
end
Модель LotteryParticipant (app/models/lottery_participant.rb):
after_commit :publish_lottery_update, on: [:create, :destroy]
private
def publish_lottery_update
self.lottery.publish_update!
end
Логи бэкенда подтверждают, что вызов MessageBus.publish выполняется успешно:
[Lottery] 📡 Публикация обновления MessageBus:
Канал: /lottery/27
Сообщение: {:id=>52}
[Lottery] ✅ MessageBus.publish завершено
Фронтенд (JavaScript)
Декоратор (discourse-lottery-decorator.gjs):
api.decorateCookedElement((cooked, helper) => {
const post = helper.getModel();
if (!post?.lottery_data) return;
// Проверка, был ли элемент уже декорирован (ИИ предложил использовать data-атрибут)
if (cooked.dataset.lotteryDecorated === "true") return;
const lotteryNode = cooked.querySelector(".discourse-lottery");
if (!lotteryNode) return;
const wrapper = document.createElement("div");
lotteryNode.before(wrapper);
const lottery = Lottery.create(post.lottery_data);
helper.renderGlimmer(
wrapper,
<template><DiscourseLottery @lotteryData={{lottery}} /></template>
);
lotteryNode.remove();
cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });
Компонент (discourse-lottery/index.gjs):
export default class DiscourseLottery extends Component {
@service messageBus;
@service lotteryApi;
constructor() {
super(...arguments);
const { lotteryData } = this.args;
if (lotteryData?.topicId) {
this.lotteryPath = `/lottery/${lotteryData.topicId}`;
this.lotteryData = lotteryData;
// ИИ предложил вручную привязать обратный вызов
this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);
registerDestructor(this, () => {
this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
});
}
}
async _onLotteryUpdate(msg) {
console.log('[Lottery Component] 🔔 Сообщение MessageBus получено!');
const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
this.lotteryData.updateFromLottery(updatedData);
}
}
Модель (models/lottery.js):
export default class Lottery {
@tracked _stats;
set stats(stats) {
this._stats = LotteryStats.create(stats || {});
}
updateFromLottery(lottery) {
// Обновление всех свойств, включая статистику
this.stats = lottery.stats || {};
// ... другие свойства
}
}
Проблема
Только страница участника обновляется в реальном времени, а страницы других зрителей не обновляются без перезагрузки.
Что работает:
Когда пользователь отвечает для участия, его собственная страница показывает обновление количества участников в реальном времени
Бэкенд успешно публикует сообщения MessageBus
Сообщение MessageBus получает страница участника (в логах видно “
Сообщение MessageBus получено!”)
Модель обновляется с новым количеством участников (в логах видно увеличение с 0 до 1)
Что не работает:
Другие зрители, наблюдающие за темой лотереи, не видят обновления количества в реальном времени
Страница создателя лотереи не обновляется
Страница любого другого пользователя, у которого открыта тема лотереи, не обновляется- Им всем приходится вручную перезагружать страницу, чтобы увидеть обновленное количество участников
Кроме того, я вижу, что компонент многократно уничтожается и создается заново в консоли:
[Lottery Component] ✅ Подписка MessageBus установлена
[Lottery Component] 🧹 Очистка MessageBus для темы: 27
[Lottery Component] ✅ Подписка MessageBus установлена
[Lottery Component] 🧹 Очистка MessageBus для темы: 27
Это указывает на то, что decorateCookedElement вызывается несколько раз, что приводит к уничтожению и повторному созданию компонентов, что, возможно, нарушает подписку MessageBus для других зрителей.
Вопросы
-
Является ли использование атрибута
data-decoratedдля элемента cooked правильным способом предотвращения дублирования декорирования? ИИ предложил этот подход, но я не смог найти официальную документацию, подтверждающую, что это рекомендуемый паттерн. -
Почему UI не обновляется, хотя отслеживаемое свойство модели изменяется?
@tracked _statsдолжно вызывать перерисовку при обновлении черезthis.stats = lottery.stats, но отображаемое количество участников остается прежним. Неужели что-то не так с моим использованием@tracked? -
Не стоит ли мне использовать совершенно другой подход? Я попросил ИИ следовать паттерну discourse-calendar, но, возможно, я упускаю что-то фундаментальное в том, как Discourse обрабатывает обновления в реальном времени.
-
Не является ли удаление исходного
lotteryNodeпричиной проблемы? ИИ изначально пытался не удалять его, но это приводило к тому, что карточка лотереи скрывалась из-за CSS (.cooked > .discourse-lottery { display: none; }). Есть ли лучший паттерн?
Поскольку я не программист, возможно, я задаю базовые вопросы — любые ссылки на документацию или примеры будут невероятно полезны! Спасибо!