Skip to content

Commit aae390e

Browse files
committed
refactor: solve code style and minor issues
1 parent 3911322 commit aae390e

11 files changed

Lines changed: 273 additions & 208 deletions

File tree

app/modules/admin/controllers/players_controller.rb

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -239,74 +239,73 @@ def change_status
239239
# Transfers a player to another organization
240240
def transfer
241241
new_organization_id = params[:new_organization_id]
242-
reason = params[:reason] || 'Player transfer'
242+
new_organization = resolve_transfer_target(new_organization_id)
243+
return unless new_organization
243244

244-
unless new_organization_id.present?
245-
return render_error(
246-
message: 'New organization ID is required',
247-
code: 'VALIDATION_ERROR',
248-
status: :unprocessable_entity
249-
)
250-
end
245+
old_org_id = @player.organization_id
246+
execute_player_transfer(@player, new_organization, old_org_id, params[:reason])
247+
publish_player_transferred(@player, old_org_id, new_organization_id)
251248

252-
new_organization = Organization.find_by(id: new_organization_id)
253-
unless new_organization
254-
return render_error(
255-
message: 'Organization not found',
256-
code: 'NOT_FOUND',
257-
status: :not_found
258-
)
259-
end
249+
render_success({
250+
message: 'Player transferred successfully',
251+
player: PlayerSerializer.render_as_hash(@player),
252+
previous_organization: old_org_id,
253+
new_organization: new_organization_id
254+
})
255+
rescue ActiveRecord::RecordInvalid => e
256+
render_error(
257+
message: "Failed to transfer player: #{e.message}",
258+
code: 'TRANSFER_ERROR',
259+
status: :unprocessable_entity
260+
)
261+
end
260262

261-
old_org_id = @player.organization_id
263+
private
262264

263-
ActiveRecord::Base.transaction do
264-
# Save current organization as previous
265-
@player.update!(previous_organization_id: old_org_id)
265+
def resolve_transfer_target(new_organization_id)
266+
unless new_organization_id.present?
267+
render_error(message: 'New organization ID is required', code: 'VALIDATION_ERROR',
268+
status: :unprocessable_entity)
269+
return nil
270+
end
266271

267-
# Transfer to new organization
268-
@player.update!(organization: new_organization, status: 'inactive')
272+
org = Organization.find_by(id: new_organization_id)
273+
render_error(message: 'Organization not found', code: 'NOT_FOUND', status: :not_found) unless org
274+
org
275+
end
269276

277+
def execute_player_transfer(player, new_organization, old_org_id, reason)
278+
ActiveRecord::Base.transaction do
279+
player.update!(previous_organization_id: old_org_id)
280+
player.update!(organization: new_organization, status: 'inactive')
270281
log_user_action(
271282
action: 'transfer',
272283
entity_type: 'Player',
273-
entity_id: @player.id,
284+
entity_id: player.id,
274285
old_values: { organization_id: old_org_id },
275286
new_values: {
276-
organization_id: new_organization_id,
287+
organization_id: new_organization.id,
277288
previous_organization_id: old_org_id,
278-
transfer_reason: reason
289+
transfer_reason: reason || 'Player transfer'
279290
}
280291
)
281292
end
293+
end
282294

295+
def publish_player_transferred(player, old_org_id, new_organization_id)
283296
Events::EventPublisher.publish(
284297
user_id: current_user.id,
285298
org_id: old_org_id,
286299
type: 'player.transferred',
287300
payload: {
288-
player_id: @player.id,
289-
player_name: @player.summoner_name,
301+
player_id: player.id,
302+
player_name: player.summoner_name,
290303
from_org_id: old_org_id,
291304
to_org_id: new_organization_id
292305
}
293306
)
294-
render_success({
295-
message: 'Player transferred successfully',
296-
player: PlayerSerializer.render_as_hash(@player),
297-
previous_organization: old_org_id,
298-
new_organization: new_organization_id
299-
})
300-
rescue ActiveRecord::RecordInvalid => e
301-
render_error(
302-
message: "Failed to transfer player: #{e.message}",
303-
code: 'TRANSFER_ERROR',
304-
status: :unprocessable_entity
305-
)
306307
end
307308

