• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yurak / fanta / #639064463

26 Apr 2026 08:58PM UTC coverage: 0.0%. First build
#639064463

Pull #511

travis-ci

Pull Request #511: Add pg gem and configs, fix specs

0 of 1 new or added line in 1 file covered. (0.0%)

0 of 8337 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/models/player.rb
1
class Player < ApplicationRecord
×
2
  belongs_to :club
×
3
  belongs_to :national_team, optional: true
×
4

5
  has_many :player_positions, -> { order(:position_id) }, dependent: :destroy, inverse_of: :player
×
6
  has_many :positions, -> { order(:id) }, through: :player_positions
×
7

8
  has_many :player_teams, dependent: :destroy
×
9
  has_many :teams, through: :player_teams
×
10

11
  has_many :player_bids, dependent: :destroy
×
12
  has_many :player_requests, dependent: :destroy
×
13
  has_many :player_season_stats, dependent: :destroy
×
14
  has_many :round_players, dependent: :destroy
×
15
  has_many :transfers, dependent: :destroy
×
16
  has_many :club_transfers, dependent: :destroy
17

×
18
  BUCKET_URL = 'https://mantrafootball.s3-eu-west-1.amazonaws.com'.freeze
×
19
  TM_PATH = 'https://www.transfermarkt.com/player-path/profil/spieler/'.freeze
20

×
21
  validates :name, presence: true
×
22
  validates :tm_id, uniqueness: true, allow_nil: true
×
23
  validates :fotmob_id, uniqueness: true, allow_nil: true
24

×
25
  delegate :kit_path, :profile_kit_path, to: :club
26

×
27
  COUNTRY = {
28
    bo: 'Bolivia',
×
29
    bq: 'Bonaire',
×
30
    ba: 'Bosnia-Herzegovina',
×
31
    cd: 'DR Congo',
×
32
    cv: 'Cape Verde',
×
33
    cz: 'Czech Republic',
×
34
    'gb-eng': 'England',
×
35
    'gb-wls': 'Wales',
×
36
    'gb-sct': 'Scotland',
×
37
    'gb-nir': 'Northern Ireland',
×
38
    gm: 'The Gambia',
×
39
    ir: 'Iran',
×
40
    kr: 'Korea, South',
×
41
    kn: 'St. Kitts & Nevis',
×
42
    lc: 'St. Lucia',
×
43
    md: 'Moldova',
×
44
    ps: 'Palestine',
×
45
    ru: 'terrorist state',
×
46
    sy: 'Syria',
×
47
    tz: 'Tanzania',
×
48
    us: 'United States',
×
49
    ve: 'Venezuela',
×
50
    xk: 'Kosovo'
×
51
  }.freeze
×
52

×
53
  scope :by_club, ->(club_id) { where(club_id: club_id) if club_id.present? }
54
  scope :search_by_name, lambda { |search_str|
×
55
    where('lower(players.name) LIKE :search OR lower(players.first_name) LIKE :search', search: "%#{search_str.downcase}%")
×
NEW
56
  }
×
57
  scope :by_position, ->(position) { joins(:positions).where(positions: { name: position }) if position.present? }
×
58
  scope :by_classic_position, ->(position) { joins(:positions).where(positions: { human_name: position }) if position.present? }
×
59
  scope :by_tournament, ->(tournament) { where(club: tournament.clubs.active) if tournament.present? }
×
60
  scope :by_ec_tournament, ->(tournament) { where(club: tournament.ec_clubs.active) }
×
61
  scope :by_national_tournament, ->(tment_id) { joins(:national_team).where(national_teams: { tournament: tment_id, status: 'active' }) }
×
62
  scope :by_national_teams, ->(nt_id) { where(national_team_id: nt_id) }
×
63
  scope :by_national_tournament_round, ->(tr) { by_national_teams(tr.national_matches.pluck(:host_team_id, :guest_team_id).reduce([], :+)) }
×
64
  scope :by_tournament_round, ->(tr) { by_club(tr.tournament_matches.pluck(:host_club_id, :guest_club_id).reduce([], :+)) }
×
65
  scope :stats_query, -> { includes(:club, :positions).order(:name) }
×
66
  scope :with_team, -> { includes(:teams).where.not(teams: { id: nil }) }
×
67

×
68
  def avatar_path
