CSC/ECE 517 Spring 2025 - E2531 Refactor participants controller.rb

From Expertiza_Wiki
Jump to navigation Jump to search

About Expertiza

Expertiza is an open-source project used to manage and submit assignments for CSC 517: Object-Oriented Design & Development as well as serving as a way for CSC 517 students to be able to apply their skills with the Ruby on Rails framework when contributing to the project. Using the site, students are able to access assignments that are assigned to them by the teaching staff or request topics that are opened to students at the instructor's discretion. Students are also able to submit multiple file types as deliverables (.pdf, links, etc.) in the event that an assignment requires multiple deliverables.

Expertiza's use in the classroom is also a way to familiarize students with the system as they use it throughout the semester before having to contribute to it themselves. This way, students can more accurately understand and infer why and how the codebase operates when they eventually begin work on it.

Introduction

From the project description: "This project aims to refactor the participants_controller.rb and its helper class, participants_helper.rb, in the Expertiza system. The focus will be on improving code structure by implementing DRY and SOLID principles, enhancing readability with meaningful comments and clear naming conventions, and ensuring robustness through comprehensive RSpec testing."

Requirements

Existing Issues

Design / Proposed Solution (OLD/TEMP)

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 :


participants_helper.rb Methods

Pattern(s)

SOLID Principle(s)

Implementation (TEMP)

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 containing a confirmation message and a 200 OK status.
      • If the final list would be empty, then it will return a JSON response containing validation errors and a 422 Unprocessable Entity status.

Code:

  # Return a list of participants for a given user
  # params - user_id
  # GET /participants/user/:user_id
  def user_index
    user = find_user if params[:user_id].present?
    return if params[:user_id].present? && user.nil?

    participants = filter_user_participants(user)

    if participants.nil?
      render json: participants.errors, status: :unprocessable_entity
    else
      render json: participants, status: :ok
    end
  end

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 containing a confirmation message and a 200 OK status.
      • If the final list would be empty, then it will return a JSON response containing validation errors and a 422 Unprocessable Entity status.

Code:

  # Return a list of participants for a given assignment
  # params - assignment_id
  # GET /participants/assignment/:assignment_id
  def assignment_index
    assignment = find_assignment if params[:assignment_id].present?
    return if params[:assignment_id].present? && assignment.nil?

    participants = filter_assignment_participants(assignment)

    if participants.nil?
      render json: participants.errors, status: :unprocessable_entity
    else
      render json: participants, status: :ok
    end
  end

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.

Code:

  # Assign the specified authorization to the participant and add them to an assignment
  # POST /participants/:authorization
  def add
    user = find_user
    return unless user

    assignment = find_assignment
    return unless assignment

    authorization = validate_authorization
    return unless authorization

    permissions = retrieve_participant_permissions(authorization)

    participant = assignment.add_participant(user)
    participant.authorization = authorization
    participant.can_submit = permissions[:can_submit]
    participant.can_review = permissions[:can_review]
    participant.can_take_quiz = permissions[:can_take_quiz]
    participant.can_mentor = permissions[:can_mentor]

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

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 a 422 Unprocessable Entity status.
  • 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.

Code:

  # Update the specified participant to the specified authorization
  # PATCH /participants/:id/:authorization
  def update_authorization
    participant = find_participant
    return unless participant

    authorization = validate_authorization
    return unless authorization

    permissions = retrieve_participant_permissions(authorization)

    participant.authorization = authorization
    participant.can_submit = permissions[:can_submit]
    participant.can_review = permissions[:can_review]
    participant.can_take_quiz = permissions[:can_take_quiz]
    participant.can_mentor = permissions[:can_mentor]

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

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.