308-
private
309-
310309
def require_admin_access
311310
return if current_user.admin? || current_user.owner?
312311

app/modules/analytics/controllers/competitive_controller.rb

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -336,29 +336,38 @@ def build_opponents_data(rows)
336336
# ── patch_meta helpers ─────────────────────────────────────────
337337

338338
def build_patch_meta(rows)
339-
rows.group_by { |m| m.patch_version.presence }.filter_map do |patch, patch_rows|
340-
next if patch.nil?
339+
rows.group_by { |m| m.patch_version.presence }
340+
.filter_map { |patch, patch_rows| build_patch_entry(patch, patch_rows) }
341+
.sort_by { |entry| entry[:patch] }
342+
.reverse
343+
end
341344

342-
games = patch_rows.size
343-
wins = patch_rows.count(&:victory)
345+
def build_patch_entry(patch, patch_rows)
346+
return nil if patch.nil?
344347

345-
{
346-
patch: patch,
347-
games: games,
348-
wins: wins,
349-
losses: games - wins,
350-
win_rate: games.positive? ? (wins.to_f / games * 100).round(1) : 0,
351-
blue_games: patch_rows.count { |m| m.side&.downcase == 'blue' },
352-
red_games: patch_rows.count { |m| m.side&.downcase == 'red' },
353-
top_picks: top_n_from_jsonb(patch_rows, :our_picks, 5),
354-
top_bans: top_n_from_jsonb(patch_rows, :our_bans, 5)
355-
}
356-
end.sort_by { |p| p[:patch] }.reverse
348+
games = patch_rows.size
349+
wins = patch_rows.count(&:victory)
350+
351+
{
352+
patch: patch,
353+
games: games,
354+
wins: wins,
355+
losses: games - wins,
356+
win_rate: patch_win_rate(wins, games),
357+
blue_games: patch_rows.count { |m| m.side&.downcase == 'blue' },
358+
red_games: patch_rows.count { |m| m.side&.downcase == 'red' },
359+
top_picks: top_n_from_jsonb(patch_rows, :our_picks, 5),
360+
top_bans: top_n_from_jsonb(patch_rows, :our_bans, 5)
361+
}
362+
end
363+
364+
def patch_win_rate(wins, games)
365+
games.positive? ? (wins.to_f / games * 100).round(1) : 0
357366
end
358367

359-
def top_n_from_jsonb(rows, field, n)
368+
def top_n_from_jsonb(rows, field, limit)
360369
tally = rows.flat_map { |m| Array(m.public_send(field)).filter_map { |e| e['champion'] } }.tally
361-
tally.sort_by { |_, count| -count }.first(n).map { |champion, count| { champion: champion, count: count } }
370+
tally.sort_by { |_, count| -count }.first(limit).map { |champion, count| { champion: champion, count: count } }
362371
end
363372

364373
# ── empty state helpers ────────────────────────────────────────

app/modules/competitive/concerns/match_fingerprint.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
# frozen_string_literal: true
22

3+
# Shared fingerprinting logic for deduplicating competitive match imports.
4+
#
5+
# Two import pipelines (Riot API numeric IDs and Leaguepedia textual IDs) can
6+
# produce different external_match_id values for the same physical game. This
7+
# module derives a source-agnostic fingerprint so duplicates are caught before
8+
# they are persisted.
39
module MatchFingerprint
410
# Generates a stable fingerprint for a physical game based on attributes that
5-
# are source-agnostic. Used to detect duplicates when the same game arrives
6-
# from two import pipelines (Riot API numeric ID and Leaguepedia textual ID)
7-
# with different external_match_id values.
11+
# are source-agnostic.
812
#
913
# @param org_id [String] organization UUID
1014
# @param match_date [DateTime, nil]
1115
# @param game_number [Integer, nil] game within the series (1-5)
1216
# @param opponent_name [String, nil]
13-
# @return [String, nil] MD5 hex string, or nil when inputs are insufficient
17+
# @return [String, nil] SHA-256 hex string, or nil when inputs are insufficient
1418
def generate_fingerprint(org_id, match_date, game_number, opponent_name)
1519
return nil if match_date.nil? || opponent_name.nil? || opponent_name.strip.empty?
1620