69
    "#{BUCKET_URL}/player_avatars/#{path_name}.png"
×
70
  end
×
71

×
72
  def profile_avatar_path
73
    "#{BUCKET_URL}/players/#{path_name}.png"
×
74
  end
×
75

×
76
  def country
77
    return '' unless nationality
×
78

×
79
    COUNTRY[nationality.to_sym] || ISO3166::Country.new(nationality)&.iso_short_name
80
  end
×
81

×
82
  def full_name
83
    first_name ? "#{first_name} #{name}" : name
×
84
  end
×
85

×
86
  def full_name_reverse
87
    first_name ? "#{name} #{first_name}" : name
×
88
  end
×
89

×
90
  def full_name_with_positions
91
    "#{full_name} (#{position_names.join(' ')})"
×
92
  end
×
93

×
94
  def pseudo_name
95
    pseudonym.empty? ? full_name : pseudonym
×
96
  end
×
97

×
98
  def path_name
99
    @path_name ||= avatar_name || full_name.downcase.tr(' ', '_').tr('-', '_').delete("'")
×
100
  end
×
101

×
102
  def national_kit_path
103
    national_team&.kit_path || NationalTeam.find_by(code: nationality)&.kit_path
×
104
  end
×
105

×
106
  def profile_national_kit_path
107
    national_team&.profile_kit_path
×
108
  end
×
109

×
110
  def tm_path
111
    return '' unless tm_id
×
112

×
113
    "#{TM_PATH}#{tm_id}"
114
  end
×
115

×
116
  def tm_position_path(season_start_year)
117
    return '' unless tm_id
×
118

×
119
    "https://www.transfermarkt.com/player-path/leistungsdaten/spieler/#{tm_id}/plus/0?saison=#{season_start_year}"
120
  end
×
121

×
122
  def position_names
123
    @position_names ||= positions.map(&:name)
×
124
  end
×
125

×
126
  def position_sequence_number
127
    positions.first&.id || Float::INFINITY
×
128
  end
×
129

×
130
  def transfer_by(team)
131
    transfers.select { |t| t.incoming? && t.team_id == team.id }.last
×
132
  end
×
133

×
134
  def current_average_price
135
    return 0 if teams.blank?
×
136

×
137
    (teams.map { |team| transfer_by(team)&.price || 0 }.sum(0.0) / teams.count).round(1)
138
  end
×
139

×
140
  def age
141
    return if birth_date.empty?
×
142

×
143
    (Time.zone.today.strftime('%Y%m%d').to_i - birth_date.to_date.strftime('%Y%m%d').to_i) / 10_000
144
  end
×
145

×
146
  def team_by_league(league_id)
147
    return teams.find { |t| t.league_id == league_id.to_i } if teams.loaded?
×
148

×
149
    teams.find_by(league_id: league_id)
×
150
  end
151

×
152
  def stats_price
×
153
    @stats_price ||= player_season_stats.where(season: Season.second_to_last, tournament: club.tournament).last&.position_price || 1
×
154
  end
155

×
156
  def player_bids_by(auction_id)
×
157
    player_bids
×
158
      .joins('INNER JOIN auction_bids ON player_bids.auction_bid_id = auction_bids.id')
×
159
      .joins('INNER JOIN auction_rounds ON auction_bids.auction_round_id = auction_rounds.id')
×
160
      .joins('INNER JOIN auctions ON auction_rounds.auction_id = auctions.id')
×
161
      .includes(auction_bid: %i[auction_round team])
×
162
      .where(auctions: { id: auction_id })
×
163
      .order('player_bids.price DESC')
×
164
      .group_by { |bid| bid.auction_bid.auction_round.number }
×
165
      .sort_by { |round_number, _| round_number }.reverse.to_h
166
  end
167

168
  # Current season statistic
×
169

×
170
  def chart_info(matches)
×
171
    bs = {}
×
172
    ts = {}
×
173
    matches.each do |rp|
×
174
      bs[rp.tournament_round.number] = rp.score.to_s
×
175
      ts[rp.tournament_round.number] = rp.result_score.to_s
×
176
    end
×
177
    [{ name: 'Total score', data: ts }, { name: 'Base score', data: bs }]
178
  end
×
179

×
180
  def season_matches_with_scores