Code:

  # Delete a participant
  # params - id
  # DELETE /participants/:id
  def destroy
    participant = Participant.find_by(id: params[:id])
  
    if participant.nil?
      render json: { error: 'Not Found' }, status: :not_found
    elsif participant.destroy
      successful_deletion_message = if params[:team_id].nil?
                                      "Participant #{params[:id]} in Assignment #{params[:assignment_id]} has been deleted successfully!"
                                    else
                                      "Participant #{params[:id]} in Team #{params[:team_id]} of Assignment #{params[:assignment_id]} has been deleted successfully!"
                                    end
      render json: { message: successful_deletion_message }, status: :ok
    else
      render json: participant.errors, status: :unprocessable_entity
    end
  end

show Method (TEMP)

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.

Code:

  # Return a specified participant
  # params - id
  # GET /participants/:id
  def show
    participant = Participant.find(params[:id])

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

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.
  • authorization (optional): A string indicating the "Role" of the user, that being one of the following: reader, reviewer, submitter, or mentor.
  • can_submit (optional): A boolean indicating whether the participant has submit permissions granted.
  • can_review (optional): A boolean indicating whether the participant has review permissions granted.
  • can_take_quiz (optional): A boolean indicating whether the participant has take_quiz permissions granted.
  • can_mentor (optional): A boolean indicating whether the participant has mentor permissions granted.
  • handle (optional): A string containing the participant's display name.
  • team_id (optional): The ID of the team the participant belongs to.
  • 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.

Code:

  # Filters participants based on the provided user
  # Returns participants ordered by their IDs
  def filter_user_participants(user)
    participants = Participant.all
    participants = participants.where(user_id: user.id) if user
    participants.order(:id)
  end

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.

Code:

  def filter_assignment_participants(assignment)
    participants = Participant.all
    participants = participants.where(assignment_id: assignment.id) if assignment
    participants.order(:id)
  end

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.

Code:

  def find_user
    user_id = params[:user_id]
    user = User.find_by(id: user_id)
    render json: { error: 'User not found' }, status: :not_found unless user
    user
  end

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.

Code:

  def find_assignment
    assignment_id = params[:assignment_id]
    assignment = Assignment.find_by(id: assignment_id)
    render json: { error: 'Assignment not found' }, status: :not_found unless assignment
    assignment
  end

find_participant Method

Description:

Retrieves a 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.

Code:

  def find_participant
    participant_id = params[:id]
    participant = Participant.find_by(id: participant_id)
    render json: { error: 'Participant not found' }, status: :not_found unless participant
    participant
  end

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.

Code:

  def validate_authorization
    valid_authorizations = %w[reader reviewer submitter mentor]
    authorization = params[:authorization]
    authorization = authorization.downcase if authorization.present?

    unless authorization
      render json: { error: 'authorization is required' }, status: :unprocessable_entity
      return
    end

    unless valid_authorizations.include?(authorization)
      render json: { error: 'authorization not valid. Valid authorizations are: Reader, Reviewer, Submitter, Mentor' },
             status: :unprocessable_entity
      return
    end

    authorization
  end
end

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.

Code:

  def retrieve_participant_permissions(authorization)
    default_permissions = {
      can_submit: true,
      can_review: true,
      can_take_quiz: true,
      can_mentor: false
    }

    permissions_map = {
      'reader' => { can_submit: false },
      'reviewer' => { can_submit: false, can_take_quiz: false },
      'submitter' => { can_review: false, can_take_quiz: false },
      'mentor' => { can_mentor: true }
    }

    default_permissions.merge(permissions_map[authorization])
  end
end

API Creation

Testing with rswag

Swagger UI Video Documentation

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 (TEMP)

Next Steps

Key concerns identified include the overlap and redundancy within the current system:

Team

Mentor

  • Aniruddha Rajnekar <aarajnek@ncsu.edu>

Students

  • Akhil Adusumilli <aadusum@ncsu.edu>
  • Vansh Dodiya <vkdodiya@ncsu.edu>
  • Brian Huynh <bhhuynh@ncsu.edu>

Relevant Links

Pull Request (Unimplemented) []
GitHub Repository [1]
GitHub Project Board [2]
participants_controller.rb in Old Expertiza [3]
participants_helper.rb in Old Expertiza [4]