1

game_typeを持つゲームでゲームをプレイするためのポイントをユーザーに提供するアプリがあります。私がしたいことは次のとおりです。

  1. ゲームに基づいてポイントを小計する
  2. 他のユーザーと比較したgame_idのポイント数に基づいて、ユーザーのゲームランク(ランク1 =最も多くのポイント)を決定します
  3. game_typeに基づいてポイントを小計する
  4. 他のユーザーと比較したgame_type_idのポイント数に基づいて、ユーザーのgame_typeランク(ランク1 =ほとんどのポイント)を決定します

上記のクエリ結果に基づいて、game_typesとgamesの間の上位5ポイントのランキングのみを表示したいと思います。

たとえば、ユーザーの計算が次のようになっているとします。

game_id = 1, rank 200
game_id = 2, rank 10
game_id = 3, rank 6
game_id = 4, rank 31

game_type_id = 1, rank 500
game_type_id = 2, rank 400
game_type_id = 3, rank 1
game_type_id = 4, rank 7
game_type_id = 5, rank 100

次に、games:2、3、4およびgame_types:3、4、5のランクのみを表示したいと思います。これらは、このユーザーのゲームおよびgame_typesの上位5つのランクであるためです。

ユーザーテーブルにgame_typeとgame(game_1_rank、game_2_rank、game_type_1_rankなど)ごとにgame_typeとgameフィールドを作成して、バックグラウンドジョブのポイントを1時間ごとに計算し、から最高ランクを取得しようと考えました。そこにありますが、新しいゲームとgame_typesが時間の経過とともに追加されているため、これが最善のアプローチではないと思います。

したがって、users#showページが読み込まれたときに計算を行い、そのページをキャッシュする(そして1時間ごとに期限切れになる)のが最善の方法だと考えていました。

私のモデルは次のようになります。

user
  has_many :points

point
  belongs_to :game
  belongs_to :game_type
  belongs_to :user

game
  has_many :points
  has_one :game_type

game_type
  has_many :points

すべてのゲームとgame_typesの全体的なランキングを計算するために、Users#showにこのコードがありますが、ビューのトップランクにアクセスできるように調整する方法がわかりません(ビューにコードはまだありません)このユーザーの上位5ランクと、そのユーザーの対象となるゲーム/ game_typesを表示します)。

# calculate ranks for all users for all games in order to find the user's rank
@games = Game.all

@games.each do |game|
  @users_by_game = Point.where(“game_id = ?”, game.id).select("sum(amount) as points, user_id").order("points desc").group("user_id")
  rank = 0
  points = 0

  @users_by_game.each_with_index do |user_by_game, index|           
    if user_by_game.points != points
      points = user_by_game.points
      rank += 1
  end
end

# calculate ranks for all users for all games_types in order to find the user's rank
@game_types = GameType.all   

@game_types.each do |game_type| 
  @users_by_game_type = Point.where(“game_type_id = ?”, game_type.id).select("sum(amount) as points, user_id").order("points desc").group("user_id")
  rank = 0
  points = 0

  @users_by_game_type.each_with_index do |user_by_game_type, index|           
    if user_by_game_type.points != points
      points = user_by_game_type.points
      rank += 1
    end
  end
end

私が決定しようとしているのは:

  1. これは、これらのランクを計算するための最良のアプローチですか、それともよりリソース効率の高い、またはDRYの方法がありますか?
  2. これが最善の方法である場合、コードはすべてのユーザーのランクのみを計算しているため、コードとビューを変更して、この@userの上位5つのgame / game_typeランキングを表示するにはどうすればよいですか?
4

1 に答える 1

1

ゲームごとのランクとゲームタイプごとのランクを保存する2つの新しいテーブルを導入します。1時間ごとに全体のランクを計算します。このようにして、ページの読み込みがはるかに高速になります。宝石のようwheneverに、1時間ごとにランク計算をスケジュールすることができます。

現在の実装は、数百人のユーザーを超えて拡張することはありません。

class User
  has_many :points
  has_many :game_ranks, :order => "rank DESC"
  has_many :game_type_ranks, :order => "rank DESC"

  # schedule this function every hour.
  def self.update_rank
    update_rank_by(Game)
    update_rank_by(GameType)
  end

  def self.top_games(page_size=5)
    game_ranks.includes(:game).limit(page_size)
  end

  def self.top_games_by_type(page_size=5)
    game_type_ranks.includes(:game).limit(page_size)
  end

  def self.update_rank_by klass
    rank_class = (klass.name + "Rank").constantize
    rank_by_col = "#{klass.name.underscore}_id".to_sym        

    rank = total_points = rank_by = 0
    page = 1;page_size=1000
    sql = sum_points_by(rank_by_col)
    while(points= sql.limit(page_size).offset((page-1)*page_size)).present?
      page += 1
      User.trasaction do
        points.each do |point|
          rank_by_col_val = point.send(rank_by_col) 

          # calculate rank
          if ( rank_by != rank_by_col_val)
            rank = total_points = 0
            rank_by = rank_by_col_val
          end

          if point.total_points > total_points
            total_points = point.total_points
            rank +=1
          end

          create_or_update_rank_object(rank_class, rank_by_col, point.user_id, rank_by_col_val, total_points, rank)
        end
      end      
    end    
  end

  def self.sum_points_by(rank_by_col)
    select_sql = "points.user_id, points.#{rank_by_col}, 
                  SUM(points.points) total_points"
    Point.select(select_sql).group(:user_id, rank_by_col).
      order("#{rank_by_col}, total_points DESC")
  end

  def self.create_or_update_rank_object(rank_class, rank_by_col, user_id, rank_by_col_val, total_points, rank)
    ro = rank_class.send(
      "find_or_initialize_by_user_id_and_#{rank_by_col}",
      user_id, rank_by_col_val)
    ro.total_points = total_points           
    ro.rank = rank
    ro.save
  end
end

新しいモデルを追加して、すべてのユーザーとゲームIDのランクと合計ポイントを保持します

class GameTypeRank
  # Add columns total_points and rank 
  belongs_to :game_type
  belongs_to :user
end

新しいモデルを追加して、すべてのユーザーとゲームタイプのランクと合計ポイントを保持します

class GameRank
  # Add columns total_points and rank 
  belongs_to :game
  belongs_to :user
end

ユーザーのランク別に上位5つのゲームを取得するには

# array of game_id, game_name and game rank
current_user.top_games.map {|r| [r.game.id, r.game.name, r.rank]} 
# array of game_type_id, game_type_name and game rank
current_user.top_games_by_type.map { |r| 
  [r.game_type.id, r.game_type.name, r.rank]
} 
于 2013-03-04T22:37:34.147 に答える