large refactor, ship a few columns from the user table into user_stats

This commit is contained in:
Sam 2013-10-04 13:28:49 +10:00
parent 4613006ae3
commit 5bf26ec34e
29 changed files with 354 additions and 251 deletions

View file

@ -17,7 +17,7 @@ module Jobs
CategoryFeaturedTopic.feature_topics CategoryFeaturedTopic.feature_topics
# Update view counts for users # Update view counts for users
User.update_view_counts UserStat.update_view_counts
# Update the scores of posts # Update the scores of posts
ScoreCalculator.new.calculate ScoreCalculator.new.calculate

View file

@ -61,7 +61,7 @@ class PostTiming < ActiveRecord::Base
def self.process_timings(current_user, topic_id, topic_time, timings) def self.process_timings(current_user, topic_id, topic_time, timings)
current_user.update_time_read! current_user.user_stat.update_time_read!
highest_seen = 1 highest_seen = 1
timings.each do |post_number, time| timings.each do |post_number, time|

View file

@ -191,18 +191,21 @@ end
# #
# Table name: site_customizations # Table name: site_customizations
# #
# id :integer not null, primary key # id :integer not null, primary key
# name :string(255) not null # name :string(255) not null
# stylesheet :text # stylesheet :text
# header :text # header :text
# position :integer not null # position :integer not null
# user_id :integer not null # user_id :integer not null
# enabled :boolean not null # enabled :boolean not null
# key :string(255) not null # key :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# override_default_style :boolean default(FALSE), not null # override_default_style :boolean default(FALSE), not null
# stylesheet_baked :text default(""), not null # stylesheet_baked :text default(""), not null
# mobile_stylesheet :text
# mobile_header :text
# mobile_stylesheet_baked :text
# #
# Indexes # Indexes
# #

View file

@ -663,7 +663,9 @@ end
# #
# Indexes # Indexes
# #
# idx_topics_user_id_deleted_at (user_id) # idx_topics_user_id_deleted_at (user_id)
# index_forum_threads_on_bumped_at (bumped_at) # index_forum_threads_on_bumped_at (bumped_at)
# index_topics_on_deleted_at_and_visible_and_archetype_and_id (deleted_at,visible,archetype,id)
# index_topics_on_id_and_deleted_at (id,deleted_at)
# #

View file

@ -106,8 +106,9 @@ end
# #
# Indexes # Indexes
# #
# index_uploads_on_sha1 (sha1) UNIQUE # index_uploads_on_id_and_url (id,url)
# index_uploads_on_url (url) # index_uploads_on_sha1 (sha1) UNIQUE
# index_uploads_on_user_id (user_id) # index_uploads_on_url (url)
# index_uploads_on_user_id (user_id)
# #

View file

