class Board < ApplicationRecord belongs_to :game belongs_to :creator, class_name: "Participant", foreign_key: "created_by_participant_id", optional: true has_many :board_memberships, dependent: :destroy has_many :active_memberships, -> { where(left_at: nil) }, class_name: "BoardMembership" has_many :participants, through: :board_memberships has_many :board_posts, dependent: :destroy has_many :board_proposals, dependent: :destroy enum :board_type, { global: "global", negotiation: "negotiation" } validate :no_duplicate_member_combination, if: :negotiation? # メンバーかどうか判定(退出済みはfalse) def member?(participant) return false unless participant board_memberships.exists?(participant_id: participant.id, left_at: nil) end # 履歴モードかどうか(ゲーム終了後は常にtrue) def history_mode? game.status == "finished" end # 指定した参加者の未読件数を取得 def unread_count_for(participant) return 0 unless participant membership = board_memberships.find_by(participant_id: participant.id) # メンバーでない、または退出済みの場合は未読なしとする(あるいは全件とするか要検討だが、一旦0) return 0 unless membership && membership.active? last_id = membership.last_read_post_id || 0 board_posts.where("id > ?", last_id).count end private def no_duplicate_member_combination # 新規作成時のみチェック(更新時はメンバー変動があるため別途考慮が必要だが、今回は作成時のみ想定) # ただしメンバー追加時にもチェックが必要になる可能性があるため、Boardモデルのバリデーションとして定義 # 比較対象のメンバーIDリスト(ソート済み) current_member_ids = board_memberships.map(&:participant_id).sort # 同じゲーム内の他の交渉用掲示板を検索 game.boards.negotiation.where.not(id: id).each do |other_board| # 退出者を含めた全メンバー構成で比較するか、アクティブメンバーのみで比較するか # 仕様書「同じメンバー構成の掲示板は作成できない」 # ここでは「現在参加しているメンバー」の構成が重複しないようにする other_member_ids = other_board.board_memberships.where(left_at: nil).pluck(:participant_id).sort if other_member_ids == current_member_ids errors.add(:base, "同じメンバー構成の掲示板がすでに存在します") return end end end end