1721
day = match_date.to_date.to_s
1822
normalized = opponent_name.strip.downcase
19-
Digest::MD5.hexdigest("#{org_id}|#{day}|#{game_number || 1}|#{normalized}")
23+
Digest::SHA256.hexdigest("#{org_id}|#{day}|#{game_number || 1}|#{normalized}")
2024
end
2125

2226
# Returns true if a record with this fingerprint already exists for the org.

app/modules/competitive/controllers/pro_matches_controller.rb

Lines changed: 52 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -400,42 +400,10 @@ def match_preview
400400
}, status: :unprocessable_entity
401401
end
402402

403-
# Fetch PandaScore data in parallel
404-
t1_data = Thread.new { @pandascore_service.fetch_team(team1_id) }
405-
t2_data = Thread.new { @pandascore_service.fetch_team(team2_id) }
406-
t1_recent = Thread.new { @pandascore_service.fetch_team_recent_matches(team1_id) }
407-
t2_recent = Thread.new { @pandascore_service.fetch_team_recent_matches(team2_id) }
403+
team1_data, team2_data, team1_recent, team2_recent =
404+
fetch_pandascore_preview_data(team1_id, team2_id)
408405

409-
team1_data = t1_data.value
410-
team2_data = t2_data.value
411-
team1_recent = t1_recent.value
412-
team2_recent = t2_recent.value
413-
414-
# H2H stats from Elasticsearch
415-
must_clauses = [
416-
{
417-
bool: {
418-
should: [
419-
{ bool: { must: [team_clause(team1_name, 'team1'), team_clause(team2_name, 'team2')] } },
420-
{ bool: { must: [team_clause(team2_name, 'team1'), team_clause(team1_name, 'team2')] } }
421-
],
422-
minimum_should_match: 1
423-
}
424-
}
425-
]
426-
427-
es_body = {
428-
query: { bool: { must: must_clauses } },
429-
size: 0,
430-
aggs: {
431-
team1_wins: { filter: win_team_clause(team1_name) },
432-
team2_wins: { filter: win_team_clause(team2_name) }
433-
}
434-
}
435-
436-
es_result = ElasticsearchClient.new.search(index: 'lol_pro_matches', body: es_body)
437-
h2h_wins_t1 = es_result.dig('aggregations', 'team1_wins', 'doc_count') || 0
438-
h2h_wins_t2 = es_result.dig('aggregations', 'team2_wins', 'doc_count') || 0
406+
h2h_wins_t1, h2h_wins_t2 = fetch_h2h_wins(team1_name, team2_name)
439407