@ -259,7 +259,7 @@ class User < ActiveRecord::Base
def update_visit_record!(date) def update_visit_record!(date)
unless has_visit_record?(date) unless has_visit_record?(date)
update_column(:days_visited, days_visited + 1) user_stat.update_column(:days_visited, user_stat.days_visited + 1)
user_visits.create!(visited_at: date) user_visits.create!(visited_at: date)
end end
end end
@ -316,42 +316,6 @@ class User < ActiveRecord::Base
uploaded_avatar_path || User.gravatar_template(email) uploaded_avatar_path || User.gravatar_template(email)
end end
# Updates the denormalized view counts for all users
def self.update_view_counts
# 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
exec_sql "UPDATE users SET topics_entered = X.c
FROM
(SELECT v.user_id,
COUNT(DISTINCT parent_id) AS c
FROM views AS v
WHERE parent_type = 'Topic' AND 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 = users.id AND
X.c <> topics_entered
", seen_at: 1.hour.ago
# Update denormalzied posts_read_count
exec_sql "UPDATE users SET posts_read_count = X.c
FROM
(SELECT pt.user_id,
COUNT(*) AS c
FROM post_timings AS pt
WHERE pt.user_id IN (
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
)
GROUP BY pt.user_id) AS X
WHERE X.user_id = users.id AND
X.c <> posts_read_count
", seen_at: 1.hour.ago
end
# The following count methods are somewhat slow - definitely don't use them in a loop. # The following count methods are somewhat slow - definitely don't use them in a loop.
# They might need to be denormalized # They might need to be denormalized
@ -458,20 +422,6 @@ class User < ActiveRecord::Base
end end
end end
MAX_TIME_READ_DIFF = 100
# attempt to add total read time to user based on previous time this was called
def update_time_read!
last_seen_key = "user-last-seen:#{id}"
last_seen = $redis.get(last_seen_key)
if last_seen.present?
diff = (Time.now.to_f - last_seen.to_f).round
if diff > 0 && diff < MAX_TIME_READ_DIFF
User.where(id: id, time_read: time_read).update_all ["time_read = time_read + ?", diff]
end
end
$redis.set(last_seen_key, Time.now.to_f)
end
def readable_name def readable_name
return "#{name} (#{username})" if name.present? && name != username return "#{name} (#{username})" if name.present? && name != username
username username
@ -486,18 +436,6 @@ class User < ActiveRecord::Base
where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count where('created_at > ?', sinceDaysAgo.days.ago).group('date(created_at)').order('date(created_at)').count
end end
def update_topic_reply_count
self.topic_reply_count =
Topic
.where(['id in (
SELECT topic_id FROM posts p
JOIN topics t2 ON t2.id = p.topic_id
WHERE p.deleted_at IS NULL AND
t2.user_id <> p.user_id AND
p.user_id = ?
)', self.id])
.count
end
def secure_category_ids def secure_category_ids
cats = self.staff? ? Category.where(read_restricted: true) : secure_categories.references(:categories) cats = self.staff? ? Category.where(read_restricted: true) : secure_categories.references(:categories)
@ -549,7 +487,7 @@ class User < ActiveRecord::Base
def create_user_stat def create_user_stat
stat = UserStat.new stat = UserStat.new
stat.user_id = self.id stat.user_id = id
stat.save! stat.save!
end end
@ -647,8 +585,6 @@ end
# approved :boolean default(FALSE), not null # approved :boolean default(FALSE), not null
# approved_by_id :integer # approved_by_id :integer
# approved_at :datetime # approved_at :datetime
# topics_entered :integer default(0), not null
# posts_read_count :integer default(0), not null
# digest_after_days :integer # digest_after_days :integer
# previous_visit_at :datetime # previous_visit_at :datetime
# banned_at :datetime # banned_at :datetime
@ -657,22 +593,18 @@ end
# auto_track_topics_after_msecs :integer # auto_track_topics_after_msecs :integer
# views :integer default(0), not null # views :integer default(0), not null
# flag_level :integer default(0), not null # flag_level :integer default(0), not null
# time_read :integer default(0), not null
# days_visited :integer default(0), not null
# ip_address :string # ip_address :string
# new_topic_duration_minutes :integer # new_topic_duration_minutes :integer
# external_links_in_new_tab :boolean default(FALSE), not null # external_links_in_new_tab :boolean default(FALSE), not null
# enable_quoting :boolean default(TRUE), not null # enable_quoting :boolean default(TRUE), not null
# moderator :boolean default(FALSE) # moderator :boolean default(FALSE)
# likes_given :integer default(0), not null
# likes_received :integer default(0), not null
# topic_reply_count :integer default(0), not null
# blocked :boolean default(FALSE) # blocked :boolean default(FALSE)
# dynamic_favicon :boolean default(FALSE), not null # dynamic_favicon :boolean default(FALSE), not null
# title :string(255) # title :string(255)
# use_uploaded_avatar :boolean default(FALSE) # use_uploaded_avatar :boolean default(FALSE)
# uploaded_avatar_template :string(255) # uploaded_avatar_template :string(255)
# uploaded_avatar_id :integer # uploaded_avatar_id :integer
# email_always :boolean default(FALSE), not null
# #
# Indexes # Indexes
# #

View file

@ -254,9 +254,9 @@ SQL
def self.update_like_count(user_id, action_type, delta) def self.update_like_count(user_id, action_type, delta)
if action_type == LIKE if action_type == LIKE
User.where(id: user_id).update_all("likes_given = likes_given + #{delta.to_i}") UserStat.where(user_id: user_id).update_all("likes_given = likes_given + #{delta.to_i}")
elsif action_type == WAS_LIKED elsif action_type == WAS_LIKED
User.where(id: user_id).update_all("likes_received = likes_received + #{delta.to_i}") UserStat.where(user_id: user_id).update_all("likes_received = likes_received + #{delta.to_i}")
end end
end end

View file

@ -66,11 +66,11 @@ end
# == Schema Information # == Schema Information
# #
# Table name: staff_action_logs # Table name: user_histories
# #
# id :integer not null, primary key # id :integer not null, primary key
# action :integer not null # action :integer not null
# staff_user_id :integer not null # acting_user_id :integer
# target_user_id :integer # target_user_id :integer
# details :text # details :text
# created_at :datetime not null # created_at :datetime not null
@ -81,12 +81,13 @@ end
# subject :text # subject :text
# previous_value :text # previous_value :text
# new_value :text # new_value :text
# topic_id :integer
# #
# Indexes # Indexes
# #
# index_staff_action_logs_on_action_and_id (action,id) # index_staff_action_logs_on_action_and_id (action,id)
# index_staff_action_logs_on_staff_user_id_and_id (staff_user_id,id) # index_staff_action_logs_on_subject_and_id (subject,id)
# index_staff_action_logs_on_subject_and_id (subject,id) # index_staff_action_logs_on_target_user_id_and_id (target_user_id,id)
# index_staff_action_logs_on_target_user_id_and_id (target_user_id,id) # index_user_histories_on_acting_user_id_and_action_and_id (acting_user_id,action,id)
# #

View file

@ -2,4 +2,84 @@ class UserStat < ActiveRecord::Base
belongs_to :user belongs_to :user
# Updates the denormalized view counts for all users
def self.update_view_counts
# 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
exec_sql "UPDATE user_stats SET topics_entered = X.c
FROM
(SELECT v.user_id,
COUNT(DISTINCT parent_id) AS c
FROM views AS v
WHERE parent_type = 'Topic' AND 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
", seen_at: 1.hour.ago
# Update denormalzied posts_read_count
exec_sql "UPDATE user_stats SET posts_read_count = X.c
FROM
(SELECT pt.user_id,
COUNT(*) AS c
FROM post_timings AS pt
WHERE pt.user_id IN (
SELECT u1.id FROM users u1 where u1.last_seen_at > :seen_at
)
GROUP BY pt.user_id) AS X
WHERE X.user_id = user_stats.user_id AND
X.c <> posts_read_count
", seen_at: 1.hour.ago
end
def update_topic_reply_count
self.topic_reply_count =
Topic
.where(['id in (
SELECT topic_id FROM posts p
JOIN topics t2 ON t2.id = p.topic_id
WHERE p.deleted_at IS NULL AND
t2.user_id <> p.user_id AND
p.user_id = ?
)', self.user_id])
.count
end
MAX_TIME_READ_DIFF = 100
# attempt to add total read time to user based on previous time this was called
def update_time_read!
last_seen_key = "user-last-seen:#{id}"
last_seen = $redis.get(last_seen_key)
if last_seen.present?
diff = (Time.now.to_f - last_seen.to_f).round
if diff > 0 && diff < MAX_TIME_READ_DIFF
UserStat.where(user_id: id, time_read: time_read).update_all ["time_read = time_read + ?", diff]
end
end
$redis.set(last_seen_key, Time.now.to_f)
end
end end
# == Schema Information
#
# Table name: user_stats
#
# user_id :integer not null, primary key
# has_custom_avatar :boolean default(FALSE), not null
# topics_entered :integer default(0), not null
# time_read :integer default(0), not null
# days_visited :integer default(0), not null
# posts_read_count :integer default(0), not null
# likes_given :integer default(0), not null
# likes_received :integer default(0), not null
# topic_reply_count :integer default(0), not null
#

View file

@ -7,13 +7,13 @@ class UserVisit < ActiveRecord::Base
def self.ensure_consistency! def self.ensure_consistency!
exec_sql <<SQL exec_sql <<SQL
UPDATE users u set days_visited = UPDATE user_stats u set days_visited =
( (
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.id SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
) )
WHERE days_visited <> WHERE days_visited <>
( (
SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.id SELECT COUNT(*) FROM user_visits v WHERE v.user_id = u.user_id
) )
SQL SQL
end end

View file

@ -43,6 +43,7 @@ end
# #
# Indexes # Indexes
# #
# index_views_on_parent_id_and_parent_type (parent_id,parent_type) # index_views_on_parent_id_and_parent_type (parent_id,parent_type)
# index_views_on_user_id_and_parent_type_and_parent_id (user_id,parent_type,parent_id)
# #

View file

@ -5,7 +5,6 @@ class AdminUserSerializer < BasicUserSerializer
:admin, :admin,
:moderator, :moderator,
:last_seen_age, :last_seen_age,
:days_visited,
:last_emailed_age, :last_emailed_age,
:created_at_age, :created_at_age,
:username_lower, :username_lower,
@ -14,9 +13,6 @@ class AdminUserSerializer < BasicUserSerializer
:username, :username,
:title, :title,
:avatar_template, :avatar_template,
:topics_entered,
:posts_read_count,
:time_read,
:can_approve, :can_approve,
:approved, :approved,
:banned_at, :banned_at,
@ -26,7 +22,15 @@ class AdminUserSerializer < BasicUserSerializer
:can_send_activation_email, :can_send_activation_email,
:can_activate, :can_activate,
:can_deactivate, :can_deactivate,
:blocked :blocked,
:time_read
[:days_visited,:posts_read_count,:topics_entered].each do |sym|
attributes sym
define_method sym do
object.user_stat.send(sym)
end
end
def is_banned def is_banned
object.is_banned? object.is_banned?
@ -47,8 +51,8 @@ class AdminUserSerializer < BasicUserSerializer
end end
def time_read def time_read
return nil if object.time_read.blank? return nil if object.user_stat.time_read.blank?
AgeWords.age_words(object.time_read) AgeWords.age_words(object.user_stat.time_read)
end end
def created_at_age def created_at_age

View file

@ -25,7 +25,7 @@ class CurrentUserSerializer < BasicUserSerializer
end end
def reply_count def reply_count
object.topic_reply_count object.user_stat.topic_reply_count
end end
def site_flagged_posts_count def site_flagged_posts_count

View file

@ -0,0 +1,61 @@
class MoveColumnsToUserStats < ActiveRecord::Migration
def up
add_column :user_stats, :topics_entered, :integer, default: 0, null: false
add_column :user_stats, :time_read, :integer, default: 0, null: false
add_column :user_stats, :days_visited, :integer, default: 0, null: false
add_column :user_stats, :posts_read_count, :integer, default: 0, null: false
add_column :user_stats, :likes_given, :integer, default: 0, null: false
add_column :user_stats, :likes_received, :integer, default: 0, null: false
add_column :user_stats, :topic_reply_count, :integer, default: 0, null: false
execute 'UPDATE user_stats
SET topics_entered = u.topics_entered,
time_read = u.time_read,
days_visited = u.days_visited,
posts_read_count = u.posts_read_count,
likes_given = u.likes_given,
likes_received = u.likes_received,
topic_reply_count = u.topic_reply_count
FROM user_stats s
JOIN users u on u.id = s.user_id
'
remove_column :users, :topics_entered
remove_column :users, :time_read
remove_column :users, :days_visited
remove_column :users, :posts_read_count
remove_column :users, :likes_given
remove_column :users, :likes_received
remove_column :users, :topic_reply_count
end
def down
add_column :users, :topics_entered, :integer
add_column :users, :time_read, :integer
add_column :users, :days_visited, :integer
add_column :users, :posts_read_count, :integer
add_column :users, :likes_given, :integer
add_column :users, :likes_received, :integer
add_column :users, :topic_reply_count, :integer
execute 'UPDATE users
SET topics_entered = u.topics_entered,
time_read = u.time_read,
days_visited = u.days_visited,
posts_read_count = u.posts_read_count,
likes_given = u.likes_given,
likes_received = u.likes_received,
topic_reply_count = u.topic_reply_count
FROM users s
JOIN user_stats u on s.id = u.user_id
'
remove_column :user_stats, :topics_entered
remove_column :user_stats, :time_read
remove_column :user_stats, :days_visited
remove_column :user_stats, :posts_read_count
remove_column :user_stats, :likes_given
remove_column :user_stats, :likes_received
remove_column :user_stats, :topic_reply_count
end
end

View file

@ -116,6 +116,7 @@ class Autospec::Runner
puts s puts s
end end
puts puts
queue_specs(specs.zip specs)
end end
end end
end end

View file

@ -425,10 +425,13 @@ class Guardian
private private
def is_my_own?(obj) def is_my_own?(obj)
@user.present? &&
(obj.respond_to?(:user) || obj.respond_to?(:user_id)) && unless anonymous?
(obj.respond_to?(:user) ? obj.user == @user : true) && return obj.user_id == @user.id if obj.respond_to?(:user_id)
(obj.respond_to?(:user_id) ? (obj.user_id == @user.id) : true) return obj.user == @user if obj.respond_to?(:user)
end
false
end end
def is_me?(other) def is_me?(other)

View file

@ -232,7 +232,8 @@ class PostCreator
def update_user_counts def update_user_counts
# We don't count replies to your own topics # We don't count replies to your own topics
if @user.id != @topic.user_id if @user.id != @topic.user_id
@user.update_topic_reply_count @user.user_stat.update_topic_reply_count
@user.user_stat.save!
end end
@user.last_posted_at = @post.created_at @user.last_posted_at = @post.created_at

View file

@ -22,9 +22,10 @@ class Promotion
end end
def review_newuser def review_newuser
return false if @user.topics_entered < SiteSetting.basic_requires_topics_entered stat = @user.user_stat
return false if @user.posts_read_count < SiteSetting.basic_requires_read_posts return false if stat.topics_entered < SiteSetting.basic_requires_topics_entered
return false if (@user.time_read / 60) < SiteSetting.basic_requires_time_spent_mins return false if stat.posts_read_count < SiteSetting.basic_requires_read_posts
return false if (stat.time_read / 60) < SiteSetting.basic_requires_time_spent_mins
@user.change_trust_level!(:basic) @user.change_trust_level!(:basic)
@ -32,13 +33,14 @@ class Promotion
end end
def review_basic def review_basic
return false if @user.topics_entered < SiteSetting.regular_requires_topics_entered stat = @user.user_stat
return false if @user.posts_read_count < SiteSetting.regular_requires_read_posts return false if stat.topics_entered < SiteSetting.regular_requires_topics_entered
return false if (@user.time_read / 60) < SiteSetting.regular_requires_time_spent_mins return false if stat.posts_read_count < SiteSetting.regular_requires_read_posts
return false if @user.days_visited < SiteSetting.regular_requires_days_visited return false if (stat.time_read / 60) < SiteSetting.regular_requires_time_spent_mins
return false if @user.likes_received < SiteSetting.regular_requires_likes_received return false if stat.days_visited < SiteSetting.regular_requires_days_visited
return false if @user.likes_given < SiteSetting.regular_requires_likes_given return false if stat.likes_received < SiteSetting.regular_requires_likes_received
return false if @user.topic_reply_count < SiteSetting.regular_requires_topic_reply_count return false if stat.likes_given < SiteSetting.regular_requires_likes_given
return false if stat.topic_reply_count < SiteSetting.regular_requires_topic_reply_count
@user.change_trust_level!(:regular) @user.change_trust_level!(:regular)
end end

View file

@ -39,9 +39,10 @@ describe BoostTrustLevel do
context "for a user that has done the requisite things to attain their trust level" do context "for a user that has done the requisite things to attain their trust level" do
before do before do
user.topics_entered = SiteSetting.basic_requires_topics_entered + 1 stat = user.user_stat
user.posts_read_count = SiteSetting.basic_requires_read_posts + 1 stat.topics_entered = SiteSetting.basic_requires_topics_entered + 1
user.time_read = SiteSetting.basic_requires_time_spent_mins * 60 stat.posts_read_count = SiteSetting.basic_requires_read_posts + 1
stat.time_read = SiteSetting.basic_requires_time_spent_mins * 60
user.save! user.save!
user.update_attributes(trust_level: TrustLevel.levels[:basic]) user.update_attributes(trust_level: TrustLevel.levels[:basic])
end end

View file

@ -163,12 +163,15 @@ describe PostCreator do
topic_user.seen_post_count.should == first_post.post_number topic_user.seen_post_count.should == first_post.post_number
user2 = Fabricate(:coding_horror) user2 = Fabricate(:coding_horror)
user2.topic_reply_count.should == 0 user2.user_stat.topic_reply_count.should == 0
first_post.user.reload.topic_reply_count.should == 0
first_post.user.user_stat.reload.topic_reply_count.should == 0
PostCreator.new(user2, topic_id: first_post.topic_id, raw: "this is my test post 123").create PostCreator.new(user2, topic_id: first_post.topic_id, raw: "this is my test post 123").create
user2.reload.topic_reply_count.should == 1
first_post.user.reload.topic_reply_count.should == 0 first_post.user.user_stat.reload.topic_reply_count.should == 0
user2.user_stat.reload.topic_reply_count.should == 1
end end
end end

View file

@ -27,9 +27,10 @@ describe Promotion do
context "that has done the requisite things" do context "that has done the requisite things" do
before do before do
user.topics_entered = SiteSetting.basic_requires_topics_entered stat = user.user_stat
user.posts_read_count = SiteSetting.basic_requires_read_posts stat.topics_entered = SiteSetting.basic_requires_topics_entered
user.time_read = SiteSetting.basic_requires_time_spent_mins * 60 stat.posts_read_count = SiteSetting.basic_requires_read_posts
stat.time_read = SiteSetting.basic_requires_time_spent_mins * 60
@result = promotion.review @result = promotion.review
end end
@ -64,13 +65,14 @@ describe Promotion do
context "that has done the requisite things" do context "that has done the requisite things" do
before do before do
user.topics_entered = SiteSetting.regular_requires_topics_entered stat = user.user_stat
user.posts_read_count = SiteSetting.regular_requires_read_posts stat.topics_entered = SiteSetting.regular_requires_topics_entered
user.time_read = SiteSetting.regular_requires_time_spent_mins * 60 stat.posts_read_count = SiteSetting.regular_requires_read_posts
user.days_visited = SiteSetting.regular_requires_days_visited * 60 stat.time_read = SiteSetting.regular_requires_time_spent_mins * 60
user.likes_received = SiteSetting.regular_requires_likes_received stat.days_visited = SiteSetting.regular_requires_days_visited * 60
user.likes_given = SiteSetting.regular_requires_likes_given stat.likes_received = SiteSetting.regular_requires_likes_received
user.topic_reply_count = SiteSetting.regular_requires_topic_reply_count stat.likes_given = SiteSetting.regular_requires_likes_given
stat.topic_reply_count = SiteSetting.regular_requires_topic_reply_count
@result = promotion.review @result = promotion.review
end end

View file

@ -145,10 +145,11 @@ describe Admin::UsersController do
it "raises an error when demoting a user below their current trust level" do it "raises an error when demoting a user below their current trust level" do
StaffActionLogger.any_instance.expects(:log_trust_level_change).never StaffActionLogger.any_instance.expects(:log_trust_level_change).never
@another_user.topics_entered = SiteSetting.basic_requires_topics_entered + 1 stat = @another_user.user_stat
@another_user.posts_read_count = SiteSetting.basic_requires_read_posts + 1 stat.topics_entered = SiteSetting.basic_requires_topics_entered + 1
@another_user.time_read = SiteSetting.basic_requires_time_spent_mins * 60 stat.posts_read_count = SiteSetting.basic_requires_read_posts + 1
@another_user.save! stat.time_read = SiteSetting.basic_requires_time_spent_mins * 60
stat.save!
@another_user.update_attributes(trust_level: TrustLevel.levels[:basic]) @another_user.update_attributes(trust_level: TrustLevel.levels[:basic])
xhr :put, :trust_level, user_id: @another_user.id, level: TrustLevel.levels[:newuser] xhr :put, :trust_level, user_id: @another_user.id, level: TrustLevel.levels[:newuser]
response.should be_forbidden response.should be_forbidden

View file

@ -1,6 +1,4 @@
Fabricator(:user_action) do Fabricator(:user_action) do
user user
action_type UserAction::STAR action_type UserAction::STAR
end end

View file

@ -1,3 +1,6 @@
Fabricator(:user_stat) do
end
Fabricator(:user) do Fabricator(:user) do
name 'Bruce Wayne' name 'Bruce Wayne'
username { sequence(:username) { |i| "bruce#{i}" } } username { sequence(:username) { |i| "bruce#{i}" } }
@ -57,4 +60,4 @@ Fabricator(:active_user, from: :user) do
trust_level TrustLevel.levels[:basic] trust_level TrustLevel.levels[:basic]
active true active true
bio_raw "Don't as me about my dad!" bio_raw "Don't as me about my dad!"
end end

View file

@ -20,7 +20,7 @@ describe Jobs::PeriodicalUpdates do
end end
it "updates view counts" do it "updates view counts" do
User.expects(:update_view_counts).once UserStat.expects(:update_view_counts).once
end end
it "calculates scores" do it "calculates scores" do

View file

@ -123,12 +123,12 @@ describe UserAction do
it 'should result in correct data assignment' do it 'should result in correct data assignment' do
@liker_action.should_not be_nil @liker_action.should_not be_nil
@likee_action.should_not be_nil @likee_action.should_not be_nil
likee.reload.likes_received.should == 1 likee.user_stat.reload.likes_received.should == 1
liker.reload.likes_given.should == 1 liker.user_stat.reload.likes_given.should == 1
PostAction.remove_act(liker, post, PostActionType.types[:like]) PostAction.remove_act(liker, post, PostActionType.types[:like])
likee.reload.likes_received.should == 0 likee.user_stat.reload.likes_received.should == 0
liker.reload.likes_given.should == 0 liker.user_stat.reload.likes_given.should == 0
end end
end end

View file

@ -1,4 +1,5 @@
require 'spec_helper' require 'spec_helper'
require_dependency 'user'
describe User do describe User do
@ -21,66 +22,6 @@ describe User do
it { should validate_presence_of :username } it { should validate_presence_of :username }
it { should validate_presence_of :email } it { should validate_presence_of :email }
context '#update_view_counts' do
let(:user) { Fabricate(:user) }
context 'topics_entered' do
context 'without any views' do
it "doesn't increase the user's topics_entered" do
lambda { User.update_view_counts; user.reload }.should_not change(user, :topics_entered)
end
end
context 'with a view' do
let(:topic) { Fabricate(:topic) }
let!(:view) { View.create_for(topic, '127.0.0.1', user) }
before do
user.update_column :last_seen_at, 1.second.ago
end
it "adds one to the topics entered" do
User.update_view_counts
user.reload
user.topics_entered.should == 1
end
it "won't record a second view as a different topic" do
View.create_for(topic, '127.0.0.1', user)
User.update_view_counts
user.reload
user.topics_entered.should == 1
end
end
end
context 'posts_read_count' do
context 'without any post timings' do
it "doesn't increase the user's posts_read_count" do
lambda { User.update_view_counts; user.reload }.should_not change(user, :posts_read_count)
end
end
context 'with a post timing' do
let!(:post) { Fabricate(:post) }
let!(:post_timings) do
PostTiming.record_timing(msecs: 1234, topic_id: post.topic_id, user_id: user.id, post_number: post.post_number)
end
before do
user.update_column :last_seen_at, 1.second.ago
end
it "increases posts_read_count" do
User.update_view_counts
user.reload
user.posts_read_count.should == 1
end
end
end
end
context '.enqueue_welcome_message' do context '.enqueue_welcome_message' do
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
@ -266,7 +207,6 @@ describe User do
its(:approved_by_id) { should be_blank } its(:approved_by_id) { should be_blank }
its(:email_private_messages) { should be_true } its(:email_private_messages) { should be_true }
its(:email_direct ) { should be_true } its(:email_direct ) { should be_true }
its(:time_read) { should == 0}
context 'digest emails' do context 'digest emails' do
it 'defaults to digests every week' do it 'defaults to digests every week' do
@ -295,8 +235,6 @@ describe User do
its(:email_tokens) { should be_present } its(:email_tokens) { should be_present }
its(:bio_cooked) { should be_present } its(:bio_cooked) { should be_present }
its(:bio_summary) { should be_present } its(:bio_summary) { should be_present }
its(:topics_entered) { should == 0 }
its(:posts_read_count) { should == 0 }
end end
end end
@ -664,7 +602,7 @@ describe User do
end end
it "should have 0 for days_visited" do it "should have 0 for days_visited" do
user.days_visited.should == 0 user.user_stat.days_visited.should == 0
end end
describe 'with no previous values' do describe 'with no previous values' do
@ -685,7 +623,7 @@ describe User do
it "should have 0 for days_visited" do it "should have 0 for days_visited" do
user.reload user.reload
user.days_visited.should == 1 user.user_stat.days_visited.should == 1
end end
it "should log a user_visit with the date" do it "should log a user_visit with the date" do
@ -706,7 +644,7 @@ describe User do
end end
it "doesn't increase days_visited twice" do it "doesn't increase days_visited twice" do
user.days_visited.should == 1 user.user_stat.days_visited.should == 1
end end
end end
@ -811,33 +749,6 @@ describe User do
end end
describe 'update_time_read!' do
let(:user) { Fabricate(:user) }
it 'makes no changes if nothing is cached' do
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(nil)
user.update_time_read!
user.reload
user.time_read.should == 0
end
it 'makes a change if time read is below threshold' do
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(Time.now - 10.0)
user.update_time_read!
user.reload
user.time_read.should == 10
end
it 'makes no change if time read is above threshold' do
t = Time.now - 1 - User::MAX_TIME_READ_DIFF
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(t)
user.update_time_read!
user.reload
user.time_read.should == 0
end
end
describe '#readable_name' do describe '#readable_name' do
context 'when name is missing' do context 'when name is missing' do
it 'returns just the username' do it 'returns just the username' do

View file

@ -9,4 +9,96 @@ describe UserStat do
user.user_stat.should be_present user.user_stat.should be_present
end end
end context '#update_view_counts' do
let(:user) { Fabricate(:user) }
let(:stat) { user.user_stat }
context 'topics_entered' do
context 'without any views' do
it "doesn't increase the user's topics_entered" do
lambda { UserStat.update_view_counts; stat.reload }.should_not change(stat, :topics_entered)
end
end
context 'with a view' do
let(:topic) { Fabricate(:topic) }
let!(:view) { View.create_for(topic, '127.0.0.1', user) }
before do
user.update_column :last_seen_at, 1.second.ago
end
it "adds one to the topics entered" do
UserStat.update_view_counts
stat.reload
stat.topics_entered.should == 1
end
it "won't record a second view as a different topic" do
View.create_for(topic, '127.0.0.1', user)
UserStat.update_view_counts
stat.reload
stat.topics_entered.should == 1
end
end
end
context 'posts_read_count' do
context 'without any post timings' do
it "doesn't increase the user's posts_read_count" do
lambda { UserStat.update_view_counts; stat.reload }.should_not change(stat, :posts_read_count)
end
end
context 'with a post timing' do
let!(:post) { Fabricate(:post) }
let!(:post_timings) do
PostTiming.record_timing(msecs: 1234, topic_id: post.topic_id, user_id: user.id, post_number: post.post_number)
end
before do
user.update_column :last_seen_at, 1.second.ago
end
it "increases posts_read_count" do
UserStat.update_view_counts
stat.reload
stat.posts_read_count.should == 1
end
end
end
end
describe 'update_time_read!' do
let(:user) { Fabricate(:user) }
let(:stat) { user.user_stat }
it 'makes no changes if nothing is cached' do
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(nil)
stat.update_time_read!
stat.reload
stat.time_read.should == 0
end
it 'makes a change if time read is below threshold' do
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(Time.now - 10.0)
stat.update_time_read!
stat.reload
stat.time_read.should == 10
end
it 'makes no change if time read is above threshold' do
t = Time.now - 1 - UserStat::MAX_TIME_READ_DIFF
$redis.expects(:get).with("user-last-seen:#{user.id}").returns(t)
stat.update_time_read!
stat.reload
stat.time_read.should == 0
end
end
end

View file

@ -9,13 +9,13 @@ describe UserVisit do
u.update_visit_record!(1.day.ago.to_date) u.update_visit_record!(1.day.ago.to_date)
u.reload u.reload
u.days_visited.should == 2 u.user_stat.days_visited.should == 2
u.days_visited = 1 u.user_stat.days_visited = 1
u.save u.save
UserVisit.ensure_consistency! UserVisit.ensure_consistency!
u.reload u.reload
u.days_visited.should == 2 u.user_stat.days_visited.should == 2
end end
end end