Ask A Question


You’re not receiving notifications from this thread.

Strategy To Track Progress For Completed Nested Lessons

Dale Zak asked in Rails

I have models for Curriculums, Chapters and Lessons, and would like to track Progress for the user.

Curriculum -> has_many :chapters
Chapter -> has_many :lessons, belongs_to :curriculum
Lesson -> belongs_to :chapter
Progress -> belongs_to :curriculum, belongs_to :chapter, belongs_to :lesson, belongs_to :user

The Curriculums, Chapters and Lessons models have an ordinal for sorting but it's relative to its parent. To calculate percentage complete for a Lesson I'd need an index relative to Curriculum, aka its grandparent.

So I was thinking of storing position in Lesson, setting this value by loops through all Chapters and Lessons for a Toolkit, but now have a couple of questions.

1) If a Lesson is added or removed, or ordinal for Lesson is changed, how should I update the position for all Lessons? For example avoid getting in infinite loop using after_save in Lesson to trigger updating on Curriculum.

2) To set Progress completion, is it better to do:

lesson_index = curriculum.lessons.index(lesson)
progress.completion = (lesson_index.to_f / curriculum.lessons.count.to_f * 100).ceil


progress.completion = (lesson.position.to_f / curriculum.lessons.count.to_f * 100).ceil


# calculate and store completion inside each Lesson to avoid calculation
progress.completion = lesson.completion

Note, a Lesson is changed rarely where as a Progress is saved a lot, so want to make sure it's performant to avoid unnecessary database updates.

Is there a pattern or strategy for maintaining a grandchild's position like this?


It seems to me that you could break lessons into another discrete model, which then gets a boolean for completion. That would solve your ordinal problem because the percentage completed of Curriculum would just be the total number of those discrete components completed, regardless of where they sit on the tree. The count for Curriculum could also be essentially static, computed only when you update something yourself; that would make the on-the-fly overall calculation less expensive.

I think this would work best in particular if lessons don't need to be completed in a linear fashion.


Thanks Sean, this helps a lot!

With this approach, I don't care about the overall position in the tree at all, only the number of completed Lessons divided by total number of Lessons.

This way Lessons being re-ordered won't affect the Progress of existing users, the main factor for Progress is simplify the number of completed Lessons.

This is exactly what I was looking for and helps avoid a lot of unnecessary complexity. 🙏


You're welcome, Dale. Glad it helped :)

Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 83,603+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.