E2409 Refactor sign up sheet controller.rb: Difference between revisions
No edit summary |
|||
Line 69: | Line 69: | ||
'''After refactoring''' | '''After refactoring''' | ||
<syntaxhighlight> | |||
def delete_signup_common(who_user, participant, assignment, drop_topic_deadline, topic_id) | def delete_signup_common(who_user, participant, assignment, drop_topic_deadline, topic_id) | ||
# This method centralizes the shared functionality, reducing redundancy and improving maintainability | # This method centralizes the shared functionality, reducing redundancy and improving maintainability | ||
Line 125: | Line 125: | ||
redirect_to controller: 'assignments', action: 'edit', id: assignment.id | redirect_to controller: 'assignments', action: 'edit', id: assignment.id | ||
end | end | ||
</syntaxhighlight> | |||
*'''Refactoring the Update method [https://github.com/Harshil47/expertiza/commit/eb1e4417552f1067f4085ff5920addfaaaab5494 Commit]''' | *'''Refactoring the Update method [https://github.com/Harshil47/expertiza/commit/eb1e4417552f1067f4085ff5920addfaaaab5494 Commit]''' | ||
Revision as of 15:45, 1 May 2024
This wiki page is for the information regarding the changes made for the E2409 OSS assignment for Spring 2024, CSC/ECE 517.
Introduction
Expertiza is an open source project developed on Ruby on Rails. This web application is maintained by the student and faculty at NC State. This application gives complete control to the instructor to maintain the assignments in their class. With multiple functionalities such as adding topics, creating groups, and peer reviews, Expertiza is a well-developed application that can handle all types of assignments. To learn more about the full functionality Expertiza has to offer, visit the Expertiza wiki.
About Controller
The sign up sheet controller comprises all functions related to the management of the signup sheet for an assignment.
- Instructors can add and remove topics from assignments, as well as alter their attributes.
- Allows an instructor to assign and remove students to subjects.
- Allows a student to view a list of available subjects on which they can bid for an OSS assignment.
Issues/Problem Statement
- Naming is inconsistent. When used as a verb, “sign up” is two words. When an adjective or a noun, it should be one word, e.g., “signup_sheet_controller.” Cf. “sign_up_as_instructor”. Please make the naming consistent. Of course, this will result in changes in calling methods as well. [Mostly fixed by previous version of project.]
- Update method has a plethora of instance variables defined before updating. These might not be necessary (e.g., look at update method of bookmarks_controller). Decide whether so many instance variables are really needed. Refactor the variables not needed out. [This may have already been fixed, but save_topic_deadline also has these problems.]
- Add_signup_topics_staggered does not do anything different from add_signup_topics. Separate functions are needed, because add_signup_topics_staggered needs to make sure that the deadlines are set. [Assignment 1042 has staggered deadlines]
- Several method names can be improved (including: load_add_signup_topics, list, ad_info etc.)
- What are differences between signup_as_instructor and signup_as_instructor_action methods? Investigate if they are needed and improve the method names if both are needed. Provide comments as to what each method does.
- The list method is too long and is sparsely commented. Provide comments and identify if the method can be split or made cleaner by moving code to models or helper methods.
- Refactor participants variable in load_add_signup_topics [In retrospect, the meaning of this is not clear. The @participants variable is used in a way that is very obscure, with code spread across several views, and no comments saying what it is doing. It used to be that participants (individual students) signed up for topics. Now, only teams can sign up for topics. So @participants do not make sense.
- Signup_as_instructor_action has an if-else ladder. It can be made more elegant. [If it is worth keeping this method at all.]
- Delete_signup and delete_signup_as_instructor have much in common and violate the DRY principle. Refactor.
- Also see Code Climate issues. To reduce the size of the file, you could move some methods to sign_up_sheet_helper.rb [We are trying to fix how you access Code Climate, but haven’t fixed it yet.]
Design Plan
Proposed Changes
- Update the list method. The list method is too big and can be splitted to make use of helper functions. The if and unless checks can be handled by separate functions to reduce the cognitive complexity of it.
- Reduce the if-else ladder in delete_signup_common to improve the readability of the code and solve the SRP issue pointed out in Program 3 review. Reuse the log_message() function created in part-3 to log application messages and enure DRY principle is followed
- Rename methods and variables - delete_signup(delete_signup_as_student), load_add_signup_topics(function does not add anything as such hence shall be renamed)
- Reduce the if-else ladder in switch_original_topic_to_approved_suggested_topic method into separate functions to improve readability, testability and understandability of the code.
- Implement Builder method for SignUpTopic and TopicDueDate objects to reduce multiple assignment operations
- Fix code climate issues
- Update test specs to ensure the test build passes.
Design Objectives
In refactoring the sign_up_sheet_controller.rb, we focused on improving the overall design of the codebase by adhering to several fundamental design principles.
- Single Responsibility Principle (SRP):
Responsibility for Object creation for SignUpTopic and TopicDueDate can be delegated to Builder class. We will be addressing the issue of the delete_signup_common method not adhering to SRP. In order to solve the issue we will be creating a class DeleteSignupAction which will have methods like raise_flash_if_work_submitted, raise_flash_if_deadline_passed and raise_flash_after_topic_dropped. We will have two classes InstructorDeleteSignUpAction and ParticipantDeleteSignupAction extending the above class having respective flash generation code.
- Don't Repeat Yourself (DRY):
Throughout the refactor, we emphasized reducing redundancy and promoting code reuse. For instance, in the signup_as_instructor_action method, we reduced the function length and complexity by extracting common functionality into helper functions. By doing so, we minimized repetition, improved readability, and ensured consistency across the codebase.We can reuse the log_message() created in part 3 to write application logs. Delete_signup_common is also introduced to ensure the DRY principle is maintained in the code.
- Readability and Maintainability:
We prioritized enhancing the readability and maintainability of the codebase by introducing meaningful variable names, refactoring conditional blocks, and adding descriptive comments. Clear and descriptive variable names such as assignment_id and topic_id were used to improve code readability and understanding. Additionally, detailed comments were added to describe each section of the code and explain the purpose of methods and conditional blocks, making it easier for developers to understand the logic and functionality.
Plan of Work
Based on the feedback given in the OSS project we will change some method names, such as 'delete_signup_common' renamed for better clarity , adding comments for longer methods to explain the flow of method. Additionally add the test cases to the existing framework to accommodate the changes made.
Solutions
- The previous version of the project fixed the naming discrepancy issue. We have validated it and found no other changes.
- signup_as_instructor and signup_as_instructor_action methods functionalities breakdown
signup_as_instructor: This function appears to be an empty function that doesn't perform any action. It is not called anywhere in the provided code. Hence, it seems unnecessary and can be removed.
signup_as_instructor_action: This function is used to process the instructor's request to sign up a student for a particular topic for an assignment. It handles the form submission and performs the necessary actions based on the submitted data.
- Refactored delete_signup and delete_signup_as_instructor Commit
By centralizing the shared functionality, delete_signup_common, a new method, lessens redundancy and enhances maintainability. A single function can handle both scenarios thanks to the refactored code, which uses the who_user parameter to identify the type of user doing the delete operation.
The error handling logic has been enhanced to provide more descriptive error messages and to log errors appropriately using the ExpertizaLogger. Logging has been improved to include more detailed information about the errors and actions being performed. Comments have been added to describe each section of the code and explain the purpose of the methods and conditional blocks.
After refactoring
def delete_signup_common(who_user, participant, assignment, drop_topic_deadline, topic_id)
# This method centralizes the shared functionality, reducing redundancy and improving maintainability
#this function is used to delete a previous signup
if !participant.team.submitted_files.empty? || !participant.team.hyperlinks.empty?
# Handle case where participant has already submitted work
if who_user == "instructor"
flash[:error] = 'The student has already submitted their work, so you are not allowed to remove them.'
else
flash[:error] = 'You have already submitted your work, so you are not allowed to drop your topic.'
end
ExpertizaLogger.error LoggerMessage.new(controller_name, session[:user].id, "Dropping topic for already submitted work: #{topic_id}")
elsif !drop_topic_deadline.nil? && (Time.now > drop_topic_deadline.due_at)
# Handle case where drop topic deadline has passed
if who_user == "instructor"
flash[:error] = 'You cannot drop a student after the drop topic deadline!'
ExpertizaLogger.error LoggerMessage.new(controller_name, session[:user].id, 'Drop failed for ended work: ' + params[:topic_id].to_s)
else
flash[:error] = 'You cannot drop your topic after the drop topic deadline!'
ExpertizaLogger.error LoggerMessage.new(controller_name, session[:user].id, 'Dropping topic for ended work: ' + params[:topic_id].to_s)
end
else
# No errors, proceed with dropping topic
if who_user == "instructor"
delete_signup_for_topic(assignment.id, topic_id, participant.user_id)
flash[:success] = 'You have successfully dropped the student from the topic.'
ExpertizaLogger.info LoggerMessage.new(controller_name, session[:user].id, 'Student has been dropped from the topic: ' + topic_id.to_s)
else
delete_signup_for_topic(assignment.id, topic_id, session[:user].id)
flash[:success] = 'You have successfully dropped your topic.'
ExpertizaLogger.info LoggerMessage.new(controller_name, session[:user].id, 'Student has dropped the topic: ' + topic_id.to_s)
end
end
end
def delete_signup
who_user = "student"
participant = AssignmentParticipant.find(params[:id])
assignment = participant.assignment
drop_topic_deadline = assignment.due_dates.find_by(deadline_type_id: 6)
topic_id = params[:topic_id]
delete_signup_common(who_user, participant, assignment, drop_topic_deadline, topic_id)
redirect_to action: 'list', id: params[:id]
end
def delete_signup_as_instructor
who_user = "instructor"
team = Team.find(params[:id])
assignment = Assignment.find(team.parent_id)
user = TeamsUser.find_by(team_id: team.id).user
participant = AssignmentParticipant.find_by(user_id: user.id, parent_id: assignment.id)
drop_topic_deadline = assignment.due_dates.find_by(deadline_type_id: 6)
topic_id = params[:topic_id]
delete_signup_common(who_user, participant, assignment, drop_topic_deadline, topic_id)
redirect_to controller: 'assignments', action: 'edit', id: assignment.id
end
- Refactoring the Update method Commit
The refactored version is more concise and readable. It uses the Rails 'update_attributes' method to update multiple attributes of the @topic object in a single call. It also sets a success flash message when the topic is updated successfully, which was missing in the original code.
After refactoring
def update @topic = SignUpTopic.find(params[:id]) if @topic update_max_choosers @topic @topic.update_attributes(topic_identifier: params[:topic][:topic_identifier], category: params[:topic][:category], topic_name: params[:topic][:topic_name], micropayment: params[:topic][:micropayment], description: params[:topic][:description],link:params[:topic][:link] ) flash[:success] = 'The topic has been successfully updated.' undo_link("The topic: \"#{@topic.topic_name}\" has been successfully updated. ") else flash[:error] = 'The topic could not be updated.' end # Akshay - correctly changing the redirection url to topics tab in edit assignment view. redirect_to edit_assignment_path(params[:assignment_id]) + '#tabs-2' end
- Refactoring the signup_as_instructor_action method Commit
The refactored version is more concise and readable. We have reduced the function length which was raised in the Code Climate issue. Also added multiple helper function to reduce the if-else ladder complexity. We added a new user_registered_for_assignment, process_signup_as_instructor_request helper functions as well as log_message function to make sure DRY principle is followed while logging messages. The refactored version assigns params[:assignment_id] and params[:topic_id] to assignment_id and topic_id variables, respectively, to avoid repetitive use of these parameters and increase code readability. The refactored version enhances readability by using meaningful variable names (assignment_id and topic_id), the string interpolation ("The student is not registered for the assignment: #{user.id}") is used, providing a more elegant way of embedding user.id within the string. The redirection statement is cleaner, referencing the previously defined assignment_id variable (redirect_to(controller: 'assignments', action: 'edit', id: assignment_id)), reducing repetition and enhancing code clarity.
After refactoring
def signup_as_instructor_action user = User.find_by(name: params[:username]) if user.nil? # validate invalid user flash[:error] = 'That student does not exist!' else assignment_id = params[:assignment_id] topic_id = params[:topic_id] if user_registered_for_assignment?(user, assignment_id) process_signup_as_instructor_request(assignment_id,user,topic_id) else log_message("The student is not registered for the assignment: #{user.id}") end end redirect_to controller: 'assignments', action: 'edit', id: assignment_id end
def user_registered_for_assignment?(user, assignment_id) # to check if user has registered for the assignment or not. if AssignmentParticipant.exists?(user_id: user.id, parent_id: assignment_id) true else flash[:error] = 'The student is not registered for the assignment!' false end end
def log_message(message) # function to log the message ExpertizaLogger.info LoggerMessage.new(controller_name, , message) end
def process_signup_as_instructor_request(assignment_id,user,topic_id) # function to add user for a given assignment and given topic if SignUpSheet.signup_team(assignment_id, user.id, topic_id) flash[:success] = 'You have successfully signed up the student for the topic!' log_message("Instructor signed up student for topic: #{topic_id}") else flash[:error] = 'The student has already signed up for a topic!' log_message('Instructor is signing up a student who already has a topic') end end
- Refactoring the save_topic_deadlines method Part-1 Commit
As part 1 of refactoring save_topic_deadlines we will be reducing the number of lines of code for this function as it exceeds the limit which was raised in code climate. For this we gave create separate create and update methods of creating and saving TopicDueDate object. Additionally instance_variable_get('@assignment_' + deadline_type + '_due_dates')[i - 1] and instance_variable_get('@topic_' + deadline_type + '_due_date') were used multiple number of times. Hence by applying DRY principle we created variables for the same only one time and used the varaible at different locations of the method.
After refactoring def save_topic_deadlines
assignment = Assignment.find(params[:assignment_id]) @assignment_submission_due_dates = assignment.due_dates.select { |due_date| due_date.deadline_type_id == 1 } @assignment_review_due_dates = assignment.due_dates.select { |due_date| due_date.deadline_type_id == 2 } due_dates = params[:due_date] topics = SignUpTopic.where(assignment_id: params[:assignment_id]) review_rounds = assignment.num_review_rounds topics.each_with_index do |topic, index| (1..review_rounds).each do |i| @topic_submission_due_date = due_dates[topics[index].id.to_s + '_submission_' + i.to_s + '_due_date'] @topic_review_due_date = due_dates[topics[index].id.to_s + '_review_' + i.to_s + '_due_date'] @assignment_submission_due_date = DateTime.parse(@assignment_submission_due_dates[i - 1].due_at.to_s).strftime('%Y-%m-%d %H:%M') @assignment_review_due_date = DateTime.parse(@assignment_review_due_dates[i - 1].due_at.to_s).strftime('%Y-%m-%d %H:%M') %w[submission review].each do |deadline_type| deadline_type_id = DeadlineType.find_by_name(deadline_type).id next if instance_variable_get('@topic_' + deadline_type + '_due_date') == instance_variable_get('@assignment_' + deadline_type + '_due_date')
topic_due_date = begin TopicDueDate.where(parent_id: topic.id, deadline_type_id: deadline_type_id, round: i).first rescue StandardError nil end due_date_obj=instance_variable_get('@assignment_' + deadline_type + '_due_dates')[i - 1] #applying DRY principle and removing multiple instance_variable_get calls due_at=instance_variable_get('@topic_' + deadline_type + '_due_date') if topic_due_date.nil? # create a new record create_topic_due_date(i,topic,deadline_type_id,due_date_obj,due_at) else # update an existed record topic_due_date.update_attributes(due_at: due_at,submission_allowed_id: due_date_obj.submission_allowed_id,review_allowed_id: due_date_obj.review_allowed_id, review_of_review_allowed_id: due_date_obj.review_of_review_allowed_id,quiz_allowed_id: due_date_obj.quiz_allowed_id,teammate_review_allowed_id: due_date_obj.teammate_review_allowed_id) end end end end redirect_to_assignment_edit(params[:assignment_id]) end def create_topic_due_date(index,topic,deadline_type_id,due_date_obj,due_at) TopicDueDate.create( due_at: due_at, deadline_type_id: deadline_type_id, parent_id: topic.id, submission_allowed_id: due_date_obj.submission_allowed_id, review_allowed_id: due_date_obj.review_allowed_id, review_of_review_allowed_id: due_date_obj.review_of_review_allowed_id, round: index, flag: due_date_obj.flag, threshold: due_date_obj.threshold, delayed_job_id: due_date_obj.delayed_job_id, deadline_name: due_date_obj.deadline_name, description_url: due_date_obj.description_url, quiz_allowed_id: due_date_obj.quiz_allowed_id, teammate_review_allowed_id: due_date_obj.teammate_review_allowed_id, type: 'TopicDueDate' ) end
Test Cases
- When dropping topic if submission already done.
- When deadline from dropping topic has passed.
- Deleting topic when topic cannot be found.
- Signup in case when user cannot be found.
- Create signup_sheet when sign_up_topic cannot be found.
- Destroy signup_sheet when other topics with the same assignment exists.
Manual Testing
Follow these instructions to manually test the below functionalities:
- The instructor assigns a student to a topic:
- Log in as an instructor using the credentials: username - instructor6, password - password.
- Select Manage -> Assignments, go to Etc, there add the student to participant.
- Then select a topic which you want to assign to that student.
- Set/updates deadlines to that topic
- To set/update deadlines, select Manage -> Assignments
- Enable the staggered deadline checkbox
- Go to Topics, then select start show/ due date
- There you can assign the deadline, and click on save
- Drop/Delete student from the topic
- Now go to Manage-> Assignments ->Topics
- We can see the assigned users to the topic
- Click on the ‘drop student’, then that student would be dropped from the topic.
Rspec
The RSpec test cases for 'signup_sheet_controller.rb' are intended to fully examine the functionality of handling sign-up topics for assignments. These test cases cover a variety of scenarios, such as adding new topics, modifying topic properties, deleting topics, switching from original to suggested subjects, and more. They ensure that the controller correctly manages activities taken by students and teachers, including meeting deadlines and maintaining submission status. Furthermore, the test cases confirm that activities such as topic priority setting and topic deadline saving function properly.
All of these tests are designed to ensure that the controller's actions respond effectively to user input and that the underlying logic, database interactions, and authorization checks work as expected. With these tests, developers can be certain that the 'SignUpSheetController' is functional and provides a solid basis for managing sign-up topics in an educational setting.
Modified Files
- sign_up_sheet_controller.rb
- sign_up_sheet_helper.rb
- sign_up_sheet_controller_spec.rb
Relevant Links
- Github Repository: https://github.com/Harshil47/expertiza
- Pull Request: https://github.com/expertiza/expertiza/pull/2777
- Video Link: https://www.youtube.com/watch?v=v7D6gEbuq5g
Team
Mentor: Anvitha Reddy Gutha - agutha@ncsu.edu
- Mitul Patel - mpatel27@ncsu.edu
- Harshil Sanghavi - hsangha@ncsu.edu
- Sagar Dama - sudama2@ncsu.edu