Skip to content

Commit d1d732e

Browse files
committed
refactor(api): improve team comparison API controller code quality
Refactored the Api::V1::Analytics::TeamComparisonController to: - Reduce method complexity (AbcSize, CyclomaticComplexity) - Break down large methods into smaller, single-responsibility methods - Add comprehensive class documentation - Extract filtering logic into dedicated methods - Improve code readability and maintainability - Fix all MultilineBlockChain violations Changes: - Split build_matches_query into focused filter methods - Create dedicated helper methods for date range handling - Extract player statistics building into separate methods - Reduce method length from 13 to <10 lines each - Add predicate methods for better readability (date_range_params?)
1 parent da18903 commit d1d732e

4 files changed

Lines changed: 444 additions & 405 deletions

File tree

Lines changed: 151 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,151 @@
1-
class Api::V1::Analytics::TeamComparisonController < Api::V1::BaseController
2-
def index
3-
players = organization_scoped(Player).active.includes(:player_match_stats)
4-
matches = build_matches_query
5-
6-
comparison_data = build_comparison_data(players, matches)
7-
8-
render json: { data: comparison_data }
9-
end
10-
11-
private
12-
13-
def build_matches_query
14-
matches = organization_scoped(Match)
15-
16-
matches = apply_date_filter(matches)
17-
18-
matches = matches.where(opponent_team_id: params[:opponent_team_id]) if params[:opponent_team_id].present?
19-
20-
matches = matches.where(match_type: params[:match_type]) if params[:match_type].present?
21-
22-
matches
23-
end
24-
25-
def apply_date_filter(matches)
26-
if params[:start_date].present? && params[:end_date].present?
27-
matches.in_date_range(params[:start_date], params[:end_date])
28-
elsif params[:days].present?
29-
matches.recent(params[:days].to_i)
30-
else
31-
matches.recent(30)
32-
end
33-
end
34-
35-
def build_comparison_data(players, matches)
36-
{
37-
players: build_player_comparisons(players, matches),
38-
team_averages: calculate_team_averages(matches),
39-
role_rankings: calculate_role_rankings(players, matches)
40-
}
41-
end
42-
43-
def build_player_comparisons(players, matches)
44-
players.map do |player|
45-
build_player_stats(player, matches)
46-
end.compact.sort_by { |p| -p[:avg_performance_score] }
47-
end
48-
49-
def build_player_stats(player, matches)
50-
stats = PlayerMatchStat.where(player: player, match: matches)
51-
return nil if stats.empty?
52-
53-
{
54-
player: PlayerSerializer.render_as_hash(player),
55-
games_played: stats.count,
56-
kda: calculate_kda(stats),
57-
avg_damage: stats.average(:damage_dealt_total)&.round(0) || 0,
58-
avg_gold: stats.average(:gold_earned)&.round(0) || 0,
59-
avg_cs: stats.average(:cs)&.round(1) || 0,
60-
avg_vision_score: stats.average(:vision_score)&.round(1) || 0,
61-
avg_performance_score: stats.average(:performance_score)&.round(1) || 0,
62-
multikills: build_multikills(stats)
63-
}
64-
end
65-
66-
def build_multikills(stats)
67-
{
68-
double: stats.sum(:double_kills),
69-
triple: stats.sum(:triple_kills),
70-
quadra: stats.sum(:quadra_kills),
71-
penta: stats.sum(:penta_kills)
72-
}
73-
end
74-
75-
def calculate_kda(stats)
76-
total_kills = stats.sum(:kills)
77-
total_deaths = stats.sum(:deaths)
78-
total_assists = stats.sum(:assists)
79-
80-
deaths = total_deaths.zero? ? 1 : total_deaths
81-
((total_kills + total_assists).to_f / deaths).round(2)
82-
end
83-
84-
def calculate_team_averages(matches)
85-
all_stats = PlayerMatchStat.where(match: matches)
86-
87-
{
88-
avg_kda: calculate_kda(all_stats),
89-
avg_damage: all_stats.average(:damage_dealt_total)&.round(0) || 0,
90-
avg_gold: all_stats.average(:gold_earned)&.round(0) || 0,
91-
avg_cs: all_stats.average(:cs)&.round(1) || 0,
92-
avg_vision_score: all_stats.average(:vision_score)&.round(1) || 0
93-
}
94-
end
95-
96-
def calculate_role_rankings(players, matches)
97-
rankings = {}
98-
99-
%w[top jungle mid adc support].each do |role|
100-
role_players = players.where(role: role)
101-
role_data = role_players.map do |player|
102-
stats = PlayerMatchStat.where(player: player, match: matches)
103-
next if stats.empty?
104-
105-
{
106-
player_id: player.id,
107-
summoner_name: player.summoner_name,
108-
avg_performance: stats.average(:performance_score)&.round(1) || 0,
109-
games: stats.count
110-
}
111-
end.compact.sort_by { |p| -p[:avg_performance] }
112-
113-
rankings[role] = role_data
114-
end
115-
116-
rankings
117-
end
118-
end
1+
# frozen_string_literal: true
2+
3+
module Api
4+
module V1
5+
module Analytics
6+
# API Controller for team performance comparison and analytics
7+
# Provides endpoints to compare player statistics, team averages, and role rankings
8+
# with advanced filtering options
9+
class TeamComparisonController < Api::V1::BaseController
10+
def index
11+
players = fetch_active_players
12+
matches = build_matches_query
13+
14+
comparison_data = build_comparison_data(players, matches)
15+
16+
render json: { data: comparison_data }
17+
end
18+
19+
private
20+
21+
def fetch_active_players
22+
organization_scoped(Player).active.includes(:player_match_stats)
23+
end
24+
25+
def build_matches_query
26+
matches = organization_scoped(Match)
27+
matches = apply_date_filter(matches)
28+
matches = apply_opponent_filter(matches)
29+
apply_match_type_filter(matches)
30+
end
31+
32+
def apply_date_filter(matches)
33+
return matches.in_date_range(params[:start_date], params[:end_date]) if date_range_params?
34+
return matches.recent(params[:days].to_i) if params[:days].present?
35+
36+
matches.recent(30)
37+
end
38+
39+
def date_range_params?
40+
params[:start_date].present? && params[:end_date].present?
41+
end
42+
43+
def apply_opponent_filter(matches)
44+
return matches unless params[:opponent_team_id].present?
45+
46+
matches.where(opponent_team_id: params[:opponent_team_id])
47+
end
48+
49+
def apply_match_type_filter(matches)
50+
return matches unless params[:match_type].present?
51+
52+
matches.where(match_type: params[:match_type])
53+
end
54+
55+
def build_comparison_data(players, matches)
56+
{
57+
players: build_player_comparisons(players, matches),
58+
team_averages: calculate_team_averages(matches),
59+
role_rankings: calculate_role_rankings(players, matches)
60+
}
61+
end
62+
63+
def build_player_comparisons(players, matches)
64+
player_stats = players.map { |player| build_player_stats(player, matches) }
65+
sorted_stats = player_stats.compact
66+
sorted_stats.sort_by { |p| -p[:avg_performance_score] }
67+
end
68+
69+
def build_player_stats(player, matches)
70+
stats = PlayerMatchStat.where(player: player, match: matches)
71+
return nil if stats.empty?
72+
73+
{
74+
player: PlayerSerializer.render_as_hash(player),
75+
games_played: stats.count,
76+
kda: calculate_kda(stats),
77+
avg_damage: calculate_average(stats, :damage_dealt_total, 0),
78+
avg_gold: calculate_average(stats, :gold_earned, 0),
79+
avg_cs: calculate_average(stats, :cs, 1),
80+
avg_vision_score: calculate_average(stats, :vision_score, 1),
81+
avg_performance_score: calculate_average(stats, :performance_score, 1),
82+
multikills: build_multikills(stats)
83+
}
84+
end
85+
86+
def calculate_average(stats, column, precision)
87+
stats.average(column)&.round(precision) || 0
88+
end
89+
90+
def build_multikills(stats)
91+
{
92+
double: stats.sum(:double_kills),
93+
triple: stats.sum(:triple_kills),
94+
quadra: stats.sum(:quadra_kills),
95+
penta: stats.sum(:penta_kills)
96+
}
97+
end
98+
99+
def calculate_kda(stats)
100+
total_kills = stats.sum(:kills)
101+
total_deaths = stats.sum(:deaths)
102+
total_assists = stats.sum(:assists)
103+
104+
deaths = total_deaths.zero? ? 1 : total_deaths
105+
((total_kills + total_assists).to_f / deaths).round(2)
106+
end
107+
108+
def calculate_team_averages(matches)
109+
all_stats = PlayerMatchStat.where(match: matches)
110+
111+
{
112+
avg_kda: calculate_kda(all_stats),
113+
avg_damage: calculate_average(all_stats, :damage_dealt_total, 0),
114+
avg_gold: calculate_average(all_stats, :gold_earned, 0),
115+
avg_cs: calculate_average(all_stats, :cs, 1),
116+
avg_vision_score: calculate_average(all_stats, :vision_score, 1)
117+
}
118+
end
119+
120+
def calculate_role_rankings(players, matches)
121+
rankings = {}
122+
123+
%w[top jungle mid adc support].each do |role|
124+
rankings[role] = calculate_role_ranking(players, matches, role)
125+
end
126+
127+
rankings
128+
end
129+
130+
def calculate_role_ranking(players, matches, role)
131+
role_players = players.where(role: role)
132+
role_data = role_players.map { |player| build_role_player_data(player, matches) }
133+
sorted_data = role_data.compact
134+
sorted_data.sort_by { |p| -p[:avg_performance] }
135+
end
136+
137+
def build_role_player_data(player, matches)
138+
stats = PlayerMatchStat.where(player: player, match: matches)
139+
return nil if stats.empty?
140+
141+
{
142+
player_id: player.id,
143+
summoner_name: player.summoner_name,
144+
avg_performance: stats.average(:performance_score)&.round(1) || 0,
145+
games: stats.count
146+
}
147+
end
148+
end
149+
end
150+
end
151+
end

config.ru

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# This file is used by Rack-based servers to start the application.
2-
3-
require_relative "config/environment"
4-
5-
run Rails.application
6-
Rails.application.load_server
1+
# frozen_string_literal: true
2+
3+
# This file is used by Rack-based servers to start the application.
4+
5+
require_relative 'config/environment'
6+
7+
run Rails.application
8+
Rails.application.load_server

0 commit comments

Comments
 (0)