Reputation: 6571
I have three models:
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:
points_earned
column to CourseCompletion that records how many points the user has earned for that completion.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:
points_recently_changed
equals trueAre there any glaring problems with this approach, or is there a "Rails Way" of doing stuff like this?
Upvotes: 0
Views: 73
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
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