diff --git a/app/channels/board_channel.rb b/app/channels/board_channel.rb index 010c261..d6e27e9 100644 --- a/app/channels/board_channel.rb +++ b/app/channels/board_channel.rb @@ -1,34 +1,28 @@ class BoardChannel < ApplicationCable::Channel def subscribed board = Board.find_by(id: params[:board_id]) - unless board reject return end - # 権限チェック - # ゲーム参加者を取得(current_userから) current_participant = board.game.participants.find_by(user: current_user) - # 参加権限があるか(メンバー、または終了済み、または公開ボード) - # コントローラの判定ロジックと合わせる - # 共通掲示板は誰でも購読OK(ただしゲーム参加者に限る) + # ゲーム参加者でなければ一律拒否 + unless current_participant + reject + return + end - if board.global? - stream_from "board_#{board.id}" if current_participant - elsif board.is_public? - stream_from "board_#{board.id}" if current_participant - elsif board.member?(current_participant) + # 権限判定:共通掲示板/公開掲示板/メンバー/履歴モードのいずれかで許可 + if board.global? || board.is_public? || board.member?(current_participant) || board.history_mode? stream_from "board_#{board.id}" - elsif board.history_mode? - stream_from "board_#{board.id}" if current_participant else reject end end def unsubscribed - # Any cleanup needed when channel is unsubscribed + # cleanup end end diff --git a/app/controllers/board_memberships_controller.rb b/app/controllers/board_memberships_controller.rb index 65f4974..51b0536 100644 --- a/app/controllers/board_memberships_controller.rb +++ b/app/controllers/board_memberships_controller.rb @@ -1,4 +1,6 @@ class BoardMembershipsController < ApplicationController + include BoardAccessible + before_action :require_login before_action :set_game before_action :set_board @@ -25,11 +27,6 @@ class BoardMembershipsController < ApplicationController @board.board_memberships.create!(participant_id: target_participant_id, joined_at: Time.current) end - # メンバー構成が変わったので、他の掲示板と重複していないかチェックが必要だが、 - # モデルのバリデーションは「作成時」を想定しているため、ここでは簡易チェックに留めるか、 - # あるいはバリデーションエラーをハンドリングする。 - # ここではsave成功/失敗で判断 - redirect_to game_board_path(@game, @board), notice: "メンバーを追加しました" rescue ActiveRecord::RecordInvalid => e redirect_to game_board_path(@game, @board), alert: "メンバー追加に失敗しました: #{e.message}" @@ -49,18 +46,4 @@ class BoardMembershipsController < ApplicationController redirect_to game_board_path(@game, @board), alert: "退出に失敗しました" end end - - private - - def set_game - @game = Game.find(params[:game_id]) - end - - def set_board - @board = @game.boards.find(params[:board_id]) - end - - def set_current_participant - @current_participant = @game.participants.find_by(user: current_user) - end end diff --git a/app/controllers/board_posts_controller.rb b/app/controllers/board_posts_controller.rb index d9a6e51..4359bd1 100644 --- a/app/controllers/board_posts_controller.rb +++ b/app/controllers/board_posts_controller.rb @@ -1,4 +1,6 @@ class BoardPostsController < ApplicationController + include BoardAccessible + before_action :require_login before_action :set_game before_action :set_board @@ -13,11 +15,9 @@ class BoardPostsController < ApplicationController @post = @board.board_posts.new(post_params) @post.participant = @current_participant - # フェーズ情報の付与 - # 最新のターン情報を取得してセットする - latest_turn = @game.turns.order(number: :desc).first - if latest_turn - @post.phase = latest_turn.phase + # フェーズ情報の付与(latest_turnアソシエーションを使用) + if @game.latest_turn + @post.phase = @game.latest_turn.phase end if @post.save @@ -29,18 +29,6 @@ class BoardPostsController < ApplicationController private - def set_game - @game = Game.find(params[:game_id]) - end - - def set_board - @board = @game.boards.find(params[:board_id]) - end - - def set_current_participant - @current_participant = @game.participants.find_by(user: current_user) - end - def post_params params.require(:board_post).permit(:body) end diff --git a/app/controllers/board_proposals_controller.rb b/app/controllers/board_proposals_controller.rb index 8d5d4cf..7f28dc9 100644 --- a/app/controllers/board_proposals_controller.rb +++ b/app/controllers/board_proposals_controller.rb @@ -1,4 +1,6 @@ class BoardProposalsController < ApplicationController + include BoardAccessible + before_action :require_login before_action :set_game before_action :set_board @@ -13,13 +15,9 @@ class BoardProposalsController < ApplicationController @proposal.proposer = @current_participant @proposal.status = "pending" - # フェーズ情報の保存 - latest_turn = @game.turns.max_by(&:number) - if latest_turn - @proposal.phase = latest_turn.phase - else - @proposal.phase = "S1901M" # 初期値 - end + # フェーズ情報の保存(latest_turnアソシエーションを使用) + latest_turn = @game.latest_turn + @proposal.phase = latest_turn&.phase || "S1901M" if @proposal.save redirect_to game_board_path(@game, @board), notice: "提案を作成しました" @@ -31,12 +29,6 @@ class BoardProposalsController < ApplicationController def update @proposal = @board.board_proposals.find(params[:id]) - # 承認・拒否権限: - # 提案者本人以外が承認/拒否できるべきか、あるいは全員できるべきか? - # 通常は「相手」が承認するものだが、多国間の場合は? - # ここでは「メンバーであれば誰でもステータス変更可能」とする(簡易実装) - # ただし、提案者本人が自分で承認するのは変なので、他者のみとするのがベター - unless @board.member?(@current_participant) return redirect_to game_board_path(@game, @board), alert: "権限がありません" end @@ -44,7 +36,6 @@ class BoardProposalsController < ApplicationController new_status = params[:board_proposal][:status] if %w[accepted rejected].include?(new_status) @proposal.update!(status: new_status) - # ログにも残す(投稿として自動投稿しても良いが、今回はステータス変更のみ) redirect_to game_board_path(@game, @board), notice: "提案を#{new_status == 'accepted' ? '承認' : '拒否'}しました" else redirect_to game_board_path(@game, @board), alert: "不正なステータスです" @@ -53,18 +44,6 @@ class BoardProposalsController < ApplicationController private - def set_game - @game = Game.find(params[:game_id]) - end - - def set_board - @board = @game.boards.find(params[:board_id]) - end - - def set_current_participant - @current_participant = @game.participants.find_by(user: current_user) - end - def proposal_params params.require(:board_proposal).permit(:body) end diff --git a/app/controllers/boards_controller.rb b/app/controllers/boards_controller.rb index 111e572..4b29639 100644 --- a/app/controllers/boards_controller.rb +++ b/app/controllers/boards_controller.rb @@ -1,4 +1,6 @@ class BoardsController < ApplicationController + include BoardAccessible + before_action :require_login before_action :set_game before_action :set_board, only: [ :show, :toggle_public ] @@ -14,21 +16,19 @@ class BoardsController < ApplicationController if @game.status == "finished" # 履歴モード:かつて参加していた掲示板をすべて表示 - # (共通掲示板は全員参加扱いなので含む) - @boards = @game.boards.select { |b| b.member?(@current_participant) || b.global? } + @boards = @game.boards.includes(:participants, :board_posts, :board_memberships) + .select { |b| b.member?(@current_participant) || b.global? } .sort_by { |b| [ b.global? ? 0 : 1, b.created_at ] } else return redirect_to game_path(@game), alert: "参加者ではないためアクセスできません" unless @current_participant - # 進行中:参加中の掲示板のみ - @boards = @current_participant.boards.includes(:participants, :board_posts).order(created_at: :desc) - # 共通掲示板がまだ紐付いていない場合のフォールバック(通常は作成時に紐づくはずだが念の為) + # 進行中:参加中の掲示板のみ(eager load で N+1 防止) + @boards = @current_participant.boards.includes(:participants, :board_posts, :board_memberships).order(created_at: :desc) + # 共通掲示板のフォールバック global_board = @game.boards.global.first if global_board && !@boards.include?(global_board) - # 表示上追加するが、DBには保存しない(閲覧時に参加処理をする作りも考えられるが、createタイミングで保証する前提) @boards = [ global_board ] + @boards end - # 先頭に共通掲示板を持ってくる @boards = @boards.sort_by { |b| b.global? ? 0 : 1 } end end @@ -36,7 +36,6 @@ class BoardsController < ApplicationController def show @current_participant = @game.participants.find_by(user: current_user) - # アクセス制御 # アクセス制御 access_allowed = false @@ -72,11 +71,12 @@ class BoardsController < ApplicationController end @posts = @board.board_posts.includes(:participant).order(created_at: :desc) + @proposals = @board.board_proposals.includes(:proposer).order(created_at: :desc) @new_post = BoardPost.new @new_proposal = BoardProposal.new @active_members = @board.active_memberships.includes(:participant).map(&:participant) - # メンバー追加用:招待可能なプレイヤー一覧(自分と既に参加している人を除く) + # メンバー追加用:招待可能なプレイヤー一覧 if @board.negotiation? && !@board.history_mode? existing_member_ids = @board.board_memberships.where(left_at: nil).pluck(:participant_id) @candidates = @game.participants.where.not(id: existing_member_ids).where.not(power: nil) @@ -135,35 +135,17 @@ class BoardsController < ApplicationController private - def set_game - @game = Game.find(params[:game_id]) - end - - def set_board - @board = @game.boards.find(params[:id]) - end - def board_params - # パラメータは特にないが、将来的にタイトルなど追加するかも params.fetch(:board, {}).permit(:is_public) end def build_diplomacy_matrix - # 外交関係マップ構築 - # Matrix: { "FRANCE" => { "GERMANY" => true, "ENGLAND" => false }, ... } - # 全組み合わせの初期化 powers = @game.participants.where.not(power: nil).pluck(:power) matrix = {} powers.each { |p| matrix[p] = [] } - # 参加中の全掲示板を走査(自分が参加していないものも含めたいが、 - # DB設計上 board_memberships を見る必要がある) - # is_publicなもの、または自分が参加しているものについて情報を開示? - # 仕様書には「どの国とどの国が掲示板を持っているか」とあるが、 - # 秘密交渉なので、本来は「自分が見えている範囲」あるいは「公開宣言されたもの」のみ? - # ここでは「自分が参加している掲示板」および「公開された掲示板」の関係を表示する。 - - visible_boards = @game.boards.negotiation.includes(:participants) + # 自分が参加している掲示板・公開された掲示板の関係を表示 + visible_boards = @game.boards.negotiation.includes(:participants, :board_memberships) visible_boards.each do |board| # アクセス権チェック(簡易) diff --git a/app/controllers/concerns/board_accessible.rb b/app/controllers/concerns/board_accessible.rb new file mode 100644 index 0000000..728b683 --- /dev/null +++ b/app/controllers/concerns/board_accessible.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# 掲示板関連コントローラの共通ロジック +# set_game, set_board, set_current_participant を提供する +module BoardAccessible + extend ActiveSupport::Concern + + private + + def set_game + @game = Game.find(params[:game_id]) + end + + def set_board + @board = @game.boards.find(params[:board_id] || params[:id]) + end + + def set_current_participant + @current_participant = @game.participants.find_by(user: current_user) + end +end diff --git a/app/models/turn.rb b/app/models/turn.rb index 4c168bd..97c9fff 100644 --- a/app/models/turn.rb +++ b/app/models/turn.rb @@ -1,6 +1,19 @@ class Turn < ApplicationRecord belongs_to :game + SEASON_MAP = { "S" => "春", "F" => "秋", "W" => "冬" }.freeze + PHASE_TYPE_MAP = { "M" => "移動", "R" => "撤退", "A" => "調整" }.freeze + + # フェーズコードを日本語表示に変換(例: "S1901M" → "1901年 春 (移動)") + def display_phase + return nil unless phase.present? && phase.length >= 6 + + year = phase[1..4] + season = SEASON_MAP[phase[0]] || "" + type = PHASE_TYPE_MAP[phase[-1]] || "" + "#{year}年 #{season} (#{type})" + end + # 特定の国の実行可能な命令を取得 # 例: turn.possible_orders_for("FRANCE") def possible_orders_for(power_name) diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index 62ec4ef..9b70140 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -65,7 +65,6 @@
-
@@ -120,7 +119,7 @@ 条約・提案
- <% @board.board_proposals.order(created_at: :desc).each do |proposal| %> + <% @proposals.each do |proposal| %>
diff --git a/app/views/games/_header.html.erb b/app/views/games/_header.html.erb index 9d392c3..7cfc6c6 100644 --- a/app/views/games/_header.html.erb +++ b/app/views/games/_header.html.erb @@ -81,42 +81,16 @@
<% # ゲーム画面(games/show)と統一されたデザインでターン情報を表示 %> -<% - target_turn = defined?(display_turn) && display_turn ? display_turn : game.latest_turn -%> +<% target_turn = defined?(display_turn) && display_turn ? display_turn : game.latest_turn %> <% if target_turn && target_turn.phase.present? %> - <% - # フェーズ名パース (例: S1901M) - phase = target_turn.phase - year = phase[1..4] - season_code = phase[0] - type_code = phase[-1] - - season = case season_code - when 'S' then '春' - when 'F' then '秋' - when 'W' then '冬' - else '' - end - - type = case type_code - when 'M' then '移動' - when 'R' then '撤退' - when 'A' then '調整' - else '' - end - - display_date = "#{year}年 #{season} (#{type})" - %> -
-

<%= display_date %>

+

<%= target_turn.display_phase %>

ターン: <%= target_turn.number %> | - フェーズ: <%= phase %> + フェーズ: <%= target_turn.phase %>
@@ -131,3 +105,4 @@
<% end %> +