×
181
    @season_matches_with_scores ||= round_players.with_score.includes(:tournament_round).by_tournament_round(club_tournament_season_rounds)
182
  end
×
183

×
184
  def season_matches
×
185
    @season_matches ||= round_players.by_tournament_round(club_tournament_season_rounds)
186
  end
×
187

×
188
  def season_club_matches_w_scores
×
189
    @season_club_matches_w_scores ||= round_players.with_score.by_tournament_round(season_tournament_rounds)
190
  end
×
191

×
192
  def season_club_in_squad
×
193
    @season_club_in_squad ||= round_players.in_squad.by_tournament_round(season_tournament_rounds)
194
  end
×
195

×
196
  def season_ec_matches_with_scores
×
197
    @season_ec_matches_with_scores ||= round_players.with_score.by_tournament_round(season_club_eurocup_rounds).order(:tournament_round_id)
198
  end
×
199

×
200
  def season_ec_in_squad
×
201
    @season_ec_in_squad ||= round_players.in_squad.by_tournament_round(season_club_eurocup_rounds).order(:tournament_round_id)
202
  end
×
203

×
204
  def season_all_matches_with_scores
×
205
    @season_all_matches_with_scores ||= round_players.with_score.by_tournament_round(season_all_tournam_rounds).order(:tournament_round_id)
206
  end
×
207

×
208
  def national_matches_with_scores
×
209
    @national_matches_with_scores ||= round_players.with_score.by_tournament_round(national_team_rounds).order(:tournament_round_id)
210
  end
×
211

×
212
  def national_in_squad
×
213
    @national_in_squad ||= round_players.in_squad.by_tournament_round(national_team_rounds).order(:tournament_round_id)
214
  end
×
215

×
216
  def season_scores_count(matches = season_matches_with_scores)
×
217
    matches.size
218
  end
×
219

×
220
  def season_average_score(matches = season_matches_with_scores)
221
    return 0 if season_scores_count(matches).zero?
×
222

×
223
    (matches.map(&:score).sum / season_scores_count(matches)).round(2)
224
  end
×
225

×
226
  def season_average_result_score(matches = season_matches_with_scores)
227
    return 0 if season_scores_count(matches).zero?
×
228

×
229
    (matches.map(&:result_score).sum / season_scores_count(matches)).round(2)
230
  end
×
231

×
232
  def season_bonus_count(matches, bonus)
233
    return 0 unless matches.any?
×
234

×
235
    matches.map(&bonus.to_sym).sum.to_i
236
  end
×
237

×
238
  def season_cards_count(matches, card)
239
    return 0 unless matches.any?
×
240

×
241
    matches.where(card => true).count
242
  end
×
243

×
244
  def season_played_minutes(matches = season_matches_with_scores)
245
    return 0 unless matches.any?
×
246

×
247
    matches.map(&:played_minutes).sum
248
  end
×
249

×
250
  def sixty_minutes_plus(matches = season_matches_with_scores)
×
251
    matches.where('played_minutes >= ?', MatchPlayer::MIN_PLAYED_MINUTES_FOR_CS).count
252
  end
×
253

254
  private
×
255

×
256
  def current_season
×
257
    @current_season ||= Season.last
258
  end
259

×
260
  # all TournamentRound in current tournament for this season
×
261
  def club_tournament_season_rounds
×
262
    @club_tournament_season_rounds ||=
×
263
      club.tournament ? TournamentRound.by_tournament(club.tournament.id).by_season(current_season.id) : []
264
  end
265

×
266
  # all TournamentRound for this season
×
267
  def season_tournament_rounds
×
268
    @season_tournament_rounds ||=
×
269
      TournamentRound.by_tournament(Tournament.with_clubs).by_season(current_season.id).order(deadline: :desc)
270
  end
×
271

×
272
  def season_club_eurocup_rounds
×
273
    @season_club_eurocup_rounds ||=
×
274
      TournamentRound.by_tournament(Tournament.with_ec_clubs).by_season(current_season.id)
275
  end
×
276

×
277
  def season_all_tournam_rounds
×
278
    season_tournament_rounds + season_club_eurocup_rounds
279
  end
×
280

×
281
  def national_team_rounds
×
282
    return [] unless national_team&.tournament
×
283

×
284
    @national_team_rounds ||= national_team.tournament.tournament_rounds.by_season(current_season.id)
285
  end
286
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc