CSC/ECE 517 Fall 2014/final E1472 gjfz: Difference between revisions
(113 intermediate revisions by 4 users not shown) | |||
Line 6: | Line 6: | ||
==Introduction== | ==Introduction== | ||
Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). It is used in select courses at NC State and by professors at several other colleges and universities. One of the Expertiza features is to report scores to both students and the instructor. The student can see the feedback from other students, such as the max score, the min score and the average score. | Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). It is used in select courses at NC State and by professors at several other colleges and universities. One of the Expertiza features is to report scores to both students and the instructor. The student can see the feedback from other students, such as the max score, the min score and the average score. | ||
==Set Up Environment== | |||
===User Guide=== | |||
Current System (After Refactoring Grades_controller): | |||
http://152.46.18.189:3000/ | |||
Instructor: user6 Password: test | |||
Student: user1600 and user1601 Password: test | |||
Original System (Before Refactoring Grades_controller): | |||
http://152.1.13.97:3000/ | |||
Instructor: user6 Password: test | |||
Student: user1600 and user1601 Password: test | |||
All our test result based on the following test cases on expertiza, please follow these step to get it. | |||
Instructor: (Searching "Program 2" using "Ctrl + F" will be convinient for you.) | |||
Steps: Login -> Assignments->Program 2 style ->view scores. | |||
Student: | |||
Steps: Login -> Assignments->Program 2 style ->Your scores. | |||
===Set Up Environment Locally=== | |||
====Get Expertiza from Github==== | |||
<pre style="white-space:normal;">git clone https://github.com/maxlpy/expertiza.git</pre> | |||
====Use rvm to install ruby-2.1.0==== | |||
<pre style="white-space:normal;">curl -L https://get.rvm.io | bash -s stable</pre> | |||
<pre style="white-space:normal;">source ~/.rvm/scripts/rvm</pre> | |||
<pre style="white-space:normal;">echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc</pre> | |||
<pre style="white-space:normal;">rvm install 2.1.0</pre> | |||
<pre style="white-space:normal;">rvm use 2.1.0 --default</pre> | |||
<pre style="white-space:normal;">ruby -v</pre> | |||
====Install Bundled Gems==== | |||
Set JAVA_HOME for the rjb gem: | |||
Your path may be different. You can generally find out the path by looking at the symbolic link at /etc/alternatives/java | |||
<pre style="white-space:normal;">ls -la /etc/alternatives/java</pre> | |||
This outputs something like '/usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java'. Only part of this path may need to be set to JAVA_HOME. In this instance, it is '/usr/lib/jvm/java-6-openjdk-amd64'. | |||
<pre style="white-space:normal;">export JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64</pre> | |||
<pre style="white-space:normal;">bundle install</pre> | |||
====Set Up the Database==== | |||
=====Enable and Start the MySql Daemon===== | |||
<pre style="white-space:normal;">sudo service mysqld enable</pre> | |||
<pre style="white-space:normal;">sudo service mysqld start</pre> | |||
=====Set the MySql Root Password===== | |||
<pre style="white-space:normal;">mysqladmin -u root password</pre> | |||
=====Log in to MySql===== | |||
<pre style="white-space:normal;">mysql -uroot -p</pre> | |||
====Create the Databases==== | |||
<pre style="white-space:normal;">rake db:create:all</pre> | |||
====Build the Expertiza Database==== | |||
<pre style="white-space:normal;">rake db:migrate</pre> | |||
====Start Expertiza service==== | |||
<pre style="white-space:normal;">rails server</pre> | |||
== Requirements == | == Requirements == | ||
Line 20: | Line 83: | ||
===What we need to do=== | ===What we need to do=== | ||
*Remove the get_ and set_ accessor names; just use Ruby accessors. | *Remove the get_ and set_ accessor names; just use Ruby accessors. | ||
*Change camel-case | *Change camel-case to underscores to match the Ruby convention. | ||
*In grades/_participant.html.erb, don’t create local variables in the view; rather, create an object that has max, min, and avg fields for each kind of reviews. Try to avoid the need to check review type; instead, just pass the kind of review when referring to a max, min, or avg field and use polymorphism. | *In grades/_participant.html.erb, don’t create local variables in the view; rather, create an object that has max, min, and avg fields for each kind of reviews. Try to avoid the need to check review type; instead, just pass the kind of review when referring to a max, min, or avg field and use polymorphism. | ||
*Also, you shouldn't have to check for the existence topic in the view; if it is null, a null string should just be returned. | *Also, you shouldn't have to check for the existence topic in the view; if it is null, a null string should just be returned. | ||
Line 26: | Line 89: | ||
===Files Involved=== | ===Files Involved=== | ||
''' | |||
{| class="wikitable" style="font-size: 100%;" | |||
|+ style="font-size: 1.25em;" | | |||
|- | |||
!style="background:#eFeFeF;"| Actor | |||
!style="background:#eFeFeF;"| Description | |||
|- | |||
| '''Refactor Getter and Setter Accessor''' | |||
| | |||
*models/participant.rb | *models/participant.rb | ||
*models/rscore.rb | *models/rscore.rb | ||
*models/assignment_participant.rb | |||
*grades_controller.rb | |||
*models/assignment.rb | |||
*models/assignment_participant.rb | |||
*models/assignment_team.rb | |||
*models/course.rb | |||
*models/course_participant.rb | |||
*models/course_team.rb | |||
*controllers/grades_controller.rb | |||
*controllers/scores_controller.rb | |||
*controllers/assignments_controller.rb | |||
*controllers/assignment_controller.rb | |||
*views/grades/_participant.html.erb | |||
*views/grades/_scores_author_feedback.html.erb | |||
*views/grades/_scores_metareview.html.erb | |||
*views/grades/_scores_submitted_work.html.erb | |||
*views/grades/_tabbing.html.erb | |||
*views/grades/edit.html.erb | |||
*views/response/_review.html.erb | |||
* | *views/response/_submitted_files.html.erb | ||
*grades/_scores_author_feedback.html.erb | *views/response/view.html.erb | ||
|- | |||
| '''Reduce Local Variables In Views''' | |||
| | |||
*views/grades/_scores_author_feedback.html.erb | |||
*views/grades/_participant.html.erb | |||
*views/grades/_scores_header.html.erb | |||
*views/grades/_submitted_work.html.erb | |||
*grades/ | |- | ||
| '''Calculations Found in Views''' | |||
| | |||
*views/grades/_participant.html.erb | |||
*views/grades/_scores_author_feedback.html.erb | |||
|} | |||
===What we are going to do=== | ===What we are going to do=== | ||
* | *Modify the setters and getters in the models/participant.rb by refactoring | ||
*Modify the camel-case variables to underscores to match the Ruby convention in views/grades/... , models/participant.rb and controllers/grades_controller.rb | *Modify the camel-case variables to underscores to match the Ruby convention in views/grades/... , models/participant.rb and controllers/grades_controller.rb | ||
*Modify the data structure of the score, instead of using pScore which contains lots of hash tables in it, we set up a new object, '''Rscore''', which only consists of max, min and average fields of each reviews. Also, using | *Modify the data structure of the score, instead of using pScore which contains lots of hash tables in it, we set up a new object, '''Rscore''', which only consists of max, min and average fields of each reviews. Also, using encapsulation to implement this data structure to avoid check review type. | ||
*delete checking for topics in views | |||
* | *Move the calculations in the views to the model so that there won't be many calculations in the views of MVC framework.(Model file:'''assignment_participant.rb''') | ||
== | ===What we have done=== | ||
*We modified the camel-case variables in views/grade to Ruby convention by replacing them with under_score format while keeping the camel-case in JavaScript, as in JavaScript the camel-case should be its convention. | |||
*We set up new data structure Rscore to encapsulate the score and reduce the local variables. | |||
*We analysed the system and found out that polymorphism for Rscore is not necessary, encapsulation will handle all the usages. | |||
*We moved the calculations in the views into model file,'''assignment_participants.rb''', which follows the single responsibility principle and DRY principle. | |||
*After confirmation with Professor and TAs, we no longer need to check the existence of the topic. | |||
*We modified the response_map to expedite the rendering speed of showing scores. | |||
==Details of Implementation== | |||
===Moving calculation from view to model=== | |||
We find that there are some calculations in the views('''_participant.html.erb'''). The main function of these calculations in the _participant.html.erb is to curve the teams' total score. It gets the value of total_score from the instance variable('''@pscore''') and computes them in the views. | |||
The whole project utilizes the MVC design pattern to achieve a clean separation among three components(Model-View-Controller). However, the above calculations violate the MVC design pattern and ruin the program's structure. Meanwhile, it will increase the workload of the views and decrease performance of the website. | |||
In order to implement the MVC design pattern much better and improve the performance of the website, we move these calculations to the model to avoid these problems. In current system, the participant's total score is obtained by calling the '''participant.scores''' method in gradesController, so we move these calculations into scores method which is declared in the model of '''assignment_participant.rb'''. | |||
By testing, our solution solves the above problem effectively. Meanwhile, we adopt single responsibility principle and DRY principle to simplify our code. | |||
[[File: | ===Encapsulation=== | ||
In the '''grade/_participant.html.erb''' file, there are plenty of local variables such as min, max and avg. When we need to return the grades of a student for different type of reviews, we need to create different variables. This not only ruins the readability of the code, but also tends to cause problem for future maintenance and development. Since min, max and avg are the mostly used variables, we created a new data structure '''Rscore''' in this file, which encapsulated all these three variables. Whenever we need the value of min, max and avg, we can get those information directly from '''Rscore'''. | |||
In the project specification, it suggested that we use polymorphism to reduce the redundant local variables. However, we found that this simple data structure will suffice without bothering to use polymorphism, and it makes the code look cleaner. | |||
Furthermore, '''pscore''' is a complicated data structure. It consists of huge hash tables, with each hash table contains another hash table as its value. This situation makes polymorphism pretty hard to implement,if we are not allowed to modify the data structure of the score, which is the basic structure of the system. | |||
==Design Pattern and System Architecture== | |||
[[File:Expertiza003.png|frame|center|]] | |||
===MVC design pattern=== | |||
Responsibility of view_my_scores html.erb is to display all the scores with respect to a participant and an assignment. For achieving optimal functionality, a good amount of refactoring had to be done in the views that were responsible for displaying the results to a user.(By removing all the calculations in the views to models) | |||
Apart from the template methods that are present in other controller class, the grades_controller specifically has two more important methods: view and view_my_scores. | Apart from the template methods that are present in other controller class, the grades_controller specifically has two more important methods: view and view_my_scores. | ||
Line 91: | Line 220: | ||
*'''view_my_scores''': take care of displaying the scores pertaining to an assignment to a single user. | *'''view_my_scores''': take care of displaying the scores pertaining to an assignment to a single user. | ||
Scores.rb has two methods which are responsible to compute scores for the participants based on assignments and courses. | Scores.rb has two methods which are responsible to compute scores for the participants based on assignments and courses. | ||
Line 97: | Line 226: | ||
*Model class PartcipantScores.rb and AssignmentScores.rb have been created to retrieve the scores for participant and assignment respectively. | *Model class PartcipantScores.rb and AssignmentScores.rb have been created to retrieve the scores for participant and assignment respectively. | ||
===DRY Principle=== | |||
This ensures that information is not repeated in the application. We use DRY Principle to design our object and modify all other work.(By setting up Rscore data structure) | |||
===Single Responsibility Principle=== | |||
This ensures that every controller and model method will be responsible in performing exactly one functionality.(By setting up Rscore data structure) | |||
== Use Case== | == Use Case== | ||
Line 156: | Line 291: | ||
[[File:Objected Oriented Design.png|frame|center|]] | [[File:Objected Oriented Design.png|frame|center|]] | ||
The RScore class stores the variables maximum,minimum, average review scores. Different type of reviews score is declared as subclass of RScore using ploymorphism. The type field is specified in their initialize method. | The RScore class stores the variables maximum,minimum, average review scores. Different type of reviews score is declared as subclass of RScore using ploymorphism. The type field is specified in their initialize method. | ||
===Create object to avoid local variables=== | |||
Create rscore class in models (rscore.rb): | |||
<pre> | |||
class Rscore | |||
attr_accessor :my_max,:my_min,:my_avg,:my_type | |||
def initialize(my_score,type) | |||
@my_max=my_score[type][:scores][:max] | |||
@my_min=my_score[type][:scores][:min] | |||
@my_avg=my_score[type][:scores][:avg] | |||
@my_type=type | |||
end | |||
end | |||
</pre> | |||
===Use instance variables in views=== | |||
In grades/_participant.html.erb, we create an object that has max, min, and avg fields for each kind of reviews, and pass the kind of review when referring to a max, min, or avg field. | |||
'''Before:'''(views/grades/_participant.html.erb) | |||
<pre> | |||
if pscore[:review] | |||
s_max = pscore[:review][:scores][:max] | |||
s_min = pscore[:review][:scores][:min] | |||
s_avg = pscore[:review][:scores][:avg] | |||
end | |||
if pscore[:metareview] | |||
r_max = pscore[:metareview][:scores][:max] | |||
r_min = pscore[:metareview][:scores][:min] | |||
r_avg = pscore[:metareview][:scores][:avg] | |||
end | |||
if pscore[:feedback] | |||
f_max = pscore[:feedback][:scores][:max] | |||
f_min = pscore[:feedback][:scores][:min] | |||
f_avg = pscore[:feedback][:scores][:avg] | |||
end | |||
if pscore[:teammate] | |||
tr_max = pscore[:teammate][:scores][:max] | |||
tr_min = pscore[:teammate][:scores][:min] | |||
tr_avg = pscore[:teammate][:scores][:avg] | |||
end | |||
</pre> | |||
'''After:'''(views/grades/_participant.html.erb) | |||
<pre> | |||
if pscore[:review] | |||
@rscore_review=Rscore.new(pscore,:review) | |||
end | |||
if pscore[:metareview] | |||
@rscore_metareview=Rscore.new(pscore,:metareview) | |||
end | |||
if pscore[:feedback] | |||
@rscore_feedback=Rscore.new(pscore,:feedback) | |||
end | |||
if pscore[:teammate] | |||
@rscore_teammate=Rscore.new(pscore,:teammate) | |||
</pre> | |||
==Moving Calculation from view to model== | |||
We move lots of calculations from views to model. | |||
'''Before''':(views/grades/_participant.html.erb) | |||
<pre> | |||
<% if controller.action_name == 'view' or controller.action_name == "view_my_scores" %> | |||
<TD ALIGN="CENTER"> | |||
<% if stage == "Finished" %> | |||
<% if participant.grade | |||
total_score = participant.grade | |||
title = "A score in blue indicates that the value was overwritten by the instructor or teaching assistant." | |||
else | |||
total_score = pscore[:total_score] | |||
title = nil | |||
end %> | |||
<% hardline = 85 | |||
if tr_avg > hardline | |||
total_score = total_score + 0.05*total_score | |||
elsif tr_avg < hardline and (hardline -tr_avg) > 40 | |||
total_score = total_score - 10 | |||
elsif tr_avg < hardline and (hardline -tr_avg) > 20 | |||
total_score = total_score - (hardline -tr_avg)*0.5 | |||
%> | |||
<%end%> | |||
<% if total_score>100 | |||
total_score = 100 | |||
%> | |||
<%end%> | |||
<div <% if title %>title="<%=title%>" style="color:#0033FF"<% end %>><%= sprintf("%.2f",total_score) %><%= score_postfix %></div> | |||
</pre> | |||
'''After''':(model/assignment_participant.rb) | |||
<pre> | |||
# move lots of calculation from view(_participant.html.erb) to model | |||
if self.grade | |||
scores[:total_score] = self.grade | |||
end | |||
else | |||
total_score = scores[:total_score] | |||
hardline = 85 | |||
if scores[:teammate][:scores][:avg].to_f > hardline | |||
total_score = total_score + 0.05*total_score | |||
elsif scores[:teammate][:scores][:avg].to_f < hardline and (hardline - scores[:teammate][:scores][:avg].to_f) > 40 | |||
total_score = total_score - 10 | |||
elsif scores[:teammate][:scores][:avg].to_f < hardline and (hardline - scores[:teammate][:scores][:avg].to_f) > 20 | |||
total_score = total_score - (hardline - scores[:teammate][:scores][:avg].to_f)*0.5 | |||
end | |||
if total_score > 100 | |||
total_score = 100 | |||
end | |||
scores[:total_score] = total_score | |||
scores | |||
</pre> | |||
==Speeding up view score's function== | |||
At the beginning of the final project, the function of viewing scores is very slow by students and instructor. Our team found the real factor which lead to the slow problem and solved it in an easy approach. | |||
===Optimize response searching method in the model=== | |||
*Modify '''get_assessments_for''' method in response_map.rb | |||
After doing this, the time cost of view function decreased by more than 90% | |||
Before Refactoring: | |||
<pre> | |||
# the original method to find all response | |||
@all_resp=Response.all | |||
for element in @all_resp | |||
if (element.map_id == map.map_id) | |||
@array_sort << element | |||
@test << map | |||
end | |||
end | |||
</pre> | |||
After Refactoring: | |||
<pre> | |||
@all_resp=Response.find_by_map_id(map.map_id) | |||
@array_sort << @all_resp | |||
@test << map | |||
</pre> | |||
===Test Result=== | |||
{| class="wikitable" style="font-size: 90%;" | |||
|+ style="font-size: 1.25em;" | | |||
|- | |||
!style="background:#eFeFeF;"| Name | |||
!style="background:#eFeFeF;"| Before Refactoring | |||
!style="background:#eFeFeF;"| After Refactoring | |||
!style="background:#eFeFeF;"| Reduced By | |||
|- | |||
| View all team's score(instructor) | |||
| 484988ms | |||
| 8642ms | |||
| 98.21% | |||
|- | |||
| View own score(student) | |||
| 8941ms | |||
| 651ms | |||
| 92.71% | |||
|} | |||
*Original Time for Instructor to View all scores | |||
[[File:Instructor_old.png|frame|center|Original Time for Instructor to View all scores]] | |||
*Time for Instructor to View all scores after Refactoring | |||
[[File:Instructor_new.png|frame|center|Time for Instructor to View all scores after Refactoring]] | |||
*Original Time for Student to View all scores | |||
[[File:Student_old.png|frame|center|Original Time for Student to View all scores]] | |||
*Time for Student to View all scores after Refactoring | |||
[[File:Student_new.png|frame|center|Time for Student to View all scores after Refactoring]] | |||
==Snapshots== | ==Snapshots== | ||
Line 166: | Line 472: | ||
== See Also == | == See Also == | ||
[https://docs.google.com/document/d/1ncwlrTEZ1taP6ZvMc_GqP4jcAchE03zXSnZwvzgfeDw/edit?usp=sharing ''Requirement for E147''] | |||
[http://wiki.expertiza.ncsu.edu/index.php/CSC/ECE_517_Fall_2013/oss_E804_spb ''Wiki page for E804''] | [http://wiki.expertiza.ncsu.edu/index.php/CSC/ECE_517_Fall_2013/oss_E804_spb ''Wiki page for E804''] | ||
Line 172: | Line 480: | ||
[https://github.com/ajain2709/expertiza ''Github link for E912''] | [https://github.com/ajain2709/expertiza ''Github link for E912''] | ||
[http://youtu.be/RPfzaGtJtQc ''Youtube Demo''] | |||
==Future Work== | ==Future Work== | ||
We will stick to the UI design of the grades view and see what else we can do to further improve the performance of the system. We are trying to figure out other reasons that may lead to the bad performance in addition to the two main reasons mentioned and solved in E804 and E805 projects. | We will stick to the UI design of the grades view and see what else we can do to further improve the performance of the system. We are trying to figure out other reasons that may lead to the bad performance in addition to the two main reasons mentioned and solved in E804 and E805 projects. |
Latest revision as of 17:27, 5 December 2014
Design Document
E1472: Connect changes to score model with changes to score views
Introduction
Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). It is used in select courses at NC State and by professors at several other colleges and universities. One of the Expertiza features is to report scores to both students and the instructor. The student can see the feedback from other students, such as the max score, the min score and the average score.
Set Up Environment
User Guide
Current System (After Refactoring Grades_controller):
Instructor: user6 Password: test
Student: user1600 and user1601 Password: test
Original System (Before Refactoring Grades_controller):
Instructor: user6 Password: test
Student: user1600 and user1601 Password: test
All our test result based on the following test cases on expertiza, please follow these step to get it.
Instructor: (Searching "Program 2" using "Ctrl + F" will be convinient for you.)
Steps: Login -> Assignments->Program 2 style ->view scores.
Student:
Steps: Login -> Assignments->Program 2 style ->Your scores.
Set Up Environment Locally
Get Expertiza from Github
git clone https://github.com/maxlpy/expertiza.git
Use rvm to install ruby-2.1.0
curl -L https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
rvm install 2.1.0
rvm use 2.1.0 --default
ruby -v
Install Bundled Gems
Set JAVA_HOME for the rjb gem: Your path may be different. You can generally find out the path by looking at the symbolic link at /etc/alternatives/java
ls -la /etc/alternatives/java
This outputs something like '/usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java'. Only part of this path may need to be set to JAVA_HOME. In this instance, it is '/usr/lib/jvm/java-6-openjdk-amd64'.
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64
bundle install
Set Up the Database
Enable and Start the MySql Daemon
sudo service mysqld enable
sudo service mysqld start
Set the MySql Root Password
mysqladmin -u root password
Log in to MySql
mysql -uroot -p
Create the Databases
rake db:create:all
Build the Expertiza Database
rake db:migrate
Start Expertiza service
rails server
Requirements
A way to query db models to return scores, without UI changes
These methods report grades to students and instructors. The view method reports everyone’s grades to an instructor, and the view_my_scores method reports peer-review scores to a student. This code is very slow, due to many factors. Two of the most prominent are the fact that separate db queries are used for each rubric that has been filled out by anyone associated with the assignment; these queries are made sequentially while the HTML page is being written; and the fact that HTML for the whole page is generated, largely by controller methods, before anything is displayed.
UI changes for reporting scores
The grades_controller class is responsible for displaying grades to students (via view_my_scores) and instructors or Teaching Assistant (via the view method). We will modify the Score class, and reduce the number of variables in order to remove code duplication and improve the loading speed.
What we need to do
- Remove the get_ and set_ accessor names; just use Ruby accessors.
- Change camel-case to underscores to match the Ruby convention.
- In grades/_participant.html.erb, don’t create local variables in the view; rather, create an object that has max, min, and avg fields for each kind of reviews. Try to avoid the need to check review type; instead, just pass the kind of review when referring to a max, min, or avg field and use polymorphism.
- Also, you shouldn't have to check for the existence topic in the view; if it is null, a null string should just be returned.
- There are lots of calculations in the views, which should be moved to the model.
Files Involved
Actor | Description |
---|---|
Refactor Getter and Setter Accessor |
|
Reduce Local Variables In Views |
|
Calculations Found in Views |
|
What we are going to do
- Modify the setters and getters in the models/participant.rb by refactoring
- Modify the camel-case variables to underscores to match the Ruby convention in views/grades/... , models/participant.rb and controllers/grades_controller.rb
- Modify the data structure of the score, instead of using pScore which contains lots of hash tables in it, we set up a new object, Rscore, which only consists of max, min and average fields of each reviews. Also, using encapsulation to implement this data structure to avoid check review type.
- delete checking for topics in views
- Move the calculations in the views to the model so that there won't be many calculations in the views of MVC framework.(Model file:assignment_participant.rb)
What we have done
- We modified the camel-case variables in views/grade to Ruby convention by replacing them with under_score format while keeping the camel-case in JavaScript, as in JavaScript the camel-case should be its convention.
- We set up new data structure Rscore to encapsulate the score and reduce the local variables.
- We analysed the system and found out that polymorphism for Rscore is not necessary, encapsulation will handle all the usages.
- We moved the calculations in the views into model file,assignment_participants.rb, which follows the single responsibility principle and DRY principle.
- After confirmation with Professor and TAs, we no longer need to check the existence of the topic.
- We modified the response_map to expedite the rendering speed of showing scores.
Details of Implementation
Moving calculation from view to model
We find that there are some calculations in the views(_participant.html.erb). The main function of these calculations in the _participant.html.erb is to curve the teams' total score. It gets the value of total_score from the instance variable(@pscore) and computes them in the views.
The whole project utilizes the MVC design pattern to achieve a clean separation among three components(Model-View-Controller). However, the above calculations violate the MVC design pattern and ruin the program's structure. Meanwhile, it will increase the workload of the views and decrease performance of the website.
In order to implement the MVC design pattern much better and improve the performance of the website, we move these calculations to the model to avoid these problems. In current system, the participant's total score is obtained by calling the participant.scores method in gradesController, so we move these calculations into scores method which is declared in the model of assignment_participant.rb.
By testing, our solution solves the above problem effectively. Meanwhile, we adopt single responsibility principle and DRY principle to simplify our code.
Encapsulation
In the grade/_participant.html.erb file, there are plenty of local variables such as min, max and avg. When we need to return the grades of a student for different type of reviews, we need to create different variables. This not only ruins the readability of the code, but also tends to cause problem for future maintenance and development. Since min, max and avg are the mostly used variables, we created a new data structure Rscore in this file, which encapsulated all these three variables. Whenever we need the value of min, max and avg, we can get those information directly from Rscore.
In the project specification, it suggested that we use polymorphism to reduce the redundant local variables. However, we found that this simple data structure will suffice without bothering to use polymorphism, and it makes the code look cleaner.
Furthermore, pscore is a complicated data structure. It consists of huge hash tables, with each hash table contains another hash table as its value. This situation makes polymorphism pretty hard to implement,if we are not allowed to modify the data structure of the score, which is the basic structure of the system.
Design Pattern and System Architecture
MVC design pattern
Responsibility of view_my_scores html.erb is to display all the scores with respect to a participant and an assignment. For achieving optimal functionality, a good amount of refactoring had to be done in the views that were responsible for displaying the results to a user.(By removing all the calculations in the views to models)
Apart from the template methods that are present in other controller class, the grades_controller specifically has two more important methods: view and view_my_scores.
- view method: handle the functionality of viewing the assignments of the whole class. Understandably, this is done through an admin/instructor's profile as only they have the privilege of viewing all participants' scores.
- view_my_scores: take care of displaying the scores pertaining to an assignment to a single user.
Scores.rb has two methods which are responsible to compute scores for the participants based on assignments and courses.
- get_total_scores() method: make multiple sequential queries to the database to calculate scores.
- Model class PartcipantScores.rb and AssignmentScores.rb have been created to retrieve the scores for participant and assignment respectively.
DRY Principle
This ensures that information is not repeated in the application. We use DRY Principle to design our object and modify all other work.(By setting up Rscore data structure)
Single Responsibility Principle
This ensures that every controller and model method will be responsible in performing exactly one functionality.(By setting up Rscore data structure)
Use Case
The figure above mainly illustrates the process of viewing scores for different actors. The instructor and teaching assistant are able to view scores of all the team and could see the reviews of a selected team. Student actor can see his own scores and reviews.
Actor | Description |
---|---|
Instructor | This actor is responsible for viewing scores and reviews of all the students(teams). |
Admin | This actor has the same responsibility as Instructor along with the ability to create instructor(however, this is not our concern here). |
Teaching Assistant | This actor is responsible for viewing scores and reviews of all the students(teams). |
Student | This actor is responsible for viewing scores and reviews of his own or of his team. |
The following table is showing the use case of different users of the expertiza and how they interact with it. For example, when a student want to view his/her grades, there must be something in the database (precondition). He will need to login, and choose one of the assignments which he want to view grade on. He can also see the review of his work.
Name | Actor | Other Participants | Precondition | Primary Sequence |
---|---|---|---|---|
Student views his team score | Student | None | At least team's score exists. |
|
TA views teams' review | TA | None | At least team's review exists. |
|
Instructor/ Teaching Assistant/ Admin views the list of teams scores | Instructor/ Teaching Assistant/ Admin | None | At least one team's score exists. |
|
Objected Oriented Design
The following class diagram shows a combination of new and existing classes along with the attributes and operations that are relevant to the implementation of this solution.
The RScore class stores the variables maximum,minimum, average review scores. Different type of reviews score is declared as subclass of RScore using ploymorphism. The type field is specified in their initialize method.
Create object to avoid local variables
Create rscore class in models (rscore.rb):
class Rscore attr_accessor :my_max,:my_min,:my_avg,:my_type def initialize(my_score,type) @my_max=my_score[type][:scores][:max] @my_min=my_score[type][:scores][:min] @my_avg=my_score[type][:scores][:avg] @my_type=type end end
Use instance variables in views
In grades/_participant.html.erb, we create an object that has max, min, and avg fields for each kind of reviews, and pass the kind of review when referring to a max, min, or avg field.
Before:(views/grades/_participant.html.erb)
if pscore[:review] s_max = pscore[:review][:scores][:max] s_min = pscore[:review][:scores][:min] s_avg = pscore[:review][:scores][:avg] end if pscore[:metareview] r_max = pscore[:metareview][:scores][:max] r_min = pscore[:metareview][:scores][:min] r_avg = pscore[:metareview][:scores][:avg] end if pscore[:feedback] f_max = pscore[:feedback][:scores][:max] f_min = pscore[:feedback][:scores][:min] f_avg = pscore[:feedback][:scores][:avg] end if pscore[:teammate] tr_max = pscore[:teammate][:scores][:max] tr_min = pscore[:teammate][:scores][:min] tr_avg = pscore[:teammate][:scores][:avg] end
After:(views/grades/_participant.html.erb)
if pscore[:review] @rscore_review=Rscore.new(pscore,:review) end if pscore[:metareview] @rscore_metareview=Rscore.new(pscore,:metareview) end if pscore[:feedback] @rscore_feedback=Rscore.new(pscore,:feedback) end if pscore[:teammate] @rscore_teammate=Rscore.new(pscore,:teammate)
Moving Calculation from view to model
We move lots of calculations from views to model.
Before:(views/grades/_participant.html.erb)
<% if controller.action_name == 'view' or controller.action_name == "view_my_scores" %> <TD ALIGN="CENTER"> <% if stage == "Finished" %> <% if participant.grade total_score = participant.grade title = "A score in blue indicates that the value was overwritten by the instructor or teaching assistant." else total_score = pscore[:total_score] title = nil end %> <% hardline = 85 if tr_avg > hardline total_score = total_score + 0.05*total_score elsif tr_avg < hardline and (hardline -tr_avg) > 40 total_score = total_score - 10 elsif tr_avg < hardline and (hardline -tr_avg) > 20 total_score = total_score - (hardline -tr_avg)*0.5 %> <%end%> <% if total_score>100 total_score = 100 %> <%end%> <div <% if title %>title="<%=title%>" style="color:#0033FF"<% end %>><%= sprintf("%.2f",total_score) %><%= score_postfix %></div>
After:(model/assignment_participant.rb)
# move lots of calculation from view(_participant.html.erb) to model if self.grade scores[:total_score] = self.grade end else total_score = scores[:total_score] hardline = 85 if scores[:teammate][:scores][:avg].to_f > hardline total_score = total_score + 0.05*total_score elsif scores[:teammate][:scores][:avg].to_f < hardline and (hardline - scores[:teammate][:scores][:avg].to_f) > 40 total_score = total_score - 10 elsif scores[:teammate][:scores][:avg].to_f < hardline and (hardline - scores[:teammate][:scores][:avg].to_f) > 20 total_score = total_score - (hardline - scores[:teammate][:scores][:avg].to_f)*0.5 end if total_score > 100 total_score = 100 end scores[:total_score] = total_score scores
Speeding up view score's function
At the beginning of the final project, the function of viewing scores is very slow by students and instructor. Our team found the real factor which lead to the slow problem and solved it in an easy approach.
Optimize response searching method in the model
- Modify get_assessments_for method in response_map.rb
After doing this, the time cost of view function decreased by more than 90% Before Refactoring:
# the original method to find all response @all_resp=Response.all for element in @all_resp if (element.map_id == map.map_id) @array_sort << element @test << map end end
After Refactoring:
@all_resp=Response.find_by_map_id(map.map_id) @array_sort << @all_resp @test << map
Test Result
Name | Before Refactoring | After Refactoring | Reduced By |
---|---|---|---|
View all team's score(instructor) | 484988ms | 8642ms | 98.21% |
View own score(student) | 8941ms | 651ms | 92.71% |
- Original Time for Instructor to View all scores
- Time for Instructor to View all scores after Refactoring
- Original Time for Student to View all scores
- Time for Student to View all scores after Refactoring
Snapshots
Instructor Login
Select Assignment
View Scores
See Also
Future Work
We will stick to the UI design of the grades view and see what else we can do to further improve the performance of the system. We are trying to figure out other reasons that may lead to the bad performance in addition to the two main reasons mentioned and solved in E804 and E805 projects.