Ege Ersoz
Ege Ersoz

Reputation: 6571

Retroactive changes to user records (point reconciliation)

I have three models:

  1. Course
  2. User
  3. CourseCompletion

In addition to stuff like title and content, each course also has a point value. When a user completes a course, they are awarded the point value for the course, which is added to their total_points. CourseCompletion simply tracks which user has completed which courses (with columns user_id, course_id and completion_date).

One weakness with this data model is that if an admin user edits the point value of a course after a user has completed that course, the user's points are not updated. I need a way to do this retroactively. For example, if a user completes a course and earns 10 points, and then an admin changes the course to be worth 20 points, the user should have 20 points total in the end. I haven't done this sort of thing before - what would be the best approach?

My current plan is two-fold. In the first part, I make changes to the Course and CourseCompletion models:

  1. Add a points_earned column to CourseCompletion that records how many points the user has earned for that completion.
  2. Add a points_recently_changed column to Course. If a course's points are updated at any time, set this column to true for that course. (see my related question)

In the second part, a script or task (?) runs once per day and does the following:

  1. Get all courses where points_recently_changed equals true
  2. Find all course completions for those courses
  3. Calculate the difference between course.points and course_completion.points_earned
  4. Update the corresponding user's point total accordingly
  5. Change course.points_recently_updated back to false.

Are there any glaring problems with this approach, or is there a "Rails Way" of doing stuff like this?

Upvotes: 0

Views: 73

Answers (2)

Ege Ersoz
Ege Ersoz

Reputation: 6571

I tried Jorge's suggestion but was not satisfied with it. I ended up going with a similar approach whereby I recalculate a user's points during the login process.

User.rb

def recalculate_points
  new_course_points = self.course_completion_courses.sum(:worth)
  self.update_attribute(:points, self.course_completion_courses.sum(:worth))
  self.save
end

session_helper.rb

def sign_in(user)
  ...
  current_user.recalculate_points
end

Points are still stored in the User table - simply caching them doesn't work because I do some reporting that needs that information to persist in the database.

Upvotes: 0

Jorge de los Santos
Jorge de los Santos

Reputation: 4633

Why don't you use ActiveRecord::Calculations to get the sum of the points for the whole related courses and store them in the column. Update the column each time the admin does a change in the course points.

You can track changes in the points using ActiveRecord::Dirty

http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

And calculate points using Calculations:

http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html

As a possible solution:

class Course < ActiveRecord::Base
  before_save :update_user_points

  def update_user_points
    User.all.each {|user| user.update_points } if self.points_changed?
  end
end

class User < ActiveRecord::Base

  def update_points
    self.points = self.courses.sum(:points)
  end

end

Suggestion:

I dislike saving the points in the database as it is a variable. I suggest you to do the calculation each time the user logins and keep it as cached number with expire date. So it has to be recalculated each day.

Upvotes: 1

Related Questions