@@ -35,42 +35,59 @@ def details
3535 status : :bad_request )
3636 end
3737
38- # Find matches for this champion (case-insensitive search)
39- matches = PlayerMatchStat . where ( player : player )
40- . where ( 'LOWER(champion) = ?' , champion . downcase )
41- . joins ( :match )
42- . includes ( :match )
43- . order ( 'matches.game_start DESC' )
44- . limit ( params [ :limit ] || 20 )
38+ matches = fetch_champion_matches ( player , champion )
4539
4640 if matches . empty?
4741 return render_error ( message : "No matches found for champion #{ champion } " , code : 'NO_MATCHES' ,
4842 status : :not_found )
4943 end
5044
51- # Calculate aggregate stats for this champion
45+ riot_service = RiotCdnService . new
5246 matches_array = matches . to_a
47+
48+ render_success ( {
49+ player : PlayerSerializer . render_as_hash ( player ) ,
50+ champion : champion ,
51+ icon_url : riot_service . champion_icon_url ( champion ) ,
52+ aggregate_stats : build_aggregate_stats ( matches , matches_array ) ,
53+ matches : serialize_champion_matches ( matches_array , riot_service )
54+ } )
55+ rescue ActiveRecord ::RecordNotFound
56+ render_error ( message : 'Player not found' , code : 'PLAYER_NOT_FOUND' , status : :not_found )
57+ rescue StandardError => e
58+ Rails . logger . error ( "Error in champions#details: #{ e . message } " )
59+ Rails . logger . error ( e . backtrace . join ( "\n " ) )
60+ render_error ( message : "Failed to load champion details: #{ e . message } " , code : 'INTERNAL_ERROR' ,
61+ status : :internal_server_error )
62+ end
63+
64+ private
65+
66+ def fetch_champion_matches ( player , champion )
67+ PlayerMatchStat . where ( player : player )
68+ . where ( 'LOWER(champion) = ?' , champion . downcase )
69+ . joins ( :match )
70+ . includes ( :match )
71+ . order ( 'matches.game_start DESC' )
72+ . limit ( params [ :limit ] || 20 )
73+ end
74+
75+ def build_aggregate_stats ( matches , matches_array )
5376 total_kills = matches_array . sum ( &:kills )
5477 total_deaths = matches_array . sum ( &:deaths )
5578 total_assists = matches_array . sum ( &:assists )
5679 avg_kda = if matches_array . any?
57- ( ( total_kills + total_assists ) . to_f / [ total_deaths ,
58- 1 ] . max ) . round ( 2 )
80+ ( ( total_kills + total_assists ) . to_f / [ total_deaths , 1 ] . max ) . round ( 2 )
5981 else
6082 0
6183 end
84+ wins = matches_array . count { |m | m . match &.victory? }
6285
63- aggregate_stats = {
86+ {
6487 total_games : matches_array . count ,
65- wins : matches_array . count { |m | m . match &.victory? } ,
66- losses : matches_array . count { |m | !m . match &.victory? } ,
67- win_rate : if matches_array . any?
68- ( matches_array . count do |m |
69- m . match &.victory?
70- end . to_f / matches_array . count )
71- else
72- 0
73- end ,
88+ wins : wins ,
89+ losses : matches_array . count - wins ,
90+ win_rate : matches_array . any? ? ( wins . to_f / matches_array . count ) : 0 ,
7491 avg_kda : avg_kda ,
7592 avg_kills : matches_array . sum ( &:kills ) . to_f / [ matches_array . count , 1 ] . max ,
7693 avg_deaths : matches_array . sum ( &:deaths ) . to_f / [ matches_array . count , 1 ] . max ,
@@ -81,75 +98,64 @@ def details
8198 avg_gold_per_min : matches . average ( :gold_per_min ) &.round ( 0 ) || 0 ,
8299 avg_vision_score : matches . average ( :vision_score ) &.round ( 1 ) || 0
83100 }
101+ end
84102
85- riot_service = RiotCdnService . new
103+ def serialize_champion_matches ( matches_array , riot_service )
104+ matches_array . filter_map do |stat |
105+ next nil unless stat . match
86106
87- matches_data = matches . map do |stat |
88- next nil unless stat . match # Skip if match is nil
89-
90- {
91- match_id : stat . match . id ,
92- game_id : stat . match . riot_match_id ,
93- date : stat . match . game_start &.strftime ( '%Y-%m-%d %H:%M' ) ,
94- victory : stat . match . victory? ,
95- game_duration : stat . match . game_duration || 0 ,
96- kda : stat . kda_display ,
97- kda_ratio : ( stat . kda_ratio || 0 ) . round ( 2 ) ,
98- kills : stat . kills || 0 ,
99- deaths : stat . deaths || 0 ,
100- assists : stat . assists || 0 ,
101- cs : stat . cs || 0 ,
102- cs_per_min : ( stat . cs_per_min || 0 ) . round ( 1 ) ,
103- damage_dealt : stat . damage_dealt_total || 0 ,
104- damage_taken : stat . damage_taken || 0 ,
105- gold_earned : stat . gold_earned || 0 ,
106- gold_per_min : ( stat . gold_per_min || 0 ) . round ( 0 ) ,
107- vision_score : stat . vision_score || 0 ,
108- performance_score : stat . performance_score || 0 ,
109- # Additional stats
110- kill_participation : stat . kill_participation || 0 ,
111- damage_share : stat . damage_share || 0 ,
112- gold_share : stat . gold_share || 0 ,
113- wards_placed : stat . wards_placed || 0 ,
114- wards_destroyed : stat . wards_destroyed || 0 ,
115- control_wards : stat . control_wards_purchased || 0 ,
116- healing_done : stat . healing_done || 0 ,
117- double_kills : stat . double_kills || 0 ,
118- triple_kills : stat . triple_kills || 0 ,
119- quadra_kills : stat . quadra_kills || 0 ,
120- penta_kills : stat . penta_kills || 0 ,
121- first_blood : stat . first_blood || false ,
122- first_tower : stat . first_tower || false ,
123- largest_killing_spree : stat . largest_killing_spree || 0 ,
124- largest_multi_kill : stat . largest_multi_kill || 0 ,
125- # Items, runes and spells
126- items : ( stat . items || [ ] ) . map { |id | { id : id , icon_url : riot_service . item_icon_url ( id ) } } ,
127- runes : ( stat . runes || [ ] ) . map { |id | { id : id , icon_url : riot_service . rune_icon_url ( id ) } } ,
128- spells : [
129- { name : stat . summoner_spell_1 , icon_url : riot_service . spell_icon_url ( stat . summoner_spell_1 &.to_i ) } ,
130- { name : stat . summoner_spell_2 , icon_url : riot_service . spell_icon_url ( stat . summoner_spell_2 &.to_i ) }
131- ] . select { |s | s [ :name ] . present? } ,
132- role : stat . role
133- }
134- end . compact
107+ build_match_entry ( stat , riot_service )
108+ end
109+ end
135110
136- render_success ( {
137- player : PlayerSerializer . render_as_hash ( player ) ,
138- champion : champion ,
139- icon_url : riot_service . champion_icon_url ( champion ) ,
140- aggregate_stats : aggregate_stats ,
141- matches : matches_data
142- } )
143- rescue ActiveRecord ::RecordNotFound
144- render_error ( message : 'Player not found' , code : 'PLAYER_NOT_FOUND' , status : :not_found )
145- rescue StandardError => e
146- Rails . logger . error ( "Error in champions#details: #{ e . message } " )
147- Rails . logger . error ( e . backtrace . join ( "\n " ) )
148- render_error ( message : "Failed to load champion details: #{ e . message } " , code : 'INTERNAL_ERROR' ,
149- status : :internal_server_error )
111+ def build_match_entry ( stat , riot_service )
112+ {
113+ match_id : stat . match . id ,
114+ game_id : stat . match . riot_match_id ,
115+ date : stat . match . game_start &.strftime ( '%Y-%m-%d %H:%M' ) ,
116+ victory : stat . match . victory? ,
117+ game_duration : stat . match . game_duration || 0 ,
118+ kda : stat . kda_display ,
119+ kda_ratio : ( stat . kda_ratio || 0 ) . round ( 2 ) ,
120+ kills : stat . kills || 0 ,
121+ deaths : stat . deaths || 0 ,
122+ assists : stat . assists || 0 ,
123+ cs : stat . cs || 0 ,
124+ cs_per_min : ( stat . cs_per_min || 0 ) . round ( 1 ) ,
125+ damage_dealt : stat . damage_dealt_total || 0 ,
126+ damage_taken : stat . damage_taken || 0 ,
127+ gold_earned : stat . gold_earned || 0 ,
128+ gold_per_min : ( stat . gold_per_min || 0 ) . round ( 0 ) ,
129+ vision_score : stat . vision_score || 0 ,
130+ performance_score : stat . performance_score || 0 ,
131+ kill_participation : stat . kill_participation || 0 ,
132+ damage_share : stat . damage_share || 0 ,
133+ gold_share : stat . gold_share || 0 ,
134+ wards_placed : stat . wards_placed || 0 ,
135+ wards_destroyed : stat . wards_destroyed || 0 ,
136+ control_wards : stat . control_wards_purchased || 0 ,
137+ healing_done : stat . healing_done || 0 ,
138+ double_kills : stat . double_kills || 0 ,
139+ triple_kills : stat . triple_kills || 0 ,
140+ quadra_kills : stat . quadra_kills || 0 ,
141+ penta_kills : stat . penta_kills || 0 ,
142+ first_blood : stat . first_blood || false ,
143+ first_tower : stat . first_tower || false ,
144+ largest_killing_spree : stat . largest_killing_spree || 0 ,
145+ largest_multi_kill : stat . largest_multi_kill || 0 ,
146+ items : ( stat . items || [ ] ) . map { |id | { id : id , icon_url : riot_service . item_icon_url ( id ) } } ,
147+ runes : ( stat . runes || [ ] ) . map { |id | { id : id , icon_url : riot_service . rune_icon_url ( id ) } } ,
148+ spells : build_spells ( stat , riot_service ) ,
149+ role : stat . role
150+ }
150151 end
151152
152- private
153+ def build_spells ( stat , riot_service )
154+ [
155+ { name : stat . summoner_spell_1 , icon_url : riot_service . spell_icon_url ( stat . summoner_spell_1 &.to_i ) } ,
156+ { name : stat . summoner_spell_2 , icon_url : riot_service . spell_icon_url ( stat . summoner_spell_2 &.to_i ) }
157+ ] . select { |s | s [ :name ] . present? }
158+ end
153159
154160 def fetch_champion_stats ( player )
155161 PlayerMatchStat . where ( player : player )
@@ -196,21 +202,21 @@ def build_champion_data(player, champion_stats)
196202 player : PlayerSerializer . render_as_hash ( player ) ,
197203 champion_stats : champion_stats ,
198204 top_champions : champion_stats . take ( 5 ) ,
199- champion_diversity : {
200- total_champions : champion_stats . count ,
201- highly_played : champion_stats . count { |c | c [ :games_played ] >= 10 } ,
202- average_games : if champion_stats . empty?
203- 0
204- else
205- ( champion_stats . sum do |c |
206- c [ :games_played ]
207- end / champion_stats . count . to_f ) . round ( 1 )
208- end
209- }
205+ champion_diversity : build_champion_diversity ( champion_stats )
210206 }
211207 end
212208
213- private
209+ def build_champion_diversity ( champion_stats )
210+ {
211+ total_champions : champion_stats . count ,
212+ highly_played : champion_stats . count { |c | c [ :games_played ] >= 10 } ,
213+ average_games : champion_stats . empty? ? 0 : average_games_per_champion ( champion_stats )
214+ }
215+ end
216+
217+ def average_games_per_champion ( champion_stats )
218+ ( champion_stats . sum { |c | c [ :games_played ] } / champion_stats . count . to_f ) . round ( 1 )
219+ end
214220
215221 def calculate_mastery_grade ( win_rate , avg_kda )
216222 score = ( win_rate * 100 * 0.6 ) + ( ( avg_kda || 0 ) * 10 * 0.4 )
0 commit comments