본 솔루션은 Rails의 기본 기능을 활용하여 복잡한 웹소켓 관리 없이 실시간 협업 경험을 제공합니다.
Task 모델 설정
Task 모델에 locked_by (User 참조)와 locked_at (datetime) 컬럼을 추가하여 잠금 상태를 관리합니다.
ruby
class AddLockingToTasks < ActiveRecord::Migration[8.0]
def change
add_reference :tasks, :locked_by, foreign_key: { to_table: :users }
add_column :tasks, :locked_at, :datetime
end
end
Task 모델은 locked_by?, locked?, lock!, unlock! 메서드를 포함하여 잠금 로직을 처리합니다. 특히 after_update_commit 콜백을 통해 tasks Turbo Stream 채널로 변경 사항을 브로드캐스트하여 모든 연결된 클라이언트가 실시간으로 업데이트된 태스크 목록을 받도록 합니다.
컨트롤러 로직
TasksController의 edit 액션은 태스크가 이미 잠겨 있고 현재 사용자가 잠그지 않은 경우 다른 경로로 리다이렉트합니다. 유효한 경우 current_user로 태스크를 잠급니다. update 액션은 태스크가 성공적으로 업데이트되면 잠금을 해제합니다. 또한, 명시적인 잠금 해제를 위한 unlock 액션을 추가하여 현재 사용자가 잠근 경우에만 잠금을 해제하도록 합니다.
UI 잠금 상태 표시
_task.html.erb 파셜은 태스크의 잠금 상태에 따라 다른 UI를 렌더링합니다.
-
태스크가 잠겨 있으면
locked_by정보를 표시하고, “Edit” 버튼을 비활성화합니다. -
tasks/index.html.erb뷰는<%= turbo_stream_from "tasks" %>를 통해tasks채널을 구독하여, 어떤 태스크의 잠금 상태가 변경되더라도 모든 연결된 클라이언트가 즉시 업데이트를 받습니다.
사용자 이탈 시 잠금 해제
사용자가 “저장” 또는 “취소”를 클릭하지 않고 탭을 닫거나 뒤로 가기 버튼을 누르는 경우에도 잠금이 자동으로 해제되어야 합니다. 이를 위해 StimulusJS 컨트롤러가 사용됩니다.
-
task_lock_controller.js는turbo:before-visit(앱 내비게이션 시) 및beforeunload(탭 닫기, 페이지 새로고침 시) 이벤트를 수신합니다. -
이벤트 발생 시,
navigator.sendBeacon()을 사용하여/tasks/:id/unlock엔드포인트로 비동기 POST 요청을 보냅니다.sendBeacon()은 페이지 언로드 시 일반 AJAX 요청이 취소되는 것과 달리, 데이터가 안정적으로 전송되도록 설계되었습니다.