CSC/ECE 517 Fall 2024 - E2484. Reimplement participants controller.rb
About Expertiza
Expertiza is an open source project based on Ruby on Rails framework. Expertiza allows the instructor to create new assignments and customize new or existing assignments. It also allows the instructor to create a list of topics the students can sign up for. Students can form teams in Expertiza to work on various projects and assignments. Students can also peer review other students' submissions. Expertiza supports submission across various document types, including the URLs and wiki pages.
Introduction
This project aims to reimplement the participants_controller.rb
and participants_helper.rb
in the new Expertiza system. The participants_controller.rb
manages participants within assignments, and this reimplementation focuses on streamlining its methods, eliminating redundancy, and enhancing code readability and maintainability.
Requirements
participants_controller.rb
Methods: Fully implement each controller method with the same functionality as the current controller. participants_helper.rb
: Integrate relevant helper methods to streamline functionality and remove redundancy. Existing Issues
Reimplement participants_controller.rb
while addressing the following:
Design / Proposed Solution
Controller Diagram
participants_controller.rb
Methods / API Calls
# | Method | Endpoint | Description |
---|---|---|---|
1 | user_index | GET /participants/user/:user_id
|
Return a list of participants for a given user |
2 | assignment_index | GET /participants/assignment/:assignment_id
|
Return a list of participants for a given assignment |
3 | show | GET /participants/:id
|
Return a specified participant |
4 | add | POST /participants/:authorization
|
Assign the specified authorization to the participant and add them to an assignment |
5 | update_authorization | PATCH /participants/:id/:authorization
|
Update the specified participant to the specified authorization |
6 | destroy | DELETE /participants/:id
|
Delete a participant |
7 | participant_params | - | Permitted parameters for creating a Participant object |
The following methods were removed from participants_controller.rb
:
action_allowed?
- No impact on functionality controller_locale
- No impact on functionality inherit
- Since there is no direct relationship between the Participant and Course classes, this logic is redundant and no longer necessary bequeath_all
- Since there is no direct relationship between the Participant and Course classes, this logic is redundant and no longer necessary change_handle
- The User class has a `handle` field already. It would be redundant to have one in Participant as well.
delete
- Handled in the remove_participant method of the AssignmentsController ( assignments_controller.rb
) view_copyright_grants
- Incorrectly implemented in the old implementation get_user_info
- All needed user data can be accessed by leveraging the Participant model's belongs_to relationship to the User model get_signup_topics_for_assignment
- Handled in the index method of the SignUpTopicsController ( sign_up_topics_controller.rb
) participants_helper.rb
Methods
Almost all of the methods in participants_helper.rb
are outdated and do not impact any functionality in participants_controller.rb
.
As a result, these methods will not be carried forward to the new implementation:
upload_users
- Logic for creating a User specifically for the Assignment and Course classes. Since there is no direct relationship between the User and these classes, this logic is redundant and no longer necessary define_attributes
- Same reason as above define_user
- Same reason as above create_new_user
- Same reason as above add_user_to_assignment
- Same reason as above. Furthermore, since the Participant and Assignment classes are directly related, this functionality has been replaced by the add_participant method in AssignmentsController (assignments_controller.rb
) add_user_to_course
- Same reason as above get_config
- Same reason as above store_item
- Same reason as above
The only method being kept is participant_permissions
as it is responsible for handling the assignment of permissions to a Participant.
Pattern(s)
Facade: "A simplified interface that performs many other actions behind the scenes."
The participants_controller.rb uses the Facade Design Pattern to streamline interactions with the Expertiza backend, abstracting away complex logic and data handling tasks into a simplified API layer. A lot of calls to various services, objects, and features are abstracted, providing a unified, cohesive interface. By centralizing these calls, the controller can reduce dependencies on specific backend structures, making it easier for the front end to perform actions with minimal knowledge of what is going on in the backend, enhancing the maintainability and scalability of our codebase. Some such examples would be to call all the participants of a specific assignment, or pulling information about the course from a participant. Things that would otherwise complicate the front end due to the need to hunt through the backend for information that they need can be abstracted away into the facade interface. The current implementation already employs the facade pattern by its nature, but we will improve it by specifically targeting features of the facade design pattern.
SOLID Principle(s)
Open and Closed Principle: "Software entities should be open for extension, but closed for modification."
Currently, the code directly checks the type of curr_object to determine whether it’s an Assignment or Course, which can make it challenging to extend or modify. To address this, we will implement polymorphism by moving participant-related logic into the respective classes (Assignment, Course, etc.). Each class will define its own behavior for handling participants by overriding a common interface or inheriting from a shared base class. For example, A ParticipantHelper file will define methods like add_user_to_course/assignment, store item, participant_permissions, and numerous other non-idiomatic methods that go against the model view controller design pattern. Method operations that implement business logic, such as the methods listed before, should generally not be the responsibility of the controller.
Single Responsibility Principle: "A class should only have one responsibility and should never have more than one reason to change."
The current implementation of the participant controller mostly aligns with the Single Responsibility Principle, but some methods have more than one responsibility, such as combining backend logic with UI feedback. Many methods within the ParticipantsController class have this type of combined logic, for example, list, add, and inherit methods. These methods blend participant management logic with flash message handling. To make our code comply with the Single Responsibility Principle, we can move these flash messages into a helper method. These modifications reduce the controller’s responsibilities, improving our code’s readability and reusability. Lastly, the frontend developers will have predictable API behavior and consistent responses when our code adheres to the Single Responsibility Principle.
Implementation
Implement participants_controller.rb
Methods
Refer to the participants_controller.rb
Methods / API Calls section for a brief overview of its use
Old Method Name | Action | New Method Name |
---|---|---|
action_allowed? | Removed | - |
controller_locale | Removed | - |
list | Renamed and Separated | user_index |
- | - | assignment_index |
add | No Change | add |
update_authorizations | Renamed | update_authorization |
destroy | No Change | destroy |
inherit | Removed | - |
bequeath_all | Removed | - |
change_handle | Removed | - |
delete | Removed | - |
view_copyright_grants | Removed | - |
participant_params | No Change | participant_params |
get_user_info | Removed | - |
get_signup_topics_for_assignment | Removed | - |
- | Added | show |
user_index
Method
Description:
This method retrieves a list of participants associated with a specific user. If a valid user_id is provided in the request, the system searches for the corresponding user and returns the list of participants linked to that user. If no participants are found, or if the user is invalid, an appropriate error response is returned. Parameters:
user_id
: The ID of the user whose participants you want to retrieve. If the user does not exist, the method returns without further action.
Behavior:
- The method validates the presence and existence of the
user_id
.- If the user_id is invalid, the method then stops execution and does not proceed with participant retrieval.
- If a valid user is provided, then it will retrieve all participants and filter in the ones of the provided user.
- The final filtered list of participants is returned as a JSON response.
- If the final list would be empty, then it will return with error 422
assignment_index
Method
Description:
This method retrieves a list of participants associated with a specific assignment. If a valid assignment_id is provided in the request, the system searches for the corresponding assignment and returns the list of participants linked to that assignment. If no participants are found, or if the assignment is invalid, an appropriate error response is returned. Parameters:
assignment_id
: The ID of the assignment whose participants you want to retrieve. If the assignment does not exist, the method returns without further action.
Behavior:
- The method validates the presence and existence of the
assignment_id
.- If the assignment_id is invalid, the method then stops execution and does not proceed with participant retrieval.
- If a valid assignment is provided, then it will retrieve all participants and filter in the ones of the provided assignment.
- The final filtered list of participants is returned as a JSON response.
- If the final list would be empty, then it will return with error 422
add
Method
Description:
Assigns the specified authorization to a participant and adds them to a given assignment. It performs the necessary validations to ensure that the user, assignment, and authorization are valid before proceeding. If all validations pass, it associates the participant with the assignment and applies the relevant permissions based on the authorization.
Parameters:
user_id
(required): The ID of the user to be added as a participant.
assignment_id
(required): The ID of the assignment the user will be added to.
authorization
(required): The authorization ID to define the user's permissions within the assignment.
Behavior:
- Validates and retrieves the user based on the provided
user_id
. If the user is not found, the method terminates. - Validates and retrieves the assignment based on the provided
assignment_id
. If the assignment is not found, the method terminates. - Validates and retrieves the authorization based on the provided
assignment_id
. If the authorization is not found, the method terminates. - Builds a new Participant instance linked to the validated user and assignment, with the appropriate permissions obtained from authorization.
- Attempts to save the Participant:
- If successful: Returns a JSON response containing the created Participant and a 201 Created status.
- If unsuccessful: Returns a JSON response containing validation errors and a 422 Unprocessable Entity status.
update_authorization
Method
Description:
Updates the authorization and associated permissions for an existing participant.
Parameters:
participant_id
(required): The ID of the participant whose authorization is being updated.
authorization
(required): The new authorization ID to assign to the participant.
Behavior:
- Validates the provided authorization using validate_authorization after finding the participant. If the authorization is missing, the method halts and returns 422.
- Upon successful validation of the authorization, the method retrieves the permissions associated with the authorization by calling retrieve_participant_permissions.
- Updates the participant's authorization field with the new authorization. Relevant permissions (can_submit, can_review, can_take_quiz, and can_mentor) are also updated based on the authorization’s values.
- Attempts to save the updated participant
- If the participant is successfully updated and saved to the database, a JSON response with the updated participant details and a 201 Created status is returned.
- If there are validation errors or the update fails, a JSON response with error details and a 422 Unprocessable Entity status is returned.
destroy
Method
Description:
Deletes a specified Participant based on the provided ID.
Parameters:
id
(required): The ID of the Participant to be deleted.
Behavior:
- Retrieves the Participant using the provided
id
. - Attempts to delete the Participant:
- If successful: Returns a JSON response containing a confirmation message and a 200 OK status.
- If unsuccessful: Returns a JSON response containing validation errors and a 422 Unprocessable Entity status.
show
Method
Description:
Retrieves and returns a specific Participant based on the provided ID.
Parameters:
id
(required): The ID of the Participant to be retrieved.
Behavior:
- Fetches the Participant from the database using the provided
id
. - Attempts to fetch the Participant:
- If successful: Returns a JSON response containing a confirmation message and a 200 OK status.
- If unsuccessful: Returns a JSON response containing validation errors and a 422 Unprocessable Entity status.
participant_params
Method
Description:
Defines the permitted parameters for creating or updating a Participant object.
Parameters:
The method expects a Participant object within the request parameters. It permits the following attributes:
user_id
(required): The ID of the user associated with the Participant.assignment_id
(required): The ID of the assignment linked to the Participant.team_id
(optional): The ID of the team the Participant belongs to.can_submit
: A boolean indicating whether the Participant has submit permissions granted.can_review
: A boolean indicating whether the Participant has review permissions granted.can_take_quiz
: A boolean indicating whether the Participant has take_quiz permissions granted.can_mentor
: A boolean indicating whether the Participant has mentor permissions granted.join_team_request_id
(optional): The ID of any pending team join request related to the Participant.permission_granted
(optional): A boolean indicating whether the Participant has specific permissions granted.topic
(optional): The topic assigned to the Participant (could be a string or other data type depending on the application context).current_stage
(optional): The current progress stage of the Participant.stage_deadline
(optional): The deadline for the current stage.
Behavior:
- Ensures that only allowed parameters are passed when creating a Participant object.
filter_user_participants
Method
Description:
Retrieves and filters participants based on the provided user.
Parameters:
user
(required): An instance of the User model. If provided, the method filters participants associated with this user.
Behavior:
- Fetches all participants from the database.
- The method filters participants where the user_id matches the given user's ID.
- The final list of participants is then ordered by their id in ascending order.
filter_assignment_participants
Method
Description:
Retrieves and filters participants based on the provided assignment.
Parameters:
assignment
(required): An instance of the assignment model. If provided, the method filters participants associated with this assignment.
Behavior:
- Fetches all participants from the database.
- The method filters participants where the assignment_id matches the given assignment's ID.
- The final list of participants is then ordered by their id in ascending order.
find_user
Method
Description: Retrieves a User object based on the user_id provided. If the user is not found, it returns an error response and halts further execution.
Parameters:
user_id
(optional): The ID of the user to be retrieved. Extracted from the permitted parameters.
Behavior:
- Attempts to find the user in the database using the provided user_id.
- If the user is not found (nil), it renders a JSON response with an error message and 404 Not found status
- If the user exists, it returns the user object with 200 ok status.
find_assignment
Method
Description:
Retrieves an Assignment object based on the assignment_id provided. If the Assignment is not found, it returns an error response and halts further execution.
Parameters:
assignment_id
(optional): The ID of the assignment to be retrieved. Extracted from the permitted parameters.
Behavior:
- Attempts to find the assignment in the database using the provided assignment_id.
- If the assignment is not found (nil), it renders a JSON response with an error message and 404 Not found status
- If the assignment exists, it returns the assignment object with 200 ok status.
find_participant
Method
Description:
Retrieves an participant object based on the participant_id provided. If the participant is not found, it returns an error response and halts further execution.
Parameters:
participant_id
(optional): The ID of the participant to be retrieved. Extracted from the permitted parameters.
Behavior:
- Attempts to find the participant in the database using the provided participant_id.
- If the participant is not found (nil), it renders a JSON response with an error message and 404 Not found status
- If the participant exists, it returns the participant object with 200 ok status.
validate_authorization
Method
Description:
- Validates the authorization parameter provided in the request. Ensures that the authorization value is one of the accepted roles: reader, reviewer, submitter, or mentor.
Parameters:
authorization
(required): A string representing the participant’s authorization role. This value must be one of the following: reader, reviewer, submitter, or mentor.
Behavior:
- If the authorization parameter is present, it is converted to lowercase to ensure case-insensitive comparison.
- If the authorization parameter is not provided, the method renders a 422 Unprocessable Entity response with an error message indicating that a participant must have an authorization.
- If the authorization value is provided but does not match one of the valid roles (reader, reviewer, submitter, or mentor), the method renders a 422 Unprocessable Entity response with an error message listing the valid options.
- If the authorization value is valid, the method returns the authorization string.
Import from participants_helper.rb
Refer to the participants_helper.rb
Methods section for all design-related inquiries
Old Method Name | Action | New Method Name |
---|---|---|
upload_users | Removed | - |
define_attributes | Removed | - |
define_user | Removed | - |
create_new_user | Removed | - |
add_user_to_assignment | Removed | - |
add_user_to_course | Removed | - |
get_config | Removed | - |
store_item | Removed | - |
participant_permissions | Renamed | retrieve_participant_permissions |
retrieve_participant_permissions
Method
Description:
- Retrieves the permissions associated with a given authorization role. The permissions determine the actions a participant can perform (e.g., submitting, reviewing, taking quizzes, or mentoring). The method merges default permissions with role-specific overrides and returns the final set of permissions for the specified authorization.
Parameters:
authorization
(required): A string representing the participant's authorization role. This value should be one of the following: reader, reviewer, submitter, or mentor.
Behavior:
- The method defines a set of default permissions that are assigned to all participants unless overridden by their specific authorization role. The default permissions allow submitting, reviewing, and taking quizzes, while disallowing mentoring.
- A permissions_map is used to define role-specific overrides for the default permissions:
- reader: Cannot submit, but can review and take quizzes.
- reviewer: Cannot submit or take quizzes, but can review.
- submitter: Cannot review or take quizzes, but can submit.
- mentor: Can mentor, and has the same permissions as a reader for other actions.
- The method merges the default permissions with the overrides for the specified authorization. The final set of permissions is returned.
API Creation
Refer to the participants_controller.rb
Methods / API Calls section for all design-related inquiries
Testing with rswag
Refer to the Testing Plan section
To be expanded upon during implementation
Swagger UI Video Documentation
Swagger UI Testing for the participants_controller.rb [1]
Testing Plan
rswag
We will use rswag to test and document our project’s API endpoints. Rswag integrates RSpec testing with Swagger, allowing us to define test cases in RSpec to validate functionality while automatically generating interactive Swagger documentation. This documentation, accessible through Swagger UI, provides developers and stakeholders with a user-friendly way to explore and interact with our API endpoints.
Tests
Test ID | Test Description |
---|---|
1 | Test 'assignment_index' with valid assignment ID - Should list participants |
2 | Test 'assignment_index' with invalid assignment ID - Should return an 'Assignment not found' error |
3 | Test unauthorized bearer token access to 'assignment_index' endpoint - Should return a 'Not Authorized' message |
4 | Test 'user_index' with valid user ID - Should list participants |
5 | Test 'user_index' with valid user ID but no participants - Should return an empty list |
6 | Test 'user_index' with invalid user ID - Should return a 'User not found' error |
7 | Test unauthorized bearer token access to 'user_index' endpoint - Should return a 'Not Authorized' message |
8 | Test 'show' with valid participant ID - Should return the specified participant details |
9 | Test 'show' with invalid participant ID - Should return participant not found |
10 | Test unauthorized bearer token access to 'show' endpoint - Should return a 'Not Authorized' message |
11 | Test 'add' with valid user, assignment details, and authorization - Should successfully create a new participant |
12 | Test 'add' with duplicate user and assignment details - Should return an error indicating participant already exists |
13 | Test 'add' with invalid user ID - Should return a 'User not found' error |
14 | Test 'add' with invalid assignment ID - Should return an 'Assignment not found' error |
15 | Test 'add' with invalid authorization - Should return an error specifying valid authorizations (Reader, Reviewer, etc.) |
16 | Test unauthorized bearer token access to 'add' endpoint - Should return a 'Not Authorized' message |
17 | Test 'destroy' with valid participant ID - Should delete the specified participant |
18 | Test 'destroy' with invalid participant ID - Should return participant not found message |
19 | Test unauthorized bearer token access to 'destroy' endpoint - Should return a 'Not Authorized' message |
20 | Test 'update_authorization' participant authorization with valid participant ID and authorization - Should update the authorization level successfully |
21 | Test 'update_authorization' participant authorization with invalid participant ID - Should return a 'Participant not found' message |
22 | Test 'update_authorization' participant authorization with invalid participant ID and invalid authorization - Should return a 'Participant not found' message |
23 | Test 'update_authorization' participant authorization with invalid authorization - Should return a validation error for authorization |
24 | Test unauthorized bearer token access to 'update_authorization' endpoint - Should return a 'Not Authorized' message |
Next Steps
Our goal throughout this implementation was to reduce the bloat and refactor the controller to something significantly cleaner. Originally, we had a design that we believed to be fairly clean, cutting out nearly 80% of the functions and almost half of the database columns. However, we were made aware that the new, significantly cut down implementation would not work within the current system, and we feel that we ended up re adding a lot of the bloat that we had just cut out. Roles are connected to the participant, but these participant roles are different than User roles. Permissions are handled by the participant, which while not bad, we believe it could be improved. The links between assignment and course is hardly fleshed out at all, and participant handles a lot of logic that could be better distributed. We were also made aware of an AssignmentParticipant and a CourseParticipant, which we believe to be unnecessarily specific.
Proposal
We propose that the entire hierarchy and structure of the Course, Assignment, and Participant be re-designed from the ground up alongside each other to delegate tasks in a much more reasonable way. One way to do so would be a system that could look something like the following diagram.
The primary change here would be a slight decoupling of participant from assignment and a unification of the AssignmentParticipant and CourseParticipant classes. We cant really see a logical reason for distinction between the two, and this would significantly reduce the amount of code bloat in the system. Instead, Participants will be the avatar through which a User interacts with the Course hierarchy. User would just be an account that accesses a participant for each class they are in.
There would no longer be a need to create a participant object for every single user in every single assignment in a course, significantly reducing the bloat that creating an assignment for an entire class could cause under the current system.
Roles would be assigned to participants, and then assignments would handle the logic behind permissions. This provides the advantage that different assignments can control how different roles can interact with themselves independently of each other. If they could already do that, then this resolves a DRY violation where permission information is logically acted upon in both Participant and Assignment.
Incidentally, if you were to delegate some of User's roles to participant's roles, such as instructor, then instructors could then also become participants in classes without needing to create another account with student level permissions. If you were to adopt this structure even further out to include universities, Users that are admins of one university can be students of another all under one User account.
However, this is likely to have cascading effects on other critical systems as well. Teams may need to be entirely re organized since there are no longer unique participants per user for each assignment, as well as many other implications that we may not have been able to anticipate.
This is not the solution that has to be used, however, we believe that it would be an improvement on the current system.
Team
Mentor
- Jay Patel <jhpatel9@ncsu.edu>
Students
- Pierce Whelan <pwwhelan@ncsu.edu>
- Calvin Jiang <crjiang@ncsu.edu>
- Hechun Zhang <hzhang56@ncsu.edu>
Relevant Links
Pull Request [2]
GitHub Repository [3]
GitHub Project Board [4]
participants_controller.rb
in Old Expertiza [5]
participants_helper.rb
in Old Expertiza [6]