440408
render json: {
441409
data: {
@@ -462,32 +430,9 @@ def es_series
462430

463431
raise ArgumentError, 'team1 and team2 are required' if team1.blank? || team2.blank?
464432

465-
must_clauses = [
466-
{
467-
bool: {
468-
should: [
469-
{ bool: { must: [team_clause(team1, 'team1'), team_clause(team2, 'team2')] } },
470-
{ bool: { must: [team_clause(team2, 'team1'), team_clause(team1, 'team2')] } }
471-
],
472-
minimum_should_match: 1
473-
}
474-
}
475-
]
476-
477-
if params[:after].present? && params[:before].present?
478-
must_clauses << {
479-
range: { start_time: { gte: params[:after], lte: params[:before] } }
480-
}
481-
end
482-
483-
es_body = {
484-
query: { bool: { must: must_clauses } },
485-
sort: [{ start_time: { order: 'desc' } }],
486-
size: limit
487-
}
488-
489-
result = ElasticsearchClient.new.search(index: 'lol_pro_matches', body: es_body)
490-
games = result.dig('hits', 'hits')&.map { |h| h['_source'] } || []
433+
es_body = build_series_query(team1, team2, limit)
434+
result = ElasticsearchClient.new.search(index: 'lol_pro_matches', body: es_body)
435+
games = result.dig('hits', 'hits')&.map { |h| h['_source'] } || []
491436

492437
render json: { data: { games: games, total: games.size } }
493438
rescue ArgumentError => e
@@ -504,6 +449,52 @@ def set_pandascore_service
504449
@pandascore_service = PandascoreService.instance
505450
end
506451

452+
def fetch_pandascore_preview_data(team1_id, team2_id)
453+
t1_data = Thread.new { @pandascore_service.fetch_team(team1_id) }
454+
t2_data = Thread.new { @pandascore_service.fetch_team(team2_id) }
455+
t1_recent = Thread.new { @pandascore_service.fetch_team_recent_matches(team1_id) }
456+
t2_recent = Thread.new { @pandascore_service.fetch_team_recent_matches(team2_id) }
457+
[t1_data.value, t2_data.value, t1_recent.value, t2_recent.value]
458+
end
459+
460+
def fetch_h2h_wins(team1_name, team2_name)
461+
must_clauses = [h2h_matchup_clause(team1_name, team2_name)]
462+
es_body = {
463+
query: { bool: { must: must_clauses } },
464+
size: 0,
465+
aggs: {
466+
team1_wins: { filter: win_team_clause(team1_name) },
467+
team2_wins: { filter: win_team_clause(team2_name) }
468+
}
469+
}
470+
result = ElasticsearchClient.new.search(index: 'lol_pro_matches', body: es_body)
471+
wins_t1 = result.dig('aggregations', 'team1_wins', 'doc_count') || 0
472+
wins_t2 = result.dig('aggregations', 'team2_wins', 'doc_count') || 0
473+
[wins_t1, wins_t2]
474+
end
475+
476+
def h2h_matchup_clause(team1_name, team2_name)
477+
{
478+
bool: {
479+
should: [
480+
{ bool: { must: [team_clause(team1_name, 'team1'), team_clause(team2_name, 'team2')] } },
481+
{ bool: { must: [team_clause(team2_name, 'team1'), team_clause(team1_name, 'team2')] } }
482+
],
483+
minimum_should_match: 1
484+
}
485+
}
486+
end
487+
488+
def build_series_query(team1, team2, limit)
489+
must_clauses = [h2h_matchup_clause(team1, team2)]
490+
must_clauses << { range: { start_time: { gte: params[:after], lte: params[:before] } } } if date_filter?
491+
{ query: { bool: { must: must_clauses } }, sort: [{ start_time: { order: 'desc' } }], size: limit }
492+
end
493+
494+
def date_filter?
495+
params[:after].present? && params[:before].present?
496+
end
497+
507498
# Builds an ES should clause that matches a team name using:
508499
# 1. Exact term match (handles perfect name equality)
509500
# 2. Prefix wildcard on first word, case-insensitive (handles suffix differences

app/modules/competitive/services/scraper_importer_service.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ def import_one(match, our_team, stats)
8888
ext_id = build_external_match_id(match)
8989
parsed_date = parse_date(match['start_time'])
9090
game_number = match['game_number']
91-
_team1_name = match.dig('team1', 'name').to_s
92-
_team2_name = match.dig('team2', 'name').to_s
93-
_, opp_resolved = resolve_teams(_team1_name, _team2_name, match['win_team'].to_s, our_team)
91+
team1_name = match.dig('team1', 'name').to_s
92+
team2_name = match.dig('team2', 'name').to_s
93+
_, opp_resolved = resolve_teams(team1_name, team2_name, match['win_team'].to_s, our_team)
9494

9595
if duplicate_by_fingerprint?(@organization, parsed_date, game_number, opp_resolved)
9696
stats[:skipped_duplicate] += 1

0 commit comments

Comments
 (0)