Skip to content
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

E2474: Reimplement student_quizzes_controller.rb #141

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a42201a
Added previous implementation
neerua08 Dec 3, 2024
cc5b456
Merge pull request #2 from neerua08/prathyu99-main
neerua08 Dec 3, 2024
36a79e6
Deployed app
neerua08 Dec 3, 2024
c1dd8c8
Moved calculate score to Response map
neerua08 Dec 3, 2024
1ea6ff1
Merge pull request #3 from neerua08/calculate_score_srp
neerua08 Dec 3, 2024
dacecb6
Moved process_answer to response_map
neerua08 Dec 3, 2024
fe2b3a2
Merge pull request #4 from neerua08/process_answer_srp
neerua08 Dec 3, 2024
55d8607
Call the method in role for srp
neerua08 Dec 3, 2024
03392a6
Merge pull request #5 from neerua08/check_instructor_role_srp
neerua08 Dec 3, 2024
0dc53dd
Fixed DRY for finding resource
neerua08 Dec 3, 2024
005fe73
Merge pull request #6 from neerua08/find_resource_by_id_DRY
neerua08 Dec 3, 2024
b20ff7d
Refactored student_quiz names
neerua08 Dec 3, 2024
5c47438
Merge pull request #7 from neerua08/rename_methods_and_variables
neerua08 Dec 3, 2024
865857e
Refactored assign_quiz_to_student
KrishnaPallavalli Dec 4, 2024
c7bb760
Merge branch 'main' of https://github.com/neerua08/reimplementation-b…
KrishnaPallavalli Dec 4, 2024
e0e33dc
updated the db seed file to create more users
Dec 4, 2024
72f51de
Merge branch 'main' of https://github.com/neerua08/reimplementation-b…
Dec 4, 2024
e17e80a
Delete 2
KrishnaPallavalli Dec 4, 2024
9b3c034
Moved methods and removed unnecessary methods
neerua08 Dec 4, 2024
c0198db
Added concern for find_resource_by_id
neerua08 Dec 4, 2024
846a35f
Merge pull request #8 from neerua08/srp_changes
neerua08 Dec 8, 2024
fcf7d5f
Updated seed.rb
neerua08 Dec 8, 2024
d8ad7b3
Updated seed.rb with more questions
neerua08 Dec 8, 2024
04c1de4
made some edits to controller - postman runs fine;
Dec 9, 2024
ecec66d
Merge branch 'main' of https://github.com/neerua08/reimplementation-b…
Dec 9, 2024
f682741
Reverted DRY changes in previous commit
neerua08 Dec 10, 2024
2865f50
did stuff to make it work
Dec 11, 2024
0b46807
Fixed name change in test
neerua08 Dec 11, 2024
c0f263e
Fixed name change in test
neerua08 Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions app/controllers/api/v1/assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def destroy
render json: { error: "Assignment not found" }, status: :not_found
end
end

#add participant to assignment
def add_participant
assignment = Assignment.find_by(id: params[:assignment_id])
Expand Down Expand Up @@ -92,7 +92,7 @@ def remove_assignment_from_course
render json: assignment.errors, status: :unprocessable_entity
end
end

end

#update course id of an assignment/ assign the assign to some together course
Expand Down Expand Up @@ -146,7 +146,7 @@ def show_assignment_details
end

# check if assignment has topics
# has_topics is set to true if there is SignUpTopic corresponding to the input assignment id
# has_topics is set to true if there is SignUpTopic corresponding to the input assignment id
def has_topics
assignment = Assignment.find_by(id: params[:assignment_id])
if assignment.nil?
Expand All @@ -156,7 +156,7 @@ def has_topics
end
end

# check if assignment is a team assignment
# check if assignment is a team assignment
# true if assignment's max team size is greater than 1
def team_assignment
assignment = Assignment.find_by(id: params[:assignment_id])
Expand Down Expand Up @@ -204,11 +204,12 @@ def varying_rubrics_by_round?
end
end
end

private
# Only allow a list of trusted parameters through.
def assignment_params
params.require(:assignment).permit(:title, :description)
params.require(:assignment).permit(:name, :directory_path, :course_id, :instructor_id,
:require_quiz, :num_quiz_questions, :description)
end

# Helper method to determine staggered_and_no_topic for the assignment
Expand Down
35 changes: 35 additions & 0 deletions app/controllers/api/v1/participants_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class Api::V1::ParticipantsController < ApplicationController
before_action :find_user, only: :create
before_action :find_assignment, only: :create
# POST /participants
def create
participant = Participant.new(participant_params)

if participant.save
render json: participant, status: :created
else
render json: participant.errors, status: :unprocessable_entity
end
end

private

