CSC/ECE 517 Spring 2015/oss E1503 RSA
E1503. Refactor Leaderboard model, Leaderboard_helper and LeaderboardController classes
This page provides a description of the Expertiza based OSS project. This project aimed at refactoring the Leaderboard model, LeaderboardController, and Leaderboard_helper classes as per standard coding methodology for Ruby on Rails.
Introduction to Expertiza
Expertiza is a peer review based system used to provide improved learning experience. Project is developed as a combined effort of students and faculty using the Ruby on Rails framework. Expertiza allows the instructor to create and customize assignments, create a list of topics the students can sign up for, have students work on teams and then review each other's assignments at the end. Expertiza supports submission of almost any document type, including the URLs and wiki pages.
Leaderboard is a module that can be used to find top three leaders in any class based on the score they have received on their submissions and reviews.
Project Description
Classes involved:
leaderboard.rb leaderboard_controller.rb
Modules involved:
leaderboard_helper.rb
What they do
These class are responsible for calculating top 3 individuals which is to be displayed as the leaderboard for the class and generate a metric which aggregates peer review scores for all course assignments and then sorts individuals.
What needs to be done
Methods like getParticipantsScore and extractPersonalAchievements needs to be refactored as these single functions have multiple responsibilities. They can be modularized delegating single resposibility to single method. sortHash method is not an explicit leaderboard model and can be moved to helper methods. Some snippets of code are redundant and have no effect on functionality. They can be removed.
Changes Made
Leaderboard model was having public methods containing more than one feature in each method. We refactored such public methods in necessary public and private methods. Since we didn't create any new public method, we used the existing test cases to validate the changes. We removed helper methods from model class to respective helper class and changed all the references in model and controller classes.
Leaderboard Model
Method Name | Changes Made | Reason For Change |
---|---|---|
model/Leaderboard#getParticipantsScore | Refactored original method functionality into getAssignmentUniqueParticipantList, getAssignmentUniqueParticipantTeamList, getAggregatedAssignmentRevieweeList, getRevieweeListScore, and getUserScoreHashForCourse methods |
Many different features were originally written in one method. We refactored this method into many methods to achieve singularity. |
model/Leaderboard#sortHash helpers/leaderboard_helper#sortHash |
Migrated method from Leaderboard model to leaderboard_helper class. | sortHash is a helper method and is not explicit part of model class, hence it was moved to achieve code reusability and remove redundancy. |
model/Leaderboard scoreEntryScore parameter | Parameter name is changed to entryScore | The name was confusing. It was used to represent each entry's score, hence we changed the name to maintain readability of code. |
model/Leaderboard#extractPersonalAchievements | Removed unwanted code snippets | Redundant code snippet was found which has no affect on program. |
model/Leaderboard#extractPersonalAchievements | Refactored method functionality and created private method, getCourseAccomplishmentHash | More than one feature was implemented into one method. we split the functionality and remove code redundancy. |
Leaderboard Controller
Method Name | Changes Made | Reason For Change |
---|---|---|
index | Updated reference to sortHash method | We moved sortHash method from model class to helper class. We updated all the old references for this method in index method on mentioned controller. |
Re-factored Code Cases
Changes made to leaderboard_model.rb
Methods added to leaderboard_model.rb
# This method returns the unique participants for assignment list. def self.getAssignmentUniqueParticipantList(assignmentList) AssignmentParticipant.where(:parent_id => assignmentList.pluck(:id)).uniq end # This method returns the unique participant teams for assignment list. def self.getAssignmentUniqueParticipantTeamList(assignmentList) Team.where("parent_id IN (?) AND type = ?", assignmentList.pluck(:id), 'AssignmentTeam').uniq end
Before Refactoring
# Get all participants of the assignment list participantList = AssignmentParticipant.where(:parent_id => assignmentList.pluck(:id)).uniq # Get all teams participated in the given assignment list. teamList = Team.where("parent_id IN (?) AND type = ?", assignmentList.pluck(:id), 'AssignmentTeam').uniq assignmentMap = getAssignmentMapping(assignmentList, participantList, teamList)
After Refactoring
assignmentMap = getAssignmentMapping(assignmentList, getAssignmentUniqueParticipantList(assignmentList), getAssignmentUniqueParticipantTeamList(assignmentList))
Private Methods added to leaderboard_model.rb
#All the methods below are private methods private #End result is a hash (qType => (course => (user => score))) def Self.getUserScoreHashForCourse(scores,qTypeHash) for scoreEntry in scores revieweeUserIdList = Array.new if(assignmentMap["team"].has_key?(scoreEntry.reviewee_id)) # Reviewee is a team. Actual Reviewee will be users of the team. teamUserIds = TeamsUser.where(:team_id => scoreEntry.reviewee_id).pluck(:user_id) revieweeUserIdList.concat(teamUserIds) courseId = assignmentMap["team"][scoreEntry.reviewee_id].try(:course_id).to_i else # Reviewee is an individual participant. revieweeUserIdList << assignmentMap["participant"][scoreEntry.reviewee_id]["self"].try(:user_id) courseId = assignmentMap["participant"][scoreEntry.reviewee_id]["assignment"].try(:course_id).to_i end questionnaireType = questionnaireResponseTypeHash[scoreEntry.object_type] addScoreToResultantHash(qTypeHash, questionnaireType, courseId, revieweeUserIdList, scoreEntry.score) end qTypeHash end def Self.getRevieweeListScore(revieweeList,questionnaireResponseTypeHash) ScoreCache.where("reviewee_id IN (?) and object_type IN (?)", revieweeList, questionnaireResponseTypeHash.keys) end # Aggregate total reviewee list for an assignment def Self.getAggregatedAssignmentRevieweeList(assignmentList) revieweeList=Array.new revieweeList= getAssignmentUniqueParticipantTeamList(assignmentList).pluck(:id) revieweeList.concat(getAssignmentUniqueParticipantTeamList(assignmentList).pluck(:id)).uniq! end # This method returns the unique participants for assignment list. def self.getAssignmentUniqueParticipantList(assignmentList) AssignmentParticipant.where(:parent_id => assignmentList.pluck(:id)).uniq end # This method returns the unique participant teams for assignment list. def self.getAssignmentUniqueParticipantTeamList(assignmentList) Team.where("parent_id IN (?) AND type = ?", assignmentList.pluck(:id), 'AssignmentTeam').uniq end #All the methods till here are private methods
New method added to leaderboard_helper.rb
# This method does a destructive sort on the computed scores hash so # that it can be mined for personal achievement information def self.sortHash(qTypeHash) result = Hash.new # Deep-copy of Hash result = Marshal.load(Marshal.dump(qTypeHash)) result.each { |qType, courseHash| courseHash.each { |courseId, userScoreHash| userScoreSortArray = userScoreHash.sort { |a, b| b[1][0] <=> a[1][0]} result[qType][courseId] = userScoreSortArray } } result end
Method removed from leaderboard_model.rb
# This method does a destructive sort on the computed scores hash so # that it can be mined for personal achievement information def self.sortHash(qTypeHash) result = Hash.new # Deep-copy of Hash result = Marshal.load(Marshal.dump(qTypeHash)) result.each { |qType, courseHash| courseHash.each { |courseId, userScoreHash| userScoreSortArray = userScoreHash.sort { |a, b| b[1][0] <=> a[1][0]} result[qType][courseId] = userScoreSortArray } } result end
Before Refactoring
def self.addScoreToResultantHash(qTypeHash, questionnaireType, courseId, revieweeUserIdList, scoreEntryScore) userHash[revieweeUserId] = [scoreEntryScore, 1] qTypeHash[questionnaireType][courseId][revieweeUserId] = [scoreEntryScore, 1] currentUserScore[0] = (currentTotalScore + scoreEntryScore) / currentUserScore[1] csSortedHash = Leaderboard.sortHash(csHash)
After Refactoring
def self.addScoreToResultantHash(qTypeHash, questionnaireType, courseId, revieweeUserIdList, entryScore) userHash[revieweeUserId] = [entryScore, 1] qTypeHash[questionnaireType][courseId][revieweeUserId] = [entryScore, 1] currentUserScore[0] = (currentTotalScore + entryScore) / currentUserScore[1] csSortedHash = LeaderboardHelper.sortHash(csHash)
Changes made to leaderboard_helper.rb
New method added to leaderboard_helper.rb
# This method returns course accomplishment hash for a user def self.getCourseAccomplishmentHash(courseIdList,accomplishmentMap,userId,csHash) courseAccomplishmentHash = Hash.new csSortedHash = LeaderboardHelper.sortHash(csHash) for courseId in courseIdList for accomplishment in accomplishmentMap.keys # Get score for current questionnaireType/accomplishment, courseId and userId from csHash score = csHash.fetch(accomplishment, {}).fetch(courseId, {}).fetch(userId, nil) if(score) if courseAccomplishmentHash[courseId].nil? courseAccomplishmentHash[courseId] = Array.new end # Calculate rank of current user rank = 1 + csSortedHash[accomplishment][courseId].index([userId, score]) total = csSortedHash[accomplishment][courseId].length courseAccomplishmentHash[courseId] << {:accomp => accomplishmentMap[accomplishment], :score => score[0], :rankStr => "#{rank} of #{total}" } end end end courseAccomplishmentHash end
Before Refactoring
csSortedHash = LeaderboardHelper.sortHash(csHash) for courseId in courseIdList for accomplishment in accomplishmentMap.keys # Get score for current questionnaireType/accomplishment, courseId and userId from csHash score = csHash.fetch(accomplishment, {}).fetch(courseId, {}).fetch(userId, nil) if(score) if courseAccomplishmentHash[courseId].nil? courseAccomplishmentHash[courseId] = Array.new end # Calculate rank of current user rank = 1 + csSortedHash[accomplishment][courseId].index([userId, score]) total = csSortedHash[accomplishment][courseId].length courseAccomplishmentHash[courseId] << {:accomp => accomplishmentMap[accomplishment], :score => score[0], :rankStr => "#{rank} of #{total}" } end end end courseAccomplishmentHash
After Refactoring
return getCourseAccomplishmentHash(courseIdList,accomplishmentMap,userId,csHash)
Changes made to leaderboard_controller.rb
Before
@csHash = Leaderboard.sortHash(@csHash)
After
@csHash = LeaderboardHelper.sortHash(@csHash)
Steps to verify changes manually
Automated Tests
Unit test
We didn't create any new public method as part of this project, instead we refactored the existing code. Existing test suit was suitable for testing our modifications.