1

Edit #1

Here is what I have now

<% if course.complete? %>
  <%= link_to "Completed", course, class: "block text-lg w-full text-center text-white px-4 py-2 bg-green hover:bg-green-dark border-2 border-green-dark leading-none no-underline" %>
<% else %>
  <%= link_to "View Modules", course, class: "block text-lg w-full text-center text-grey-dark hover:text-darker px-4 py-2 border-2 border-grey leading-none no-underline hover:border-2 hover:border-grey-dark" %>
<% end %>

course_module.rb

class CourseModule < ApplicationRecord
  extend FriendlyId
  friendly_id :title, use: :slugged

  belongs_to :course
  has_many :course_exercises

  validates :title, :course_id, presence: true

  scope :completed, -> { where(complete: true) }

  after_save :course_completed

  private

  def course_completed
    course = course_module.course
    course.update(complete: true) if course.course_modules.all?(&:complete?)
  end
end

The names match so no issues there, however, how would I go about calling the model method?

Original Question

Currently, I have courses and within the course I have modules both have complete columns which are a boolean, currently, students can complete modules by clicking on the tick like so

image

I have the courses database like so

course

and the modules table like so

modules

But I want the functionality to work so when the last module is marked as complete, the course itself gets marked as complete in the database, currently in the course_modules_controller.rb I have

def complete
  @course_module = CourseModule.friendly.find(params[:id])
  @course_module.complete = true
  @course_module.save
  redirect_to courses_path, notice: 'Module completed, congratulations!'
end

This works for the modules, but I want to mark the course as complete if the last task has been marked as complete.

3 Answers3

2

You can do this in your controller or in an after_save in your model, but it'll look something like this:

course = course_module.course
course.update(complete: true) if course.course_modules.all?(&:complete?)

May require minor edits if the names don't quite match your schema.

elliotcm
  • 760
  • 4
  • 7
1

Seems this logic would belong in the CourseModule not the Course since the CourseModule being completed is the trigger.

Something akin to

class Course < ApplicationRecord 

  def complete!
    update_attribute(:complete,true) unless complete?
  end
end 

class CourseModule < ApplicationRecord
  scope :completed, -> {where(complete: true) }

  after_save :update_course, if: :complete?

  private
    def update_course
      course.complete! if course.course_modules.count == course.course_modules.completed.count
    end
end 

If a CourseModule can become incomplete after being completed then you will have to change a bit of the logic here.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52
0

So the answer to this question is a combination of both @elliotcm and @engineersmnky's answers.

The final answer is this:

course.rb

class Course < ApplicationRecord
  has_many :course_modules

  def complete!
    update_attribute(:complete, true)
  end
end

course_module.rb

class CourseModule < ApplicationRecord
  belongs_to :course

  scope :completed, -> { where(complete: true) }
  scope :course_completed, -> { where(complete: true) }

  after_save :update_course, if: :complete?

  private

  def update_course
    course.complete! if course.course_modules.all?(&:complete?)
  end
end

Thank you both for your help in this!

Update

Above solution will run the callback on every save of course_module after it is complete. And will update the course.complete every time (if all its course modules are complete) even if it was already marked complete. Avoid all that by modifying the callbacks a bit:

class CourseModule < ApplicationRecord
  after_save :update_course, if: -> { complete_changed? && complete? }

  private

  def update_course
    return if course.complete? || CourseModule.where(course_id: course_id, complete: false).exists?
    course.complete!
  end
end

Note: You also need to handle the case where a course_module get incompleted and so, its corresponding course also needs to be marked incomplete.

Jagdeep Singh
  • 4,880
  • 2
  • 17
  • 22
  • You have duplicate scopes with exact same criteria in model `CourseModule`. – Jagdeep Singh Aug 22 '18 at 16:08
  • @BJB please see my edit. Hope it improves the code without adding any bugs. #NotTested – Jagdeep Singh Aug 22 '18 at 16:17
  • @JagdeepSingh why would you not just post an answer? – engineersmnky Aug 22 '18 at 16:43
  • Yeah here's something I didn't think about. The course and modules are being marked as completed for all users, is there a way to do this by the user id? –  Aug 22 '18 at 16:44
  • @B.J.B you offered no insight into Course -> User Relationships so we cannot assist with this without understanding that relationship as well – engineersmnky Aug 22 '18 at 16:46
  • @engineersmnky https://gyazo.com/e9d28bed78599d34695e79e4c5e4a917 https://gyazo.com/c91de147481d1ad803f5a2c632bdc049 a course is able to has_many :users and a user has_many :courses –  Aug 22 '18 at 16:53
  • @B.J.B this should technically be a HABTM relationship in this case which will involve a join table. Also it means a `Course` can never be "complete" as completion is a state of the relationship not the `Course` itself. You will need to rethink your design a bit to make this work appropriately – engineersmnky Aug 22 '18 at 16:56
  • @engineersmnky So only the modules can be complete? Would it be worth creating a new question here to go about joining the table? Because I've never dealt with a HABTM relationship before. –  Aug 22 '18 at 17:02
  • *So only the modules can be complete?* No nothing can be "complete". *Would it be worth creating a new question here to go about joining the table?* Yes as this strays completely from the current question and to be honest the answers provided here will not really help you where you are going. – engineersmnky Aug 22 '18 at 17:04