CSC/ECE 517 Fall 2017/E1771 Refactor team.rb: Difference between revisions
| No edit summary | No edit summary | ||
| Line 1: | Line 1: | ||
| == Introduction == | == Introduction == | ||
| Expertiza is an open source webapp built on Ruby on Rails stack. It provides a platform to students with various features like peer-reviewing projects, submitting work, form teams, viewing grades etc. The project is being built and maintained by students and faculty at NCSU. | Expertiza is an open source webapp built on Ruby on Rails stack. It provides a platform to students with various features like peer-reviewing projects, submitting work, form teams, viewing grades etc. The project is being built and maintained by students and faculty at NCSU. | ||
| == Mentor and Members == | == Mentor and Members == | ||
| '''Mentor: Zhewei Hu (zhu6@ncsu.edu)''' | '''Mentor: Zhewei Hu (zhu6@ncsu.edu)''' | ||
| Line 13: | Line 9: | ||
| 3. Shikhar Sharma (ssharm29@ncsu.edu) | 3. Shikhar Sharma (ssharm29@ncsu.edu) | ||
| == About team.rb == | == About team.rb == | ||
| team.rb is the default class to interact with the team table. team.rb has 2 subclasses assignment_team.rb and course_team.rb. | team.rb is the default class to interact with the team table. team.rb has 2 subclasses assignment_team.rb and course_team.rb. | ||
| == Issue == | == Issue == | ||
| team.rb is a fairly complex file. It contains a lot of methods that are long and hard to understand.   | team.rb is a fairly complex file. It contains a lot of methods that are long and hard to understand.   | ||
| These methods need to be broken down into simpler and more specific methods that are easier to read/understand. Also, the few instances of code duplication that exist should be removed | These methods need to be broken down into simpler and more specific methods that are easier to read/understand. Also, the few instances of code duplication that exist should be removed | ||
| == Tasks accomplished == | == Tasks accomplished == | ||
|      · Refactor self.randomize_all_by_parent method |      · Refactor self.randomize_all_by_parent method | ||
|          · Split into several simpler methods and assign reasonable names |          · Split into several simpler methods and assign reasonable names | ||
| Line 43: | Line 29: | ||
|          · L166, L181   |          · L166, L181   | ||
|      · Complete the pending tests in team_spec.rb, and write integration tests for newly-created methods. Refactor corresponding methods, and only then finish pending tests. |      · Complete the pending tests in team_spec.rb, and write integration tests for newly-created methods. Refactor corresponding methods, and only then finish pending tests. | ||
| Elaborating the edits made:   | Elaborating the edits made:   | ||
| == Refactor self.randomize_all_by_parent method == | == Refactor self.randomize_all_by_parent method == | ||
| Previous code: | Previous code: | ||
|    def self.randomize_all_by_parent(parent, team_type, min_team_size) |    def self.randomize_all_by_parent(parent, team_type, min_team_size) | ||
|      participants = Participant.where(parent_id: parent.id, type: parent.class.to_s + "Participant") |      participants = Participant.where(parent_id: parent.id, type: parent.class.to_s + "Participant") | ||
| Line 101: | Line 82: | ||
|      end |      end | ||
|    end |    end | ||
| This method has been edited and divided into following methods: | This method has been edited and divided into following methods: | ||
| == sort_teams_by_members_reverse() == | == sort_teams_by_members_reverse() == | ||
|   def self.sort_teams_by_members_reverse(teams) |   def self.sort_teams_by_members_reverse(teams) | ||
|       teams.sort_by { |team| Team.size(team.id) }.reverse! |       teams.sort_by { |team| Team.size(team.id) }.reverse! | ||
|    end |    end | ||
| == assign_single_users_to_teams == | == assign_single_users_to_teams == | ||
|   def self.assign_single_users_to_teams(min_team_size, parent, teams, users) |   def self.assign_single_users_to_teams(min_team_size, parent, teams, users) | ||
|      teams.each do |team| |      teams.each do |team| | ||
| Line 126: | Line 100: | ||
|      end |      end | ||
|    end |    end | ||
| == create_team_from_single_users == | == create_team_from_single_users == | ||
|   def self.create_team_from_single_users(min_team_size, parent, team_type, users) |   def self.create_team_from_single_users(min_team_size, parent, team_type, users) | ||
|      num_of_teams = users.length.fdiv(min_team_size).ceil |      num_of_teams = users.length.fdiv(min_team_size).ceil | ||
| Line 144: | Line 115: | ||
|      end |      end | ||
|    end |    end | ||
| == Rename method and change all other places it is used == | |||
| Renamed the methods '''has_user to user?''', '''get_node_type → node_type''' and '''get_author_names → author_names''' and also updated the function calls so that they call the correct (newly renamed) methods | |||
| == Pass &:destroy as an argument to each instead of a block == | |||
|  TeamsUser.where(team_id: self.id).each{ |teams_user| teams_user.destroy } | |||
| was refactored to | |||
|  TeamsUser.where(team_id: self.id).each(&:destroy) | |||
| == Use find_by instead of dynamic method == | |||
|  parent = TeamNode.find_by_node_object_id(self.id) | |||
|  return team_name unless Team.find_by_name(team_name) | |||
|  user = User.find_by_name(row[index].to_s.strip) | |||
| were changed to | |||
|  parent = TeamNode.find_by(node_object_id: self.id) | |||
|  return team_name unless Team.find_by(name: team_name) | |||
|  user = User.find_by(name: row[index].to_s.strip) | |||
| == Use find_by instead of where.first == | |||
|  participant = AssignmentParticipant.where(user_id: user.id, parent_id: self.parent_id).first | |||
|  if AssignmentParticipant.where(parent_id: assignment_id, user_id: user.id).first.nil? | |||
|  if CourseParticipant.where(parent_id: course_id, user_id: user.id).first.nil? | |||
| were updated to | |||
|  participant = AssignmentParticipant.find_by(user_id: user.id, parent_id: self.parent_id) | |||
|  if AssignmentParticipant.find_by(parent_id: assignment_id, user_id: user.id).nil? | |||
|  if CourseParticipant.find_by(parent_id: course_id, user_id: user.id).nil? | |||
| == Test Cases == | |||
| We have completed all the pending tests in the file team_spec.rb and all the test cases are passing. This can be checked in the pull request which is linked below. | |||
| == Pull Request == | == Pull Request == | ||
| Github Link to Pull Request: [https://github.com/expertiza/expertiza/pull/1043] | Github Link to Pull Request: [https://github.com/expertiza/expertiza/pull/1043] | ||
Latest revision as of 22:02, 5 November 2017
Introduction
Expertiza is an open source webapp built on Ruby on Rails stack. It provides a platform to students with various features like peer-reviewing projects, submitting work, form teams, viewing grades etc. The project is being built and maintained by students and faculty at NCSU.
Mentor and Members
Mentor: Zhewei Hu (zhu6@ncsu.edu)
1. Darshil Patel (dpatel14@ncsu.edu)
2. Srujan Barai (sjbarai@ncsu.edu)
3. Shikhar Sharma (ssharm29@ncsu.edu)
About team.rb
team.rb is the default class to interact with the team table. team.rb has 2 subclasses assignment_team.rb and course_team.rb.
Issue
team.rb is a fairly complex file. It contains a lot of methods that are long and hard to understand. These methods need to be broken down into simpler and more specific methods that are easier to read/understand. Also, the few instances of code duplication that exist should be removed
Tasks accomplished
   · Refactor self.randomize_all_by_parent method
       · Split into several simpler methods and assign reasonable names
       · Extract duplicated code into separate methods
   · Rename method and change all other places it is used
       · has_user →  user?
       · get_node_type → node_type
       · get_author_names → author_names
   · Pass &:destroy as an argument to each instead of a block
       · L22
   · Use find_by instead of dynamic method
       · L63, L153, L162
   · Use find_by instead of where.first
       · L166, L181 
   · Complete the pending tests in team_spec.rb, and write integration tests for newly-created methods. Refactor corresponding methods, and only then finish pending tests.
Elaborating the edits made:
Refactor self.randomize_all_by_parent method
Previous code:
 def self.randomize_all_by_parent(parent, team_type, min_team_size)
   participants = Participant.where(parent_id: parent.id, type: parent.class.to_s + "Participant")
   participants = participants.sort { rand(3) - 1 }
   users = participants.map {|p| User.find(p.user_id) }.to_a
   # find teams still need team members and users who are not in any team
   teams = Team.where(parent_id: parent.id, type: parent.class.to_s + "Team").to_a
   teams_num = teams.size
   i = 0
   teams_num.times do
     teams_users = TeamsUser.where(team_id: teams[i].id)
     teams_users.each do |teams_user|
       users.delete(User.find(teams_user.user_id))
     end
     if Team.size(teams.first.id) >= min_team_size
       teams.delete(teams.first)
     else
       i += 1
     end
   end
   # sort teams by decreasing team size
   teams.sort_by {|team| Team.size(team.id) }.reverse!
   # insert users who are not in any team to teams still need team members
   if !users.empty? and !teams.empty?
     teams.each do |team|
       curr_team_size = Team.size(team.id)
       member_num_difference = min_team_size - curr_team_size
       for i in (1..member_num_difference).to_a
         team.add_member(users.first, parent.id)
         users.delete(users.first)
         break if users.empty?
       end
       break if users.empty?
     end
   end
   # If all the existing teams are fill to the min_team_size and we still have more users, create teams for them.
   unless users.empty?
     num_of_teams = users.length.fdiv(min_team_size).ceil
     nextTeamMemberIndex = 0
     for i in (1..num_of_teams).to_a
       team = Object.const_get(team_type + 'Team').create(name: "Team" + i.to_s, parent_id: parent.id)
       TeamNode.create(parent_id: parent.id, node_object_id: team.id)
       min_team_size.times do
         break if nextTeamMemberIndex >= users.length
         user = users[nextTeamMemberIndex]
         team.add_member(user, parent.id)
         nextTeamMemberIndex += 1
       end
     end
   end
 end
This method has been edited and divided into following methods:
sort_teams_by_members_reverse()
def self.sort_teams_by_members_reverse(teams)
    teams.sort_by { |team| Team.size(team.id) }.reverse!
 end
assign_single_users_to_teams
def self.assign_single_users_to_teams(min_team_size, parent, teams, users)
   teams.each do |team|
     curr_team_size = Team.size(team.id)
     member_num_difference = min_team_size - curr_team_size
     for i in (1..member_num_difference).to_a
       team.add_member(users.first, parent.id)
       users.delete(users.first)
       break if users.empty?
     end
     break if users.empty?
   end
 end
create_team_from_single_users
def self.create_team_from_single_users(min_team_size, parent, team_type, users)
   num_of_teams = users.length.fdiv(min_team_size).ceil
   nextTeamMemberIndex = 0
   for i in (1..num_of_teams).to_a
     team = Object.const_get(team_type + 'Team').create(name: "Team" + i.to_s, parent_id: parent.id)
     TeamNode.create(parent_id: parent.id, node_object_id: team.id)
     min_team_size.times do
       break if nextTeamMemberIndex >= users.length
       user = users[nextTeamMemberIndex]
       team.add_member(user, parent.id)
       nextTeamMemberIndex += 1
     end
   end
 end
Rename method and change all other places it is used
Renamed the methods has_user to user?, get_node_type → node_type and get_author_names → author_names and also updated the function calls so that they call the correct (newly renamed) methods
Pass &:destroy as an argument to each instead of a block
TeamsUser.where(team_id: self.id).each{ |teams_user| teams_user.destroy }
was refactored to
TeamsUser.where(team_id: self.id).each(&:destroy)
Use find_by instead of dynamic method
parent = TeamNode.find_by_node_object_id(self.id)
return team_name unless Team.find_by_name(team_name)
user = User.find_by_name(row[index].to_s.strip)
were changed to
parent = TeamNode.find_by(node_object_id: self.id)
return team_name unless Team.find_by(name: team_name)
user = User.find_by(name: row[index].to_s.strip)
Use find_by instead of where.first
participant = AssignmentParticipant.where(user_id: user.id, parent_id: self.parent_id).first
if AssignmentParticipant.where(parent_id: assignment_id, user_id: user.id).first.nil?
if CourseParticipant.where(parent_id: course_id, user_id: user.id).first.nil?
were updated to
participant = AssignmentParticipant.find_by(user_id: user.id, parent_id: self.parent_id)
if AssignmentParticipant.find_by(parent_id: assignment_id, user_id: user.id).nil?
if CourseParticipant.find_by(parent_id: course_id, user_id: user.id).nil?
Test Cases
We have completed all the pending tests in the file team_spec.rb and all the test cases are passing. This can be checked in the pull request which is linked below.
Pull Request
Github Link to Pull Request: [1]