You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby on Rails游戏类应用中用户评分统计追踪的最优方案咨询

嘿,我来帮你梳理下在Rails里实现这个类游戏应用评分统计系统的最优方案,结合你的需求一步步来:

核心模型与数据库设计

首先得把核心的模型关系捋顺,你需要一个Rating模型来记录用户之间的评分,关联评分者和被评分者,还要存一个布尔字段标记是否通过:

# app/models/rating.rb
class Rating < ApplicationRecord
  # 关联评分者(发起评分的用户)
  belongs_to :rater, class_name: "User"
  # 关联被评分者(被打分的用户)
  belongs_to :ratee, class_name: "User"
  # 确保passed只能是true/false
  validates :passed, inclusion: { in: [true, false] }
end

然后生成对应的迁移文件,记得加上复合索引——这对后续的统计查询速度影响很大:

# db/migrate/xxxx_create_ratings.rb
class CreateRatings < ActiveRecord::Migration[7.0]
  def change
    create_table :ratings do |t|
      t.references :rater, null: false, foreign_key: { to_table: :users }
      t.references :ratee, null: false, foreign_key: { to_table: :users }
      t.boolean :passed, null: false

      t.timestamps
    end

    # 加复合索引,优化按被评分者+通过状态、评分者+通过状态的查询
    add_index :ratings, [:ratee_id, :passed]
    add_index :ratings, [:rater_id, :passed]
  end
end

别忘了在User模型里加上对应的关联关系,方便后续查询:

# app/models/user.rb
class User < ApplicationRecord
  # 用户作为评分者发起的所有评分
  has_many :ratings, foreign_key: :rater_id, dependent: :destroy
  # 用户作为被评分者收到的所有评分
  has_many :received_ratings, class_name: "Rating", foreign_key: :ratee_id, dependent: :destroy
end
两类统计逻辑的实现

你需要的两类统计,我都封装成User模型的方法,这样调用起来非常方便:

1. 统计用户所通过对象的属性分布

也就是当前用户作为评分者,给哪些用户打了通过,这些用户的各属性值的计数:

# app/models/user.rb
# 传入属性名(比如:skill_level),返回该属性各值的计数哈希
def passed_ratees_attribute_stats(attribute)
  # 只筛选通过的评分,关联被评分用户,按属性分组统计数量
  self.ratings.where(passed: true)
             .joins(:ratee)
             .group("users.#{attribute}")
             .count
end

调用示例:current_user.passed_ratees_attribute_stats(:style),会返回类似{"casual"=>8, "aggressive"=>15}的结果。

2. 统计给用户打通过的用户的属性分布

也就是当前用户作为被评分者,哪些用户给自己打了通过,这些评分者的各属性值的计数:

# app/models/user.rb
def passed_raters_attribute_stats(attribute)
  self.received_ratings.where(passed: true)
                      .joins(:rater)
                      .group("users.#{attribute}")
                      .count
end

调用示例:current_user.passed_raters_attribute_stats(:skill_level),返回评分者的技能等级分布。

性能优化:缓存与预计算

如果你的应用用户量和评分量不大,直接用上面的实时查询就够了,但如果数据量上去了,建议做以下优化:

方案一:Rails内置缓存

把统计结果缓存起来,设置过期时间,同时在有新评分创建时主动清除相关缓存,保证数据新鲜:

# app/models/user.rb
def passed_ratees_attribute_stats(attribute)
  # 缓存键包含用户ID、属性名,避免冲突
  Rails.cache.fetch(["user_#{id}", "passed_ratees", attribute], expires_in: 1.hour) do
    self.ratings.where(passed: true)
               .joins(:ratee)
               .group("users.#{attribute}")
               .count
  end
end

def passed_raters_attribute_stats(attribute)
  Rails.cache.fetch(["user_#{id}", "passed_raters", attribute], expires_in: 1.hour) do
    self.received_ratings.where(passed: true)
                        .joins(:rater)
                        .group("users.#{attribute}")
                        .count
  end
end

然后在Rating模型里加回调,创建新评分时清除相关用户的缓存:

# app/models/rating.rb
after_create :clear_related_stats_cache

private

def clear_related_stats_cache
  # 清除评分者的统计缓存
  Rails.cache.delete_matched(["user_#{rater_id}", "passed_ratees", "*"])
  # 清除被评分者的统计缓存
  Rails.cache.delete_matched(["user_#{ratee_id}", "passed_raters", "*"])
end

方案二:预计算统计值(适合大规模应用)

如果你的评分量达到百万级,实时查询+缓存还是慢,可以用物化视图或者后台定时任务(比如Sidekiq)预计算统计值,存在单独的统计表中。比如创建一个UserStat模型,存储每个用户的两类统计数据,每天/每小时跑一次任务更新,这样前端页面直接查统计表就行,速度飞快。

统计页面开发

在控制器中获取统计数据,传给视图:

# app/controllers/stats_controller.rb
class StatsController < ApplicationController
  before_action :authenticate_user!

  def show
    @user = current_user
    # 把你的10个属性放到这个数组里,比如[:style, :skill_level, :play_mode]
    @target_attributes = [:style, :skill_level, ...]
    
    # 整理两类统计数据,方便视图遍历
    @ratees_stats = @target_attributes.each_with_object({}) do |attr, hash|
      hash[attr] = @user.passed_ratees_attribute_stats(attr)
    end
    @raters_stats = @target_attributes.each_with_object({}) do |attr, hash|
      hash[attr] = @user.passed_raters_attribute_stats(attr)
    end
  end
end

视图里可以用表格或者图表展示,这里给个简单的表格示例,你也可以用Chart.js之类的前端库做可视化:

<!-- app/views/stats/show.html.erb -->
<h1>我的评分统计</h1>

<div class="stats-section">
  <h2>我通过的用户属性分布</h2>
  <% @target_attributes.each do |attr| %>
    <h3><%= attr.humanize %></h3>
    <table class="stats-table">
      <thead>
        <tr>
          <th>属性值</th>
          <th>计数</th>
        </tr>
      </thead>
      <tbody>
        <% @ratees_stats[attr].each do |value, count| %>
          <tr>
            <td><%= value || "未设置" %></td>
            <td><%= count %></td>
          </tr>
        <% end %>
      </tbody>
    </table>
  <% end %>
</div>

<div class="stats-section">
  <h2>给我打通过的用户属性分布</h2>
  <% @target_attributes.each do |attr| %>
    <h3><%= attr.humanize %></h3>
    <table class="stats-table">
      <thead>
        <tr>
          <th>属性值</th>
          <th>计数</th>
        </tr>
      </thead>
      <tbody>
        <% @raters_stats[attr].each do |value, count| %>
          <tr>
            <td><%= value || "未设置" %></td>
            <td><%= count %></td>
          </tr>
        <% end %>
      </tbody>
    </table>
  <% end %>
</div>
最优方案总结

根据你的应用规模选对应的方案:

  • 小规模应用:直接用实时查询+简单缓存,开发快,维护简单。
  • 中等规模应用:用缓存+回调更新的方式,平衡性能和复杂度。
  • 大规模应用:预计算统计值(物化视图/定时任务),把查询压力转移到后台,保证前端响应速度。

另外,一定要记得加复合索引——这是提升统计查询速度最有效的手段之一。

内容的提问来源于stack exchange,提问作者Steve Carey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:58:59