Class: UserStat
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- UserStat
- Defined in:
- app/models/user_stat.rb
Constant Summary collapse
- UPDATE_UNREAD_MINUTES_AGO =
10
- UPDATE_UNREAD_USERS_LIMIT =
10_000
- MAX_TIME_READ_DIFF =
100
Class Method Summary collapse
- .cache_last_seen(id, val) ⇒ Object
- .ensure_consistency!(last_seen = 1.hour.ago) ⇒ Object
- .last_seen_cached(id) ⇒ Object
- .last_seen_key(id) ⇒ Object
- .reset_bounce_scores ⇒ Object
- .update_distinct_badge_count(user_id = nil) ⇒ Object
- .update_draft_count(user_id = nil) ⇒ Object
- .update_first_unread(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) ⇒ Object
- .update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) ⇒ Object
-
.update_time_read!(id) ⇒ Object
attempt to add total read time to user based on previous time this was called.
-
.update_view_counts(last_seen = 1.hour.ago) ⇒ Object
Updates the denormalized view counts for all users.
Instance Method Summary collapse
- #any_posts ⇒ Object
-
#calc_topic_reply_count!(start_time = nil) ⇒ Object
topic_reply_count is a count of posts in other users’ topics.
- #reset_bounce_score! ⇒ Object
- #update_distinct_badge_count ⇒ Object
- #update_pending_posts ⇒ Object
- #update_time_read! ⇒ Object
Class Method Details
.cache_last_seen(id, val) ⇒ Object
301 302 303 |
# File 'app/models/user_stat.rb', line 301 def self.cache_last_seen(id, val) Discourse.redis.setex(last_seen_key(id), MAX_TIME_READ_DIFF, val) end |
.ensure_consistency!(last_seen = 1.hour.ago) ⇒ Object
8 9 10 11 12 13 14 |
# File 'app/models/user_stat.rb', line 8 def self.ensure_consistency!(last_seen = 1.hour.ago) reset_bounce_scores update_distinct_badge_count update_view_counts(last_seen) update_first_unread(last_seen) update_first_unread_pm(last_seen) end |
.last_seen_cached(id) ⇒ Object
297 298 299 |
# File 'app/models/user_stat.rb', line 297 def self.last_seen_cached(id) Discourse.redis.get(last_seen_key(id)) end |
.last_seen_key(id) ⇒ Object
292 293 294 295 |
# File 'app/models/user_stat.rb', line 292 def self.last_seen_key(id) # frozen -"user-last-seen:#{id}" end |
.reset_bounce_scores ⇒ Object
145 146 147 148 149 150 |
# File 'app/models/user_stat.rb', line 145 def self.reset_bounce_scores UserStat .where("reset_bounce_score_after < now()") .where("bounce_score > 0") .update_all(bounce_score: 0) end |
.update_distinct_badge_count(user_id = nil) ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'app/models/user_stat.rb', line 195 def self.update_distinct_badge_count(user_id = nil) sql = <<~SQL UPDATE user_stats SET distinct_badge_count = x.distinct_badge_count FROM ( SELECT users.id user_id, COUNT(distinct user_badges.badge_id) distinct_badge_count FROM users LEFT JOIN user_badges ON user_badges.user_id = users.id AND (user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)) GROUP BY users.id ) x WHERE user_stats.user_id = x.user_id AND user_stats.distinct_badge_count <> x.distinct_badge_count SQL sql = sql + " AND user_stats.user_id = #{user_id.to_i}" if user_id DB.exec sql end |
.update_draft_count(user_id = nil) ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'app/models/user_stat.rb', line 218 def self.update_draft_count(user_id = nil) if user_id.present? draft_count, has_topic_draft = DB.query_single <<~SQL, user_id: user_id, new_topic: Draft::NEW_TOPIC UPDATE user_stats SET draft_count = (SELECT COUNT(*) FROM drafts WHERE user_id = :user_id) WHERE user_id = :user_id RETURNING draft_count, (SELECT 1 FROM drafts WHERE user_id = :user_id AND draft_key = :new_topic) SQL MessageBus.publish( "/user-drafts/#{user_id}", { draft_count: draft_count, has_topic_draft: !!has_topic_draft }, user_ids: [user_id], ) else DB.exec <<~SQL UPDATE user_stats SET draft_count = new_user_stats.draft_count FROM (SELECT user_stats.user_id, COUNT(drafts.id) draft_count FROM user_stats LEFT JOIN drafts ON user_stats.user_id = drafts.user_id GROUP BY user_stats.user_id) new_user_stats WHERE user_stats.user_id = new_user_stats.user_id AND user_stats.draft_count <> new_user_stats.draft_count SQL end end |
.update_first_unread(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'app/models/user_stat.rb', line 75 def self.update_first_unread(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) DB.exec(<<~SQL, min_date: last_seen, limit: limit, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago) UPDATE user_stats us SET first_unread_at = COALESCE(Y.min_date, :now) FROM ( SELECT u1.id user_id, X.min min_date FROM users u1 LEFT JOIN (SELECT u.id AS user_id, min(topics.updated_at) min FROM users u LEFT JOIN topic_users tu ON tu.user_id = u.id LEFT JOIN topics ON tu.topic_id = topics.id JOIN user_stats AS us ON us.user_id = u.id JOIN user_options AS uo ON uo.user_id = u.id JOIN categories c ON c.id = topics.category_id WHERE u.id IN ( SELECT id FROM users WHERE last_seen_at IS NOT NULL AND last_seen_at > :min_date ORDER BY last_seen_at DESC LIMIT :limit ) AND topics.archetype <> 'private_message' AND (("topics"."deleted_at" IS NULL AND tu.last_read_post_number < CASE WHEN u.admin OR u.moderator THEN topics.highest_staff_post_number ELSE topics.highest_post_number END AND COALESCE(tu.notification_level, 1) >= 2) OR (1=0)) AND (topics.visible OR u.admin OR u.moderator) AND topics.deleted_at IS NULL AND (NOT c.read_restricted OR u.admin OR category_id IN (SELECT c2.id FROM categories c2 JOIN category_groups cg ON cg.category_id = c2.id JOIN group_users gu ON gu.user_id = u.id AND cg.group_id = gu.group_id WHERE c2.read_restricted )) AND NOT EXISTS (SELECT 1 FROM category_users cu WHERE last_read_post_number IS NULL AND cu.user_id = u.id AND cu.category_id = topics.category_id AND cu.notification_level = 0) GROUP BY u.id, u.username) AS X ON X.user_id = u1.id WHERE u1.id IN ( SELECT id FROM users WHERE last_seen_at IS NOT NULL AND last_seen_at > :min_date ORDER BY last_seen_at DESC LIMIT :limit ) ) Y WHERE Y.user_id = us.user_id SQL end |
.update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'app/models/user_stat.rb', line 19 def self.update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) DB.exec( <<~SQL, UPDATE user_stats us SET first_unread_pm_at = COALESCE(Z.min_date, :now) FROM ( SELECT u1.id user_id, X.min_date FROM users u1 LEFT JOIN ( SELECT tau.user_id, MIN(t.updated_at) min_date FROM topic_allowed_users tau INNER JOIN topics t ON t.id = tau.topic_id INNER JOIN users u ON u.id = tau.user_id LEFT JOIN topic_users tu ON t.id = tu.topic_id AND tu.user_id = tau.user_id #{SiteSetting.whispers_allowed_groups_map.any? ? "LEFT JOIN group_users gu ON gu.group_id IN (:whisperers_group_ids) AND gu.user_id = u.id" : ""} WHERE t.deleted_at IS NULL AND t.archetype = :archetype AND tu.last_read_post_number < CASE WHEN u.admin OR u.moderator #{SiteSetting.whispers_allowed_groups_map.any? ? "OR gu.id IS NOT NULL" : ""} THEN t.highest_staff_post_number ELSE t.highest_post_number END AND (COALESCE(tu.notification_level, 1) >= 2) AND tau.user_id IN ( SELECT id FROM users WHERE last_seen_at IS NOT NULL AND last_seen_at > :last_seen ORDER BY last_seen_at DESC LIMIT :limit ) GROUP BY tau.user_id ) AS X ON X.user_id = u1.id WHERE u1.id IN ( SELECT id FROM users WHERE last_seen_at IS NOT NULL AND last_seen_at > :last_seen ORDER BY last_seen_at DESC LIMIT :limit ) ) AS Z WHERE us.user_id = Z.user_id SQL archetype: Archetype., now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit, whisperers_group_ids: SiteSetting.whispers_allowed_groups_map, ) end |
.update_time_read!(id) ⇒ Object
attempt to add total read time to user based on previous time this was called
272 273 274 275 276 277 278 279 280 281 282 |
# File 'app/models/user_stat.rb', line 272 def self.update_time_read!(id) if last_seen = last_seen_cached(id) diff = (Time.now.to_f - last_seen.to_f).round if diff > 0 && diff < MAX_TIME_READ_DIFF update_args = ["time_read = time_read + ?", diff] UserStat.where(user_id: id).update_all(update_args) UserVisit.where(user_id: id, visited_at: Time.zone.now.to_date).update_all(update_args) end end cache_last_seen(id, Time.now.to_f) end |
.update_view_counts(last_seen = 1.hour.ago) ⇒ Object
Updates the denormalized view counts for all users
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'app/models/user_stat.rb', line 153 def self.update_view_counts(last_seen = 1.hour.ago) # NOTE: we only update the counts for users we have seen in the last hour # this avoids a very expensive query that may run on the entire user base # we also ensure we only touch the table if data changes # Update denormalized topics_entered DB.exec(<<~SQL, seen_at: last_seen) UPDATE user_stats SET topics_entered = X.c FROM (SELECT v.user_id, COUNT(topic_id) AS c FROM topic_views AS v WHERE v.user_id IN ( SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at ) GROUP BY v.user_id) AS X WHERE X.user_id = user_stats.user_id AND X.c <> topics_entered SQL # Update denormalized posts_read_count DB.exec(<<~SQL, seen_at: last_seen) WITH filtered_users AS ( SELECT id FROM users u JOIN user_stats ON user_id = u.id WHERE last_seen_at > :seen_at AND posts_read_count < 10000 ) UPDATE user_stats SET posts_read_count = X.c FROM (SELECT pt.user_id, COUNT(*) as c FROM filtered_users AS u JOIN post_timings AS pt ON pt.user_id = u.id JOIN topics t ON t.id = pt.topic_id WHERE t.archetype = 'regular' AND t.deleted_at IS NULL GROUP BY pt.user_id ) AS X WHERE X.user_id = user_stats.user_id AND X.c <> posts_read_count SQL end |
Instance Method Details
#any_posts ⇒ Object
266 267 268 |
# File 'app/models/user_stat.rb', line 266 def any_posts user.posts.exists? end |
#calc_topic_reply_count!(start_time = nil) ⇒ Object
topic_reply_count is a count of posts in other users’ topics
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'app/models/user_stat.rb', line 248 def calc_topic_reply_count!(start_time = nil) sql = <<~SQL SELECT COUNT(DISTINCT posts.topic_id) AS count FROM posts INNER JOIN topics ON topics.id = posts.topic_id WHERE posts.user_id = ? AND topics.user_id <> posts.user_id AND posts.deleted_at IS NULL AND topics.deleted_at IS NULL AND topics.archetype <> 'private_message' #{start_time.nil? ? "" : "AND posts.created_at > ?"} SQL if start_time.nil? DB.query_single(sql, self.user_id).first else DB.query_single(sql, self.user_id, start_time).first end end |
#reset_bounce_score! ⇒ Object
288 289 290 |
# File 'app/models/user_stat.rb', line 288 def reset_bounce_score! update_columns(reset_bounce_score_after: nil, bounce_score: 0) end |
#update_distinct_badge_count ⇒ Object
214 215 216 |
# File 'app/models/user_stat.rb', line 214 def update_distinct_badge_count self.class.update_distinct_badge_count(self.user_id) end |
#update_pending_posts ⇒ Object
305 306 307 308 309 310 311 312 313 |
# File 'app/models/user_stat.rb', line 305 def update_pending_posts update(pending_posts_count: user.pending_posts.count) MessageBus.publish( "/u/#{user.username_lower}/counters", { pending_posts_count: pending_posts_count }, user_ids: [user.id], group_ids: [Group::AUTO_GROUPS[:staff]], ) end |
#update_time_read! ⇒ Object
284 285 286 |
# File 'app/models/user_stat.rb', line 284 def update_time_read! UserStat.update_time_read!(id) end |