-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reimplemented StudentQuizzesController #112
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
class Api::V1::StudentQuizzesController < ApplicationController | ||
include AuthorizationHelper | ||
|
||
# Check if the current user is authorized to perform the action | ||
def action_allowed? | ||
if current_user_is_a? 'Student' | ||
if action_name.eql? 'index' | ||
are_needed_authorizations_present?(params[:id], 'reviewer', 'submitter') | ||
else | ||
true | ||
end | ||
else | ||
current_user_has_ta_privileges? && user_ta_for_current_course? | ||
end | ||
end | ||
|
||
# Fetches and lists all quiz response mappings for a specific participant in an assignment. | ||
def index | ||
@participant = AssignmentParticipant.find(params[:id]) | ||
# Ensures the current user is authorized to view the participant's quiz mappings. | ||
return unless current_user_id?(@participant.user_id) | ||
|
||
@assignment = Assignment.find(@participant.parent_id) | ||
@quiz_mappings = ResponseMap.mappings_for_reviewer(@participant.id) | ||
end | ||
|
||
# Displays the questions and participant responses for a completed quiz | ||
def show_quiz_responses | ||
@response = Response.find(params[:response_id]) | ||
@response_map = ResponseMap.find(params[:map_id]) | ||
@questions = Question.where(questionnaire_id: @response_map.reviewed_object_id) | ||
@participant = AssignmentTeam.find(@response_map.reviewee_id).participants.first | ||
@quiz_score = @response.aggregate_questionnaire_score # Use the score calculated by Response model | ||
end | ||
|
||
# Fetches quizzes for a given assignment that a reviewer has not yet taken. | ||
def fetch_available_quizzes_for_reviewer(assignment_id, reviewer_id) | ||
quizzes = [] | ||
reviewer = Participant.find_by(user_id: reviewer_id, parent_id: assignment_id) | ||
review_response_maps = ReviewResponseMap.where(reviewer_id: reviewer.id) | ||
|
||
review_response_maps.each do |response_map| | ||
reviewee_team = Team.find(response_map.reviewee_id) | ||
|
||
next unless reviewee_team.parent_id == assignment_id | ||
|
||
quiz_questionnaire = Questionnaire.find_by(instructor_id: reviewee_team.id) | ||
|
||
if quiz_questionnaire && !quiz_questionnaire.started_by?(reviewer) | ||
quizzes << quiz_questionnaire | ||
end | ||
end | ||
|
||
quizzes | ||
end | ||
|
||
# Submits the quiz response and calculates the score. | ||
def submit_quiz | ||
map = ResponseMap.find(params[:map_id]) | ||
# Check if there is any response for this map_id. This is to prevent student from taking the same quiz twice. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are embedding a POLICY into this mechanism. Bad design! Let the instructor decide whether someone can take a quiz over, e.g., to improve their score. |
||
if map.response.empty? | ||
response = Response.create(map_id: params[:map_id], created_at: DateTime.current, updated_at: DateTime.current) | ||
if response.calculate_score(params) # TODO: add score calculation logic | ||
redirect_to controller: 'student_quizzes', action: 'show_finished_quiz', map_id: map.id | ||
else | ||
flash[:error] = 'Please answer every question.' | ||
redirect_to action: :fetch_available_quizzes_for_reviewer, assignment_id: params[:assignment_id], questionnaire_id: response.response_map.reviewed_object_id, map_id: map.id | ||
end | ||
else | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another POLICY that belongs elsewhere. Did you ever skip a question on a test? Why don't you want to let other students do that? |
||
flash[:error] = 'You have already taken this quiz, below are the records for your responses.' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "below is the record of your responses." |
||
redirect_to controller: 'student_quizzes', action: 'show_finished_quiz', map_id: map.id | ||
end | ||
end | ||
|
||
# Provides a list of quiz questionnaires for a given assignment. | ||
# This method is called when instructors click "view quiz questions" on the pop-up panel. | ||
def view_questions | ||
@assignment_id = params[:id] | ||
@quiz_questionnaires = [] | ||
Team.where(parent_id: params[:id]).each do |quiz_creator| | ||
Questionnaire.where(instructor_id: quiz_creator.id).each do |questionnaire| | ||
@quiz_questionnaires.push questionnaire | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
class ApplicationController < ActionController::API | ||
include JwtToken | ||
|
||
# Check if a participant has given authorizations | ||
def are_needed_authorizations_present?(id, *authorizations) | ||
participant = Participant.find_by(id: id) | ||
return false if participant.nil? | ||
|
||
authorization = participant.authorization | ||
!authorizations.include?(authorization) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module AuthorizationHelper | ||
|
||
# Determine if the currently logged-in user has the privileges of a TA (or higher) | ||
def current_user_has_ta_privileges? | ||
current_user_has_privileges_of?('Teaching Assistant') | ||
end | ||
|
||
# Determine if the currently logged-in user has the privileges of a Student (or higher) | ||
def current_user_has_student_privileges? | ||
current_user_has_privileges_of?('Student') | ||
end | ||
|
||
private | ||
|
||
# Determine if the currently logged-in user has the privileges of the given role name (or higher privileges) | ||
# If there is no currently logged-in user return false | ||
def current_user_has_privileges_of?(role_name) | ||
current_user_and_role_exist? && session[:user].role.has_all_privileges_of?(Role.find_by(name: role_name)) | ||
end | ||
|
||
# Check whether user is logged-in and user role exists | ||
SahithiAmmana marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def current_user_and_role_exist? | ||
user_logged_in? && !session[:user].role.nil? | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,19 @@ class Question < ApplicationRecord | |
before_create :set_seq | ||
belongs_to :questionnaire # each question belongs to a specific questionnaire | ||
has_many :answers, dependent: :destroy | ||
|
||
has_many :quiz_question_choices, class_name: 'QuizQuestionChoice', foreign_key: 'question_id' | ||
|
||
validates :seq, presence: true, numericality: true # sequence must be numeric | ||
validates :txt, length: { minimum: 0, allow_nil: false, message: "can't be nil" } # user must define text content for a question | ||
validates :question_type, presence: true # user must define type for a question | ||
validates :break_before, presence: true | ||
|
||
QUESTION_TYPES = { | ||
'MultipleChoiceCheckbox' => :calculate_score_for_checkbox_question, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "MultipleChocieTextbox" --> "Checkbox" |
||
'TrueFalse' => :calculate_score_for_truefalse_question, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Method name is too complicated. Couldn't there just be a score method implemented in Response, that could be overridden where necessary? -Ed |
||
'MultipleChoiceRadio' => :calculate_score_for_truefalse_question | ||
}.freeze | ||
|
||
def scorable? | ||
false | ||
end | ||
|
@@ -25,4 +32,38 @@ def as_json(options = {}) | |
})).tap do |hash| | ||
end | ||
end | ||
|
||
# Calculates the score for a question based on the type and user answers | ||
def calculate_score(params) | ||
correct_answers = quiz_question_choices.where(iscorrect: true) | ||
user_answers = params[id.to_s] | ||
|
||
case question_type | ||
when 'MultipleChoiceCheckbox' | ||
calculate_score_for_checkbox_question(correct_answers, user_answers) | ||
when 'TrueFalse', 'MultipleChoiceRadio' | ||
calculate_score_for_truefalse_question(correct_answers.first, user_answers) | ||
else | ||
0 # Default score for unsupported question types | ||
end | ||
end | ||
|
||
private | ||
|
||
# Calculates score for MultipleChoiceCheckbox type questions | ||
def calculate_score_for_checkbox_question(correct_answers, user_answers) | ||
return 0 if user_answers.nil? | ||
|
||
score = 0 | ||
correct_answers.each do |correct| | ||
score += 1 if user_answers.include?(correct.txt) | ||
end | ||
|
||
score == correct_answers.count && score == user_answers.count ? 1 : 0 | ||
end | ||
|
||
# Calculates score for TrueFalse and MultipleChoiceRadio type questions | ||
def calculate_score_for_truefalse_question(correct_answer, user_answer) | ||
correct_answer.txt == user_answer ? 1 : 0 | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
class QuizQuestionChoice < ApplicationRecord | ||
belongs_to :question, dependent: :destroy | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to cycle through all the response maps? Couldn't we just do a find to find the response map with the right parent?