안녕하세요 여러분,
저는 디스코르스(Discourse)에서 사용자가 복권 주제에 답변을 달아 참여할 수 있는 복권 플러그인을 개발 중입니다. 충분한 고지를 드립니다: 저는 프로그래머가 아닙니다 - Claude Code를 사용하여 AI의 도움으로 이 프로젝트를 전적으로 구축하고 있으므로, 근본적인 개념을 놓치고 있을 수 있습니다. 기본 기능은 작동하지만, 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] 📡 Publishing MessageBus update:
Channel: /lottery/27
Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed
프론트엔드 (JavaScript)
디코레이터 (discourse-lottery-decorator.gjs):
api.decorateCookedElement((cooked, helper) => {
const post = helper.getModel();
if (!post?.lottery_data) return;
// 이미 장식되었는지 확인 (AI가 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;
// AI가 콜백을 수동으로 바인딩하도록 제안
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 message received!');
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) {
// stats를 포함한 모든 속성 업데이트
this.stats = lottery.stats || {};
// ... 다른 속성들
}
}
문제점
참가자의 페이지만 실시간으로 업데이트되고, 다른 시청자의 페이지는 새로고침 없이는 업데이트되지 않습니다.
정상 작동하는 부분:
사용자가 참여하기 위해 답변을 달면, 해당 사용자의 페이지에서 참가자 수 업데이트가 실시간으로 표시됩니다.
백엔드가 MessageBus 메시지를 성공적으로 게시합니다.
참가자의 페이지에서 MessageBus 메시지가 수신됩니다 (로그에 “
MessageBus message received!” 표시).
모델이 새로운 참가자 수로 업데이트됩니다 (로그에 0에서 1로 증가하는 수치가 표시됨).
작동하지 않는 부분:
복권 주제를 보고 있는 다른 시청자들은 실시간으로 수치가 업데이트되는 것을 볼 수 없습니다.
복권 생성자의 페이지가 업데이트되지 않습니다.
복권 주제가 열려 있는 다른 사용자의 페이지도 업데이트되지 않습니다.- 모두 업데이트된 참가자 수를 보려면 페이지를 수동으로 새로고침해야 합니다.
또한 콘솔에서 컴포넌트가 여러 번 파괴되고 재생성되는 것을 확인할 수 있습니다:
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
이는 decorateCookedElement가 여러 번 트리거되어 컴포넌트가 파괴되고 재생성되며, 이로 인해 다른 시청자의 MessageBus 구독이 깨지는 것일 수 있음을 시사합니다.
질문
-
cooked 요소에
data-decorated속성을 사용하는 것이 중복 장식을 방지하는 올바른 방법인가요? AI가 이 접근법을 제안했지만, 이것이 권장되는 패턴임을 확인하는 공식 문서를 찾지 못했습니다. -
tracked 모델 속성이 변경되고 있음에도 UI가 업데이트되지 않는 이유는 무엇인가요?
@tracked _stats는this.stats = lottery.stats를 통해 업데이트될 때 다시 렌더링을 트리거해야 하지만, 표시되는 참가자 수는 그대로입니다.@tracked사용법에 문제가 있는 것인가요? -
전혀 다른 접근법을 사용해야 하나요? AI에게 discourse-calendar 패턴을 따르라고 요청했지만, 디스코르스가 실시간 업데이트를 처리하는 방식에 대해 근본적으로 놓치고 있는 것이 있을 수 있습니다.
-
원래
lotteryNode를 제거하는 것이 문제의 원인가요? AI는 처음에는 제거하지 않는 것을 시도했지만, 그렇게 하면 CSS(.cooked > .discourse-lottery { display: none; })에 의해 복권 카드가 숨겨지는 문제가 발생했습니다. 더 나은 패턴이 있을까요?
저는 프로그래머가 아니므로 기본적인 질문을 하고 있을 수 있습니다 - 문서나 예제에 대한 힌트를 주시면 정말 감사하겠습니다! 감사합니다!