# to fetch user
def find_user
unless User.exists?(params[:participant][:user_id])
render json: { error: 'User does not exist' }, status: :not_found and return
end
end

#to find assignment in the db
def find_assignment
unless Assignment.exists?(params[:participant][:assignment_id])
render json: { error: 'Assignment does not exist' }, status: :not_found and return
end
end

#to check params of a participant
def participant_params
params.require(:participant).permit(:user_id, :assignment_id)
end
end
8 changes: 6 additions & 2 deletions app/controllers/api/v1/questionnaires_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def toggle_access
@questionnaire.toggle!(:private)
@access = @questionnaire.private ? 'private' : 'public'
render json: "The questionnaire \"#{@questionnaire.name}\" has been successfully made #{@access}. ",
status: :ok
status: :ok
rescue ActiveRecord::RecordNotFound
render json: $ERROR_INFO.to_s, status: :not_found
rescue ActiveRecord::RecordInvalid
Expand All @@ -87,15 +87,19 @@ def toggle_access
private

def questionnaire_params
params.require(:questionnaire).permit(:name, :questionnaire_type, :private, :min_question_score, :max_question_score, :instructor_id)
params.require(:questionnaire).permit(:name, :questionnaire_type, :private, :min_question_score,
:max_question_score, :instructor_id, :assignment_id)
end

def sanitize_display_type(type)
return nil if type.nil? # Return immediately if type is nil

display_type = type.split('Questionnaire')[0]
if %w[AuthorFeedback CourseSurvey TeammateReview GlobalSurvey AssignmentSurvey BookmarkRating].include?(display_type)
display_type = (display_type.split(/(?=[A-Z])/)).join('%')
end
display_type
end


end
6 changes: 3 additions & 3 deletions app/controllers/api/v1/questions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def create
question = questionnaire.questions.build(
txt: params[:txt],
question_type: params[:question_type],
break_before: true
break_before: true,
skippable: params[:skippable]
)

case question.question_type
Expand Down Expand Up @@ -120,10 +121,9 @@ def types
end

private

# Only allow a list of trusted parameters through.
def question_params
params.permit(:txt, :weight, :seq, :questionnaire_id, :question_type, :size,
:alternatives, :break_before, :max_label, :min_label)
:alternatives, :break_before, :max_label, :min_label, :assignment_id, :correct_answer, :score_value)
end
end
188 changes: 188 additions & 0 deletions app/controllers/api/v1/student_quizzes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
class Api::V1::StudentQuizzesController < ApplicationController
before_action :check_instructor_role, except: [:submit_quiz]
before_action :set_student_quiz, only: [:show, :update, :destroy]

include ResourceFinder

# Rescue from ActiveRecord::RecordInvalid exceptions and render an error response
rescue_from ActiveRecord::RecordInvalid do |exception|
render_error(exception.message)
end

# GET /student_quizzes
# Fetch and render all quizzes
def index
quizzes = Questionnaire.all
render_success(quizzes)
end

# GET /student_quizzes/:id
# Fetch and render a specific quiz by ID
def show
render_success(@student_quiz)
end

# GET /student_quizzes/:id/get_score
# Calculate and render the score for a specific quiz attempt
def get_score
response_map = ResponseMap.find_by(id: params[:id])
if response_map
render_success({ score: response_map.get_score })
else
render_error('Attempt not found or you do not have permission to view this score.', :not_found)
end
end

# POST /student_quizzes
# Create a new quiz with associated questions and answers
def create
questionnaire = ActiveRecord::Base.transaction do
questionnaire = create_questionnaire(questionnaire_params.except(:questions_attributes))
create_questions_and_answers(questionnaire, questionnaire_params[:questions_attributes])
questionnaire
end
render_success(questionnaire, :created)
rescue StandardError => e
render_error(e.message, :unprocessable_entity)
end

# Helper method to create a questionnaire
# @param params [Hash] the parameters for creating a questionnaire
# @return [Questionnaire] the created questionnaire
def create_questionnaire(params)
Questionnaire.create!(params)
end

# POST /student_quizzes/assign
# Assign a quiz to a student
def assign_quiz
participant = find_resource_by_id(Participant, params[:participant_id])
questionnaire = find_resource_by_id(Questionnaire, params[:questionnaire_id])

# Stop execution if either the participant or questionnaire does not exist
return unless participant && questionnaire

# Check if the quiz has already been assigned to this student
if quiz_already_assigned?(participant, questionnaire)
render_error("This student is already assigned to the quiz.", :unprocessable_entity)
return
end

# Create a new response map to link the student and the quiz
response_map = ResponseMap.build_response_map(participant.user_id, questionnaire)

# Attempt to save the response map and render appropriate success or error messages
if response_map.save
render_success(response_map, :created)
else
render_error(response_map.errors.full_messages.to_sentence, :unprocessable_entity)
end
end

# POST /student_quizzes/submit_answers
# Submit answers for a quiz and calculate the total score
def submit_quiz
ActiveRecord::Base.transaction do
response_map = find_response_map_for_current_user
unless response_map
render_error("You are not assigned to take this quiz.", :forbidden)
return
end

total_score = response_map.process_answers(params[:answers])
response_map.update!(score: total_score)
response_map.update!(score: total_score)
render_success({ total_score: total_score })
end
rescue ActiveRecord::RecordInvalid => e
render_error(e.message, :unprocessable_entity)
end

# PUT /student_quizzes/:id
# Update a specific quiz by ID
def update
if @student_quiz.update(questionnaire_params)
render_success(@student_quiz)
else
render_error(@student_quiz.errors.full_messages.to_sentence, :unprocessable_entity)
end
end

# DELETE /student_quizzes/:id
# Delete a specific quiz by ID
def destroy
@student_quiz.destroy
render json: { message: "Quiz with id #{params[:id]}, deleted" }, status: :ok
rescue ActiveRecord::RecordNotFound
render_error('Record does not exist', :not_found)
end

private

# Fetch and set the quiz from the database
# @return [void]
def set_student_quiz
@student_quiz = find_resource_by_id(Questionnaire, params[:id])
end

# Find the response map for the current user's attempt to submit quiz answers
# @return [ResponseMap, nil] the response map if found, otherwise nil
def find_response_map_for_current_user
ResponseMap.find_by(reviewee_id: current_user.id, reviewed_object_id: params[:questionnaire_id])
end

# Check if the quiz has already been assigned to the student
# @param participant [Participant] the participant
# @param questionnaire [Questionnaire] the questionnaire
# @return [Boolean] true if the quiz is already assigned, false otherwise
def quiz_already_assigned?(participant, questionnaire)
ResponseMap.exists?(reviewee_id: participant.user_id, reviewed_object_id: questionnaire.id)
end

# Create questions and their respective answers for a questionnaire
# @param questionnaire [Questionnaire] the questionnaire
# @param questions_attributes [Array<Hash>] the attributes for the questions
# @return [void]
def create_questions_and_answers(questionnaire, questions_attributes)
questions_attributes.each do |question_attr|
question = questionnaire.questions.create!(question_attr.except(:answers_attributes))
question_attr[:answers_attributes]&.each do |answer_attr|
question.answers.create!(answer_attr)
end
end
end

# Permit and require the necessary parameters for creating/updating a questionnaire
# @return [ActionController::Parameters] the permitted parameters
def questionnaire_params
params.require(:questionnaire).permit(
:name, :instructor_id, :min_question_score, :max_question_score, :assignment_id,
questions_attributes: [:id, :txt, :question_type, :break_before, :correct_answer, :score_value,
{ answers_attributes: %i[id answer_text correct] }]
)
end

# Render a success response with optional custom status code
# @param data [Object] the data to render
# @param status [Symbol] the HTTP status code
# @return [void]
def render_success(data, status = :ok)
render json: data, status: status
end

# Render an error response with message and status code
# @param message [String] the error message
# @param status [Symbol] the HTTP status code
# @return [void]
def render_error(message, status = :unprocessable_entity)
render json: { error: message }, status: status
end

# Ensure only instructors can perform certain actions
# @return [void]
def check_instructor_role
unless current_user.role.instructor?
render_error('Only instructors are allowed to perform this action', :forbidden)
end
end
end
17 changes: 16 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
class ApplicationController < ActionController::API
include JwtToken
end

def find_resource_by_id(model, id)
model.find(id)
rescue ActiveRecord::RecordNotFound
render_error("#{model.name} not found", :not_found)
nil
end

def render_success(data, status = :ok)
render json: data, status: status
end

def render_error(message, status = :unprocessable_entity)
render json: { error: message }, status: status
end
end
17 changes: 17 additions & 0 deletions app/controllers/concerns/resource_finder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module ResourceFinder
extend ActiveSupport::Concern

included do

# Find a resource by its ID and handle the case where it is not found
# @param resource [Class] the resource class to search
# @param id [Integer] the ID of the resource
# @return [Object, nil] the found resource or nil if not found
def find_resource_by_id(resource, id)
resource.find(id)
rescue ActiveRecord::RecordNotFound
render_error("#{resource.name} not found", :not_found)
nil
end
end
end
3 changes: 2 additions & 1 deletion app/models/answer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
class Answer < ApplicationRecord
belongs_to :response
belongs_to :question
validates :answer_text, presence: true
validates :correct, inclusion: {in: [true, false]}
end
Loading