掲示板実装
This commit is contained in:
61
app/models/board.rb
Normal file
61
app/models/board.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
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
|
||||
21
app/models/board_membership.rb
Normal file
21
app/models/board_membership.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class BoardMembership < ApplicationRecord
|
||||
belongs_to :board
|
||||
belongs_to :participant
|
||||
|
||||
# 参加中かどうか(退出していない)
|
||||
def active?
|
||||
left_at.nil?
|
||||
end
|
||||
|
||||
# 退出処理
|
||||
def leave!
|
||||
update!(left_at: Time.current)
|
||||
end
|
||||
|
||||
# 既読位置を更新
|
||||
def mark_read!(post_id)
|
||||
# 既存の既読位置より新しい場合のみ更新(巻き戻り防止)
|
||||
# ただし今回は単純に最新をセットする形で良い
|
||||
update!(last_read_post_id: post_id)
|
||||
end
|
||||
end
|
||||
26
app/models/board_post.rb
Normal file
26
app/models/board_post.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class BoardPost < ApplicationRecord
|
||||
belongs_to :board
|
||||
belongs_to :participant
|
||||
|
||||
validates :body, presence: true
|
||||
|
||||
# 作成後にAction Cableで配信
|
||||
after_create_commit :broadcast_to_board
|
||||
|
||||
private
|
||||
|
||||
def broadcast_to_board
|
||||
# ボードごとのチャンネルに配信
|
||||
ActionCable.server.broadcast(
|
||||
"board_#{board_id}",
|
||||
{
|
||||
post_id: id,
|
||||
participant_id: participant.id,
|
||||
power: participant.power, # 国名
|
||||
body: body,
|
||||
phase: phase, # フェーズ情報
|
||||
created_at: created_at.in_time_zone("Asia/Tokyo").strftime("%Y-%m-%d %H:%M")
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
9
app/models/board_proposal.rb
Normal file
9
app/models/board_proposal.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class BoardProposal < ApplicationRecord
|
||||
belongs_to :board
|
||||
belongs_to :proposer, class_name: "Participant",
|
||||
foreign_key: "proposer_participant_id"
|
||||
|
||||
enum :status, { pending: "pending", accepted: "accepted", rejected: "rejected" }
|
||||
|
||||
validates :body, presence: true
|
||||
end
|
||||
@@ -2,6 +2,8 @@ class Game < ApplicationRecord
|
||||
has_many :turns, dependent: :destroy
|
||||
has_many :participants, dependent: :destroy
|
||||
has_many :users, through: :participants
|
||||
has_many :boards, dependent: :destroy
|
||||
has_one :latest_turn, -> { order(number: :desc) }, class_name: "Turn"
|
||||
|
||||
# パスワード保護
|
||||
has_secure_password :password, validations: false
|
||||
@@ -26,6 +28,9 @@ class Game < ApplicationRecord
|
||||
# ターンスケジュールバリデーション
|
||||
validate :validate_turn_schedule
|
||||
|
||||
# コールバック
|
||||
after_create :create_global_board
|
||||
|
||||
# ヘルパーメソッド
|
||||
def password_protected?
|
||||
password_digest.present?
|
||||
@@ -162,4 +167,8 @@ class Game < ApplicationRecord
|
||||
errors.add(:turn_schedule, "は0〜23の数値をカンマ区切りで入力してください(例: 0,18)")
|
||||
end
|
||||
end
|
||||
|
||||
def create_global_board
|
||||
boards.create!(board_type: "global", is_public: true)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
class Participant < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :game
|
||||
|
||||
|
||||
has_many :board_memberships, dependent: :destroy
|
||||
has_many :boards, through: :board_memberships
|
||||
has_many :board_posts, dependent: :destroy
|
||||
|
||||
# バリデーション
|
||||
validates :user_id, uniqueness: {
|
||||
validates :user_id, uniqueness: {
|
||||
scope: :game_id,
|
||||
message: "既にこのゲームに参加しています"
|
||||
}
|
||||
|
||||
validates :power, uniqueness: {
|
||||
|
||||
validates :power, uniqueness: {
|
||||
scope: :game_id,
|
||||
message: "この国は既に選択されています"
|
||||
}, allow_nil: true
|
||||
|
||||
validates :power, inclusion: {
|
||||
|
||||
validates :power, inclusion: {
|
||||
in: %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY],
|
||||
message: "無効な国です"
|
||||
}, allow_nil: true
|
||||
|
||||
Reference in New Issue
Block a user