CSC/ECE 517 Fall 2024 - E2481 Reimplement response map.rb

From Expertiza_Wiki
Jump to navigation Jump to search

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.

Response map overview

The response_map is a crucial component in the Expertiza system, it ensures the relationship between reviewers and reviewees in an assignment. In Expertiza, when a user submits a review, it creates an instance of the Response class. Each Response is associated with a specific ResponseMap that identifies the reviewer (reviewer_id), the person being reviewed (reviewee_id), the item under evaluation (reviewed_object_id), and the type of review, such as ReviewResponseMap, MetareviewResponseMap, FeedbackResponseMap, and TeammateReviewResponseMap, among others.

Attributes

1. reviewed_object_id: This integer field identifies the object (assignment, project, etc.) being reviewed. It serves as a foreign key to link to the specific item under review. 
2. reviewer_id: This integer field denotes the user who is acting as the reviewer. It connects to the users table, allowing the system to track who provided the feedback or evaluation. 
3. reviewee_id: This integer identifies the user or entity being reviewed, establishing a relationship with the person or group receiving the feedback. 
4. created_at & updated_at: These timestamp fields automatically track when the response map was created and last updated, ensuring that the system maintains accurate records of modifications.

Current implementation

  • ResponseMap class is responsible for managing the relationships between responses and the participants involved in the review process. It establishes association between multiple models such as Response, Pariticipant and Assignment.
  • Currently, the "assessments_for" method in response_map.rb is being utilized in the response.rb file to retrieve the latest assessment responses associated with a specified team or reviewer. This method fetches all responses related to the given team, filters them based on submission status (specifically for ReviewResponseMap types), and sorts them to return the most recent assessments. Additionally, it sorts the final response list by the full name of the reviewer.
def self.assessments_for(team)
    responses = []
    # stime = Time.now
    if team
      array_sort = []
      sort_to = []
      maps = where(reviewee_id: team.id)
      maps.each do |map|
        next if map.response.empty?

        all_resp = Response.where(map_id: map.map_id).last
        if map.type.eql?('ReviewResponseMap')
          # If its ReviewResponseMap then only consider those response which are submitted.
          array_sort << all_resp if all_resp.is_submitted
        else
          array_sort << all_resp
        end
        # sort all versions in descending order and get the latest one.
        sort_to = array_sort.sort # { |m1, m2| (m1.updated_at and m2.updated_at) ? m2.updated_at <=> m1.updated_at : (m1.version_num ? -1 : 1) }
        responses << sort_to[0] unless sort_to[0].nil?
        array_sort.clear
        sort_to.clear
      end
      responses = responses.sort { |a, b| a.map.reviewer.fullname <=> b.map.reviewer.fullname }
    end
    responses
  end

Drawbacks

  • The implementation of "assessments_for" method violates the Single Responsibility Principle (SRP), as it handles multiple responsibilities, including fetching response maps, filtering responses, sorting them, and returning the final results. This leads to reduced readability and maintainability.
  • The class lacks validations for its associations and attributes, which could lead to potential inconsistencies or errors in the data. Without proper validations, there's a risk of creating ResponseMap records that do not conform to the expected data integrity, such as having invalid or nonexistent reviewer, reviewee, or assignment associations.

Enhancements

  • To enhance clarity and adhere to the SRP, the assessments_for method could be split into several smaller methods. For example, one method could focus on retrieving response maps, another could filter responses based on submission status, and a third could handle sorting. This modular approach would improve the method's maintainability and make the codebase easier to understand and extend in the future.
  • Implementing validations for associations and attributes would ensure data integrity and reduce the likelihood of runtime errors. Overall, while the ResponseMap class provides essential functionality within the response management system, its current structure could be optimized for better readability, maintainability, and reliability.
  • In addition to this restructuring, implementing scopes and delegations would further enhance the class. Scopes could be introduced to encapsulate commonly used queries, such as retrieving all response maps for a specific reviewer or reviewee, making the code cleaner and more expressive. Delegation could be employed to simplify access to frequently used attributes or methods from associated models, reducing the need for repetitive code and enhancing readability.

Newly added methods

  • latest_responses_for_team_by_reviewer: A new method that retrieves the latest responses from a specific reviewer for a given team, allowing for targeted data access based on team and reviewer relationships.
def latest_responses_for_team_by_reviewer(team, reviewer)
   return [] if team.nil? || reviewer.nil?
   fetch_latest_responses(for_team(team.id).by_reviewer(reviewer.id))
end
  • responses_by_reviewer: Added a method to fetch all responses submitted by a specific reviewer, enhancing the retrieval capabilities of the model.
def responses_by_reviewer(reviewer)
   return [] if reviewer.nil?
   fetch_submitted_responses(by_reviewer(reviewer.id))
end
  • fetch_submitted_responses: Added a method that retrieves all submitted responses associated with the given maps by filtering only those marked as submitted
