CSC/ECE 517 Fall 2024 - E2481 Reimplement response map.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.
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