在Ruby on Rails课程订阅系统中为课时添加完成状态
Hey there! Let's break down how to implement the "mark lesson as completed" feature for subscribed users. Here's a step-by-step solution tailored to your existing models:
CompletedLesson Model First, we need a way to track which lessons a user has finished. Since this is a many-to-many relationship between User and Lesson, create a join model to store completion status:
Run this Rails generator to create the model and migration:
rails generate model CompletedLesson user:references lesson:references completed_at:datetime rails db:migrate
This adds a table with user_id, lesson_id, and a completed_at timestamp to record when the lesson was marked done.
Update your existing models to include associations with the new CompletedLesson model:
User Model
class User < ApplicationRecord # Your existing associations has_many :subscriptions has_many :courses, through: :subscriptions # New associations for completed lessons has_many :completed_lessons has_many :completed_lessons_list, through: :completed_lessons, source: :lesson end
Lesson Model
class Lesson < ApplicationRecord belongs_to :course # New association has_many :completed_lessons has_many :users_who_completed, through: :completed_lessons, source: :user end
CompletedLesson Model
Add a uniqueness validation to prevent users from marking the same lesson completed multiple times accidentally:
class CompletedLesson < ApplicationRecord belongs_to :user belongs_to :lesson validates :user_id, uniqueness: { scope: :lesson_id, message: "has already completed this lesson" } end
Create an action to toggle the completion status of a lesson. We'll add this to the LessonsController (you could also make a dedicated CompletedLessonsController if preferred):
class LessonsController < ApplicationController before_action :authenticate_user! # Ensure user is logged in before_action :validate_subscription, only: [:mark_completed] def mark_completed lesson = Lesson.find(params[:id]) completed_lesson = current_user.completed_lessons.find_or_initialize_by(lesson: lesson) if completed_lesson.persisted? # If already completed, let's allow un-marking it completed_lesson.destroy render json: { status: "unmarked", message: "Lesson marked as incomplete" } else completed_lesson.completed_at = Time.current completed_lesson.save! render json: { status: "marked", message: "Lesson marked as completed" } end end private # Ensure the user is subscribed to the lesson's course def validate_subscription lesson = Lesson.find(params[:id]) unless current_user.subscriptions.exists?(course_id: lesson.course_id) render json: { error: "You must subscribe to the course first to mark lessons as completed" }, status: :forbidden end end end
Update your config/routes.rb to map the action to a URL:
resources :lessons do post :mark_completed, on: :member end
This creates a route like /lessons/:id/mark_completed that accepts POST requests.
Add a button in your lesson view that triggers the action. Here's an example using Rails ERB with Turbo for seamless updates:
<% lesson = @lesson %> <% if lesson_completed?(lesson, current_user) %> <%= button_to "Mark as Incomplete", mark_completed_lesson_path(lesson), method: :post, class: "btn btn-secondary", data: { lesson_id: lesson.id } %> <% else %> <%= button_to "Mark as Completed", mark_completed_lesson_path(lesson), method: :post, class: "btn btn-success", data: { lesson_id: lesson.id } %> <% end %>
Add a helper method in app/helpers/application_helper.rb to simplify checking completion status:
module ApplicationHelper def lesson_completed?(lesson, user) user.completed_lessons.exists?(lesson_id: lesson.id) end end
For AJAX-style updates (no page reload), add this JavaScript (using Turbo):
document.addEventListener('turbo:submit-end', (event) => { const form = event.target; if (form.action.includes('/mark_completed')) { const button = form.querySelector('button'); if (event.detail.success) { if (button.textContent.includes('Completed')) { button.textContent = 'Mark as Incomplete'; button.classList.replace('btn-success', 'btn-secondary'); } else { button.textContent = 'Mark as Completed'; button.classList.replace('btn-secondary', 'btn-success'); } } } });
- Security: The
validate_subscriptionmethod ensures only users who've subscribed to the course can mark lessons as completed—this prevents unauthorized access. - Uniqueness: The validation in
CompletedLessonstops duplicate entries for the same user and lesson. - Flexibility: The toggle functionality lets users un-mark lessons if they want to revisit them.
内容的提问来源于stack exchange,提问作者Cristian Echeverria