def fetch_submitted_responses(maps)
    maps.with_submitted_responses
          .includes(:response)
          .flat_map { |map| map.response.select(&:is_submitted) }
end  
  • responses_for_assignment: method retrieves all submitted responses for a given assignment. It returns an empty array if the assignment is nil
def responses_for_assignment(assignment)
   return [] if assignment.nil?

   fetch_submitted_responses(for_assignment(assignment.id))
end
  • fetch_and_sort_responses: A private method collects valid responses from the given maps and then sorts these responses by the reviewer's name.
def fetch_and_sort_responses(maps)
    responses = collect_valid_responses(maps)
    sort_responses_by_reviewer_name(responses)
end
  • collect_valid_responses: This method gathers all valid responses from the given maps. For each map, it includes the associated response and reviewer (with the user details). It skips any map that has no responses and processes each response based on its type, returning a compacted array of the results.
def collect_valid_responses(maps)
   maps.includes(:response, reviewer: :user).map do |map|
     next if map.response.empty?

     process_response_by_type(map)
   end.compact
end
  • process_response_by_type: This method retrieves the latest response from `map.response`. If there is no response, it returns `nil`. For maps of type `ReviewResponseMap`, it returns the latest response only if it has been submitted (i.e., `is_submitted` is `true`). For other map types, it simply returns the latest response regardless of its submission status.
def process_response_by_type(map)
 latest_response = map.response.last
   return nil if latest_response.nil?

   if map.type == 'ReviewResponseMap'
      latest_response if latest_response.is_submitted
   else
      latest_response
   end
end     
  • sort_responses_by_reviewer_name: The `sort_responses_by_reviewer_name` method sorts the given `responses` array in alphabetical order based on each response's reviewer's full name. It converts the name to a string to ensure compatibility during sorting.
 def sort_responses_by_reviewer_name(responses)
   responses.sort_by { |response| response.map.reviewer_fullname.to_s }
 end
  • fetch_latest_responses: The method retrieves the latest response for each map in `maps`, includes the associated `response` records to minimize database queries, and filters out any maps without a response. It then returns only the latest responses that have been submitted (i.e., where `is_submitted` is `true`).
def fetch_latest_responses(maps)
    maps.includes(:response)
     .map { |map| map.response.last }
     .compact
     .select(&:is_submitted)
end
  • fetch_submitted_responses: The method retrieves all responses associated with the given `maps` that have been submitted. It does this by first narrowing `maps` down to those with submitted responses, then including the associated `response` records, and finally flattening and selecting only the submitted responses from each `map`.
def fetch_submitted_responses(maps)
   maps.with_submitted_responses
   .includes(:response)
   .flat_map { |map| map.response.select(&:is_submitted) }
end
  • response_assignment and response_count: The `response_assignment ` method returns the assignment associated with the `reviewer` of the response map. The `response_count` method returns the total number of responses associated with the response map by counting the entries in the `response` collection.
  def response_assignment
    reviewer.assignment
  end

  def response_count
    response.count
  end

Code Improvements

  1. Using Scopes for Querying: Reimplemented code uses scopes (for_team, by_reviewer, for_assignment, with_responses, with_submitted_responses) for cleaner and more readable querying
  2. Private Class Methods: Reimplemented code utilizes private class methods for better encapsulation and organization
  3. Comprehensive Fetching Methods: newly implemented code has well-defined methods like fetch_latest_responses and fetch_submitted_responses, providing clarity on data retrieval while maintaining separation of concerns.
  4. Delegation: uses delegation for retrieving fullname and name
delegate :fullname, to: :reviewer, prefix: true, allow_nil: true
delegate :name, to: :assignment, prefix: true, allow_nil: true


Testing

  • Unit Testing: Earlier Version: No unit tests were provided, leaving functionality unverified. Current Version: The use of RSpec framework provides a structured way to test the model's behavior. Each test is organized into meaningful groups (described using describe blocks).
  • Validation Testing: Tests for all validation rules to ensure that the model behaves correctly when provided with valid and invalid data. Specific tests check for presence, uniqueness, and the handling of invalid IDs.
  • Scope Testing: Tests are provided for each scope method to ensure they return the expected results based on different conditions. Each scope is verified against the ResponseMap records, ensuring the correct filtering and retrieval of records.
  • Method Testing: Tests for custom class methods (.assessments_for, .latest_responses_for_team_by_reviewer, .responses_by_reviewer, .responses_for_assignment) to validate their functionality and expected outputs.Each method is checked against various scenarios, including edge cases where inputs might be nil.
  • Association Testing: Tests the behavior of instance methods like #response_assignment and #response_count, ensuring they return the expected values based on the state of the ResponseMap.

References

  1. Expertiza on GitHub
  2. GitHub Project Repository Fork
  3. The live Expertiza website
  4. Rspec Documentation
  5. Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin