CSC/ECE 517 Fall 2022 - E2258. Refactor submitted content controller: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(22 intermediate revisions by 3 users not shown)
Line 1: Line 1:
==E1553. Refactoring the Versions Controller==
==E2258. Refactoring submitted content Controller==


This page provides a description of the Expertiza based OSS project.  
This wiki is a description of Expertiza OSS project E2258. Refactor submitted_content_controller.rb




Line 12: Line 12:


===Problem Statement===
===Problem Statement===
The following tasks were accomplished in this project:


* Improved the clarity of code by improving the variable and parameter names.
* Remove the comment  “# hence use team count for the check”. The code it refers to is no longer there.
* Long character strings were taken and given appropriate names.
* Change the method name view to something more informative of the method.
* Handled pagination by a separate helper module, which can be used by multiple controllers.
* Line 79 Check the validity of the comment ”# Note: This is not used yet in the view until we all decide to do so”.
* Implemented action_allowed for access_control to prevent unauthorized access of methods.
* If we are not using this method, then remove it. We want to follow YAGNI (You Ain’t Gonna Need It)
* Prevented displaying of all versions for all users and tables when a user views the index page.
* [Important] submit_file method is long. Try to break it into multiple methods.
* Added missing CRUD methods to Versions Controller
* Also, check if variables defined as instance variables can be made local variables?
* Added RSPEC testcases for testing changes done in Versions Controller
* Inside folder_action, Too many nested if-else statements inside this method, can we do something better?
* The code needs method commenting and better commenting in general.
* Some of the actions in the code (from line 187 to 247) could perhaps be moved to another class.  Would it be helpful and clean to separate this controller into a submitted_files_controller and a submitted_hyperlinks_controller?
* If there is any dead code (not being used anymore ) delete it


===About Versions Controller===
===Solution===
This class manages different versions of reviews. If a reviewer reviews a submission, and after that, the author revises the submission, the next time the reviewer does a review (s)he will create a new version. Sometimes it’s necessary to find the current version of a review; sometimes it’s necessary to find all versions. Similarly, a user may want to delete the current version of a review, or all versions of a review. Pagination of versions helps the user to view a subset of versions at a time. Considering the huge number of versions in the system, it is very useful to have a pagination mechanism and a filtering mechanism which can be applied on the whole set of versions. The idea is to display the versions in an ordered, comprehensible and logical manner. In Expertiza the gem ‘will_paginate’ is used to achieve pagination.
The following tasks were accomplished in this project:


===Current Implementation===
* Improved code readability by updating the generic variable and function names to more specific naming based on its functionalities.
* Implemented DRY principle by eliminating redundant code.
* Eliminated unused blocks of code, following the principles of YAGNI(You Ain’t Gonna Need It).
* Updated existing comments and added new ones to provide better understanding of written code.
* Divided large functions into atomic modules ensuring one function is responsible to a single task.
* Added new tests to increase code coverage


===About Submitted Content Controller===
Submitted content controller controls everything a user submits to expertiza. This function is mainly used to add hyperlinks and upload files to an assignment.


=====Functionality=====
=====Functionality=====
* Any user irrespective of his/ her privileges can view all the versions.
* Only a user who is also the participant of the assignment can view the assignment
::The versions which a particular user can view should be restricted based on the privileges of the user. For instance, only a user with Administrator privileges should be able to view all the versions in the system. However, that is not the case now. Every user can view all the versions irrespective of whether the user is a student or an administrator.
::Any other user is redirected to the main page of the system.
* Any user can delete any version
* Participant cannot upload a file/hyperlink if the assignment submission deadline has crosses
::The versions which a particular user can delete should be restricted based on the privileges of the user. For instance, a student should not be allowed to delete any version. According to the current implementation any user can delete any version in the system.
::After the deadline, participants can just view the assignment.
* Filtering of versions were restricted to the current user
* Files of particular type and max 5 mb allowed
::The filtering options on versions were restricted to the current user. Sometimes a user might want to view versions associated with other users. For instance, an instructor might want to view the list of versions created by a particular student. This is not possible with the current implementation.
::No file beyond 5mb is allowed to be uploaded and a error message is displayed.


=====Drawbacks and Solutions=====
=====Drawbacks and Solutions=====
* '''Problem 1''': The method paginate_list is doing more than one thing.
* '''Problem 1''': Violation of the YAGNI principle:
::The method paginate_list was building a complex search criteria based on the input params, getting the list of versions from the Database matching this search criteria and then calling the Page API. All these tasks in a single method made it difficult to understand.
::Functions related to moving, copying, renaming and deleting an uploaded file were present in the controller, javascript file as well as the view. Though in the view, only functionality related to delete was uncommented whereas the other functionalities were disabled. This resulted in unused code being accumulated for each of these functionalities in the javascript file, which increased the size of the javascript file.
 
<pre>
# app/views/submitted_content/_submitted_files.html.erb
 
    <% if files.length > 0 and participant.team.directory_num != nil and participant.team.directory_num >= 0 %>
      <% if same_team_flag && stage != "Finished" %>
        <div style="padding-bottom:0.6em"><h5>File actions:</h5>
        <!--<input type="button" onclick="createNewFolder()"    value="Create new folder"    />
          <input type="button" onclick="copySelectedFile()"    value="Copy selected file"  />
          <input type="button" onclick="moveSelectedFile()"    value="Move selected file"  />
          <input type="button" onclick="renameFile()"          value="Rename selected file" />-->
          <input type="button" onclick="deleteSelectedFile();" value="Delete selected file" />
          <input type="reset" value="Reset">
        </div>
</pre>
 
<pre>
<pre>
# For filtering the versions list with proper search and pagination.
#app/assets/javascripts/submissions.js
  def paginate_list(id, user_id, item_type, event, datetime)
 
    # Set up the search criteria
function createNewFolder(){
    criteria = ''
var new_folder = prompt("Enter a name for a new folder","");
    criteria = criteria + "id = #{id} AND " if id && id.to_i > 0
.
    if current_user_role? == 'Super-Administrator'
        .
      criteria = criteria + "whodunnit = #{user_id} AND " if user_id && user_id.to_i > 0
        .
    end
}
    criteria = criteria + "whodunnit = #{current_user.try(:id)} AND " if current_user.try(:id) && current_user.try(:id).to_i > 0
 
    criteria = criteria + "item_type = '#{item_type}' AND " if item_type && !(item_type.eql? 'Any')
function moveSelectedFile(){
    criteria = criteria + "event = '#{event}' AND " if event && !(event.eql? 'Any')
var old_filename = getSelectedName();
    criteria = criteria + "created_at >= '#{time_to_string(params[:start_time])}' AND "
.
    criteria = criteria + "created_at <= '#{time_to_string(params[:end_time])}' AND "
        .
        .
}
 
function copySelectedFile(){
var old_filename = getSelectedName();
.
        .
        .
}
 
function renameFile(){       
var old_filename = getSelectedName();
.
        .
        .
}
</pre>


    if current_role == 'Instructor' || current_role == 'Administrator'
::For handling the action based on the user input for the above functionality, a nested if else loop was also present which was executed according to the value passed by the view (delete, create, copy, move, rename). As the functions were already commented, this nested if else loop was also unnecessary and increasing the program complexity.


    end
<pre>
#app/controllers/submitted_content_controller.rb


    # Remove the last ' AND '
'''# if else loop based on the actions'''
    criteria = criteria[0..-5]


     versions = Version.page(params[:page]).order('id').per_page(25).where(criteria)
     if params[:faction][:delete]
     versions
      delete_selected_files
  end
    elsif params[:faction][:rename]
      rename_selected_file
     elsif params[:faction][:move]
      move_selected_file
    elsif params[:faction][:copy]
      copy_selected_file
    elsif params[:faction][:create]
      create_new_folder
    end
</pre>
</pre>
* '''Solution''': The implementation has been changed in such a way that the versions which a user is allowed to see depends on the privileges of the user. The approach we have taken is as follows:
* '''Solution''': The implementation has been changed in such a way that the versions which a user is allowed to see depends on the privileges of the user. The approach we have taken is as follows:
**An administrator can see all the versions
**An administrator can see all the versions
Line 70: Line 121:
**A TA can see all the versions created by him and other users who are in the course for which he/ she assists.
**A TA can see all the versions created by him and other users who are in the course for which he/ she assists.
**A Student can see all the versions created by him/ her.
**A Student can see all the versions created by him/ her.
* '''Problem 2''': The search criteria created in the method paginate_list was difficult to comprehend.
* '''Problem 2''': A method performing multiple functions:
::The code which builds the search criteria in the method paginate_list uses many string literals and conditions and is hardly intuitive. The programmer will have to spend some time to understand what the code is really doing.
::The submit_file method in the controller app/controllers/submitted_content_controller.rb was performing multiple functions such as checking the file size, validating the file integrity by checking its type and then using its content to upload the file. This resulted in the controller being lengthy and complex. Ideally, there should be a modular approach for the above requirement.
* '''Solution''': The implementation has been changed. A student is not allowed to delete any versions now. Other types of users, for instance administrators, instructors and TAs are allowed to delete only the versions they are authorized to view.
* '''Problem 3''': The paginate method can be moved to a helper class.
::VersionsController is not the only component which require to paginate items. There are other components too. For instance, the UsersController has to paginate the list of users. Hence the Paginate method can be moved to a helper class which can be accessed by other components as well.
* '''Solution''': The filtering options has also been enhanced. The current user can now choose as part of the version search filter any user from a list of users if the current user is authorized to see the versions created by that user.


===New Implementation===
* '''Solution''': We have made the controller modular by introducing the below methods:
*The method paginate_list has been split into 2 methods now.
**validate_file_size_type
** BuildSearchCriteria – as the name suggests the sole purpose of this method is to build a search criteria based on the input search filters when the current user initiates a search in versions.
**get_sanitized_file_path
** paginate_list – this method will call the paginate API.
**get_curr_directory
:First the search criteria is built, then the criteria is applied to versions in the database to get all versions which matches the criteria and then the retrieved versions are paginated.
 
<pre>
* '''Problem 3''': Convention over configuration:
  # pagination.
::The method view is performing similar to that of show function which is the convention used by Rails for naming the function. Though in the code, the method was called 'view' and the related variables were named according to that.
  def paginate_list(versions)
    paginate(versions, VERSIONS_PER_PAGE);
  end


  def BuildSearchCriteria(id, user_id, item_type, event)
* '''Solution''': The method was renamed to show and the related variables were renamed accordingly.
    # Set up the search criteria
    search_criteria = ''
    search_criteria = search_criteria + add_id_filter_if_valid(id).to_s
    if current_user_role? == 'Super-Administrator'
      search_criteria = search_criteria + add_user_filter_for_super_admin(user_id).to_s
    end
    search_criteria = search_criteria + add_user_filter
    search_criteria = search_criteria + add_version_type_filter(item_type).to_s
    search_criteria = search_criteria + add_event_filter(event).to_s
    search_criteria = search_criteria + add_date_time_filter
    search_criteria
  end
</pre>
* The string literals and conditions in the method paginate_list were replaced with methods with intuitive names so that the programmer can understand the code more easily. We also removed an empty if clause and a redundant statement.
<pre>
  def add_id_filter_if_valid (id)
    "id = #{id} AND " if id && id.to_i > 0
  end


  def add_user_filter_for_super_admin (user_id)
* '''Problem 4''': Violation of the DRY principle:
    "whodunnit = #{user_id} AND " if user_id && user_id.to_i > 0
::The function of authorization was performed in each of the functions that were defined in the controller. This caused repetition of the code in each of the method. A change in the common functionality would mean changes at multiple places.
  end


  def add_user_filter
* '''Solution''': The common functionality was implemented in a common method called ensure_current_user_is_participant and that function is called in the before_action of each of the methods defined.
    "whodunnit = #{current_user.try(:id)} AND " if current_user.try(:id) && current_user.try(:id).to_i > 0
  end


  def add_event_filter (event)
===Code improvements===
    "event = '#{event}' AND " if event && !(event.eql? 'Any')
* To reduce redundancy and ensure DRY principle, the following function "ensure_current_user_is_participant" was created and called in before_action instead of calling it individually in multiple functions.
  end


  def add_date_time_filter
<pre>
    "created_at >= '#{time_to_string(params[:start_time])}' AND " +
before_action :ensure_current_user_is_participant, only: %i[edit show submit_hyperlink folder_action]
        "created_at <= '#{time_to_string(params[:end_time])}'"
</pre>
  end


  def add_version_type_filter (version_type)
<pre>
    "item_type = '#{version_type}' AND " if version_type && !(version_type.eql? 'Any')
def ensure_current_user_is_participant
  end
  @participant = AssignmentParticipant.find(params[:id])
  return unless current_user_id?(@participant.user_id)
end
</pre>
</pre>
* The paginate method has been moved to the helper class Pagination_Helper. This new method can be now reused by the different components like UsersController etc. The method receives two parameters, first the list to paginate and second the number of items to be displayed in a page.
 
* Renamed the :view function to :show. This was done to make it more relatable to the code written within that function.
 
* Divided the submit_file function into multiple atomic functions to ensure each function is responsible for one task.


<pre>
<pre>
module PaginationHelper
  # Check file content size and file type
  def validate_file_size_type(file, file_size_limit, file_content)
  .
  .
  end


  def paginate (items, number_of_items_per_page)
  # Sanitize and return the file name
    items.page(params[:page]).per_page(number_of_items_per_page)
  def get_sanitized_file_path(file, curr_directory)
  end
  .
  .
  .
  end
 
  # Get current directory path
  def get_curr_directory(participant)
    .
    .
    .
  end


end
  def submit_file
  .
  .
  if (!validate_file_size_type(file, file_size_limit, file_content))
  .
  .
  curr_directory = get_curr_directory(participant)
  .
  .
  sanitized_file_path = get_sanitized_file_path(file, curr_directory)
  .
  .
  end
</pre>
</pre>


===Code improvements===
* Removed existing unused code for copy, move, rename file and create folder (Refer to this commit: https://github.com/expertiza/expertiza/pull/2459/commits/eb9353fccfef245dab13915e80616bc46cca723a)
* Introduced a constant VERSIONS_PER_PAGE and assigned the value 25 to it. The pagination algorithm for VersionsController displays at most 25 versions in a page. The existing implementation uses the value 25 straight in the code and there are few problems associated with such an approach.
 
** It is not easy to understand what 25 is unless the programmer takes a close look at the code.
* Added new comments and updated old ones. Example:
** In case if the value 25 is used at more than one places and in future a new requirement comes to show at most 30 versions in a page, all the values will have to be modified. It is not very DRY.
* The VersionsController was overriding AccessHelper - action_allowed? method to return true in all the cases. This was violating the whole purpose of the method action_allowed?. The purpose of this method is to determine whether the user who is triggering a CRUD operation is allowed to do so. So when the current user invokes a CRUD operation, the action_allowed? method is invoked first and if the method returns true the CRUD operation is triggered or else the user is intimated with a message and gracefully exited. Hence, when the action_allowed? method is overridden to return true always, it results in providing unauthorized access to certain users.


<pre>
<pre>
def action_allowed?
  # Validate whether a particular action is allowed by the current user or not based on the privileges
    true
  def action_allowed?
  end
 
  # submit_hyperlink is called when a new hyperlink is added to an assignment
  def submit_hyperlink
</pre>
</pre>


:With the new implementation the AccessHelper - action_allowed? method has been modified in such a way that unauthorized access is prevented. As per the new algorithm, 'new', 'create', 'edit', 'update' cannot be invoked by any user. These operations can be accessed only by ‘papertrail’ gem. Only an ‘Administrator’ or ‘Super-Administrator’ can call 'destroy_all' method. All the other methods are accessible to ‘Administrator’,  ‘Super-Administrator’, ‘Instructor’, ‘Teaching Assistant’ and ‘Student’.
===Test Plan===
The following plan is considered to ensure thorough testing of submitted_content_controller:
 
* Automated Tests with both positive and negative scenarios to ensure all functions of controller are working as expected.
* Manual Tests using UI to ensure the functionality of overall system isn't affected by adding new changes. These tests mainly involve the steps of adding/removing files and hyperlinks from assignment submission page.
 
Further details on above tests can be found in below sections.
 
===Automated Testing using RSPEC===
The submitted_content_controller has existing automated rspec tests in place.  
Furthermore following tests were added to the code to ensure the functionality is working as expected and thereby increasing code coverage:


<pre>
<pre>
  def action_allowed?
# Test to verify is one_team_can_submit_work? function is working as expected
    case params[:action]
  describe '#one_team_can_submit_work' do
     when 'new', 'create', 'edit', 'update'
     it 'participant cannot submit work if team doesnt hold a topic' do
    #Modifications can only be done by papertrail
      allow(AssignmentParticipant).to receive(:find).and_return(participant)
       return false
       allow(SignUpTopic).to receive(:where).and_return([nil])
    when 'destroy_all'
      params = { id: 21 }
       ['Super-Administrator',
       allow(controller).to receive(:params).and_return(params)
      'Administrator'].include? current_role_name
      expect(controller.send(:one_team_can_submit_work?)).to eql(false)
     else
    end
       #Allow all others
     it 'participant can submit work if team holds a topic' do
       ['Super-Administrator',
       allow(AssignmentParticipant).to receive(:find).and_return(participant)
      'Administrator',
       allow(SignUpTopic).to receive(:where).and_return([signup_topic])
      'Instructor',
      allow(SignedUpTeam).to receive(:topic_id).and_return(1)
      'Teaching Assistant',
      params = { id: 21 }
      'Student'].include? current_role_name
      allow(controller).to receive(:params).and_return(params)
      expect(controller.send(:one_team_can_submit_work?)).to eql(true)
     end
     end
   end
   end
</pre>
</pre>


===Automated Testing using RSPEC===
All the tests currently pass after pushing the updates to the code. The tests can be executed "rspec spec/controllers/submitted_content_controller_spec.rb" command as shown below.
The current version of expertiza did not have any test for VersionsController. Using the test driven development(TDD) approach, we have added an exhaustive set of RSPEC tests for VersionsController, to test all the modifications we have done to the code of the controller class. The tests use double and stub features of rspec-rails gem, to fake the log in by different users - Administrator, Instructor, Student etc. The tests can be executed "rpec spec" command as shown below.
<pre>
<pre>
user-expertiza $rspec spec
rspec spec/controllers/submitted_content_controller_spec.rb
.
.
.
.
.
.
Finished in 5.39 seconds (files took 25.33 seconds to load)
66 examples, 0 failures


Randomized with seed 19254
Finished in 15.47 seconds (files took 10 seconds to load)
.
51 examples, 0 failures, 15 pending
.
</pre>
</pre>


===Testing from UI===
===Testing from UI===
Following are a few testcases with respectto our code changes that can be tried from UI:
Following are a few testcases with respectto our code changes that can be tried from UI:
1. To go to versions index page, type in the following url after logging in:
  http://152.46.16.81:3000/versions


2. After logging in as student/instructor or admin : Try accessing the  new, create, edit, update actions. These actions are not allowed to any of the users.
1. To go to assignments submission page, type in the following url: http://yoururl.tech/expertiza
  http://152.46.16.81:3000/versions/new
  This calls the new action. In the current production version of expertiza, it is unhandled and application gives a default 404 page.


3. Another feature that can be tested from UI is Pagination. Try searching for a user's versions and see if the results are paginated or not. Search here:
2. After logging in as student/instructor or admin (ID: instructor6, Password: password), go to Assignments (http://152.7.176.53:8080/student_task/list) and open the Test1 assignment, and try to submit a new file or hyperlink.
  http://152.46.16.81:3000/versions/search


4. Visit the same URL as step 3, you should see only the students under that instructor in the users dropdown.
3. Following test cases were checked:
* Upload a file greater than 5 mb and it shouldn't be allowed and an error should be visible
* Upload a txt file and it shouldn't be allowed and an error should be visible
* Upload a pdf file less than 5 mb and it should be allowed to be uploaded
* Uploaded file can be deleted
* New hyperlinks can be added
* Existing hyperlinks can be deleted


===References===
===References===


#[https://github.com/expertiza/expertiza Expertiza on GitHub]
#[https://github.com/expertiza/expertiza Expertiza on GitHub]
#[https://github.com/WintersLt/expertiza GitHub Project Repository Fork]
#[https://github.com/OODD-OSS/expertiza GitHub Project Repository Fork]
#[http://expertiza.ncsu.edu/ The live Expertiza website]
#[http://expertiza.ncsu.edu/ The live Expertiza website]
#[http://bit.ly/myexpertiza Demo link]  
#[http://yoururl.tech/expertiza Demo link]  
#[http://wikis.lib.ncsu.edu/index.php/Expertiza Expertiza project documentation wiki]
#[http://wikis.lib.ncsu.edu/index.php/Expertiza Expertiza project documentation wiki]
#[https://relishapp.com/rspec Rspec Documentation]
#[https://relishapp.com/rspec Rspec Documentation]
#Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin
#Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin

Latest revision as of 22:17, 1 November 2022

E2258. Refactoring submitted content Controller

This wiki is a description of Expertiza OSS project E2258. Refactor submitted_content_controller.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.

Problem Statement

  • Remove the comment “# hence use team count for the check”. The code it refers to is no longer there.
  • Change the method name view to something more informative of the method.
  • Line 79 Check the validity of the comment ”# Note: This is not used yet in the view until we all decide to do so”.
  • If we are not using this method, then remove it. We want to follow YAGNI (You Ain’t Gonna Need It)
  • [Important] submit_file method is long. Try to break it into multiple methods.
  • Also, check if variables defined as instance variables can be made local variables?
  • Inside folder_action, Too many nested if-else statements inside this method, can we do something better?
  • The code needs method commenting and better commenting in general.
  • Some of the actions in the code (from line 187 to 247) could perhaps be moved to another class. Would it be helpful and clean to separate this controller into a submitted_files_controller and a submitted_hyperlinks_controller?
  • If there is any dead code (not being used anymore ) delete it

Solution

The following tasks were accomplished in this project:

  • Improved code readability by updating the generic variable and function names to more specific naming based on its functionalities.
  • Implemented DRY principle by eliminating redundant code.
  • Eliminated unused blocks of code, following the principles of YAGNI(You Ain’t Gonna Need It).
  • Updated existing comments and added new ones to provide better understanding of written code.
  • Divided large functions into atomic modules ensuring one function is responsible to a single task.
  • Added new tests to increase code coverage

About Submitted Content Controller

Submitted content controller controls everything a user submits to expertiza. This function is mainly used to add hyperlinks and upload files to an assignment.

Functionality
  • Only a user who is also the participant of the assignment can view the assignment
Any other user is redirected to the main page of the system.
  • Participant cannot upload a file/hyperlink if the assignment submission deadline has crosses
After the deadline, participants can just view the assignment.
  • Files of particular type and max 5 mb allowed
No file beyond 5mb is allowed to be uploaded and a error message is displayed.
Drawbacks and Solutions
  • Problem 1: Violation of the YAGNI principle:
Functions related to moving, copying, renaming and deleting an uploaded file were present in the controller, javascript file as well as the view. Though in the view, only functionality related to delete was uncommented whereas the other functionalities were disabled. This resulted in unused code being accumulated for each of these functionalities in the javascript file, which increased the size of the javascript file.
# app/views/submitted_content/_submitted_files.html.erb

    <% if files.length > 0 and participant.team.directory_num != nil and participant.team.directory_num >= 0 %>
      <% if same_team_flag && stage != "Finished" %>
        <div style="padding-bottom:0.6em"><h5>File actions:</h5>
        <!--<input type="button" onclick="createNewFolder()"     value="Create new folder"    />
          <input type="button" onclick="copySelectedFile()"    value="Copy selected file"   />
          <input type="button" onclick="moveSelectedFile()"    value="Move selected file"   />
          <input type="button" onclick="renameFile()"          value="Rename selected file" />-->
          <input type="button" onclick="deleteSelectedFile();" value="Delete selected file" />
          <input type="reset" value="Reset">
        </div>
#app/assets/javascripts/submissions.js

function createNewFolder(){
	var new_folder = prompt("Enter a name for a new folder","");
	.
        .
        .
}

function moveSelectedFile(){	
	var old_filename = getSelectedName();
	.
        .
        .
}

function copySelectedFile(){	
	var old_filename = getSelectedName();
	.
        .
        .
}

function renameFile(){        
	var old_filename = getSelectedName();
	.
        .
        .
}
For handling the action based on the user input for the above functionality, a nested if else loop was also present which was executed according to the value passed by the view (delete, create, copy, move, rename). As the functions were already commented, this nested if else loop was also unnecessary and increasing the program complexity.
#app/controllers/submitted_content_controller.rb

'''# if else loop based on the actions'''

    if params[:faction][:delete]
      delete_selected_files
    elsif params[:faction][:rename]
      rename_selected_file
    elsif params[:faction][:move]
      move_selected_file
    elsif params[:faction][:copy]
      copy_selected_file
    elsif params[:faction][:create]
      create_new_folder
    end
  • Solution: The implementation has been changed in such a way that the versions which a user is allowed to see depends on the privileges of the user. The approach we have taken is as follows:
    • An administrator can see all the versions
    • An instructor can see all the versions created by him and other users who are in his course or are participants in the assignments he creates.
    • A TA can see all the versions created by him and other users who are in the course for which he/ she assists.
    • A Student can see all the versions created by him/ her.
  • Problem 2: A method performing multiple functions:
The submit_file method in the controller app/controllers/submitted_content_controller.rb was performing multiple functions such as checking the file size, validating the file integrity by checking its type and then using its content to upload the file. This resulted in the controller being lengthy and complex. Ideally, there should be a modular approach for the above requirement.
  • Solution: We have made the controller modular by introducing the below methods:
    • validate_file_size_type
    • get_sanitized_file_path
    • get_curr_directory
  • Problem 3: Convention over configuration:
The method view is performing similar to that of show function which is the convention used by Rails for naming the function. Though in the code, the method was called 'view' and the related variables were named according to that.
  • Solution: The method was renamed to show and the related variables were renamed accordingly.
  • Problem 4: Violation of the DRY principle:
The function of authorization was performed in each of the functions that were defined in the controller. This caused repetition of the code in each of the method. A change in the common functionality would mean changes at multiple places.
  • Solution: The common functionality was implemented in a common method called ensure_current_user_is_participant and that function is called in the before_action of each of the methods defined.

Code improvements

  • To reduce redundancy and ensure DRY principle, the following function "ensure_current_user_is_participant" was created and called in before_action instead of calling it individually in multiple functions.
before_action :ensure_current_user_is_participant, only: %i[edit show submit_hyperlink folder_action]
def ensure_current_user_is_participant
   @participant = AssignmentParticipant.find(params[:id])
   return unless current_user_id?(@participant.user_id)
end
  • Renamed the :view function to :show. This was done to make it more relatable to the code written within that function.
  • Divided the submit_file function into multiple atomic functions to ensure each function is responsible for one task.
   # Check file content size and file type
   def validate_file_size_type(file, file_size_limit, file_content)
   .
   .
   end

   # Sanitize and return the file name
   def get_sanitized_file_path(file, curr_directory)
   .
   .
   .
   end

   # Get current directory path
   def get_curr_directory(participant)
    .
    .
    .
   end

   def submit_file
   .
   .
   if (!validate_file_size_type(file, file_size_limit, file_content))
   .
   .
   curr_directory = get_curr_directory(participant)
   .
   .
   sanitized_file_path = get_sanitized_file_path(file, curr_directory)
   .
   .
   end
  • Added new comments and updated old ones. Example:
   # Validate whether a particular action is allowed by the current user or not based on the privileges
   def action_allowed?

   # submit_hyperlink is called when a new hyperlink is added to an assignment
   def submit_hyperlink

Test Plan

The following plan is considered to ensure thorough testing of submitted_content_controller:

  • Automated Tests with both positive and negative scenarios to ensure all functions of controller are working as expected.
  • Manual Tests using UI to ensure the functionality of overall system isn't affected by adding new changes. These tests mainly involve the steps of adding/removing files and hyperlinks from assignment submission page.

Further details on above tests can be found in below sections.

Automated Testing using RSPEC

The submitted_content_controller has existing automated rspec tests in place. Furthermore following tests were added to the code to ensure the functionality is working as expected and thereby increasing code coverage:

# Test to verify is one_team_can_submit_work? function is working as expected
  describe '#one_team_can_submit_work' do
    it 'participant cannot submit work if team doesnt hold a topic' do
      allow(AssignmentParticipant).to receive(:find).and_return(participant)
      allow(SignUpTopic).to receive(:where).and_return([nil])
      params = { id: 21 }
      allow(controller).to receive(:params).and_return(params)
      expect(controller.send(:one_team_can_submit_work?)).to eql(false)
    end
    it 'participant can submit work if team holds a topic' do
      allow(AssignmentParticipant).to receive(:find).and_return(participant)
      allow(SignUpTopic).to receive(:where).and_return([signup_topic])
      allow(SignedUpTeam).to receive(:topic_id).and_return(1)
      params = { id: 21 }
      allow(controller).to receive(:params).and_return(params)
      expect(controller.send(:one_team_can_submit_work?)).to eql(true)
    end
  end

All the tests currently pass after pushing the updates to the code. The tests can be executed "rspec spec/controllers/submitted_content_controller_spec.rb" command as shown below.

rspec spec/controllers/submitted_content_controller_spec.rb
.
.
.

Finished in 15.47 seconds (files took 10 seconds to load)
51 examples, 0 failures, 15 pending

Testing from UI

Following are a few testcases with respectto our code changes that can be tried from UI:

1. To go to assignments submission page, type in the following url: http://yoururl.tech/expertiza

2. After logging in as student/instructor or admin (ID: instructor6, Password: password), go to Assignments (http://152.7.176.53:8080/student_task/list) and open the Test1 assignment, and try to submit a new file or hyperlink.

3. Following test cases were checked:

  • Upload a file greater than 5 mb and it shouldn't be allowed and an error should be visible
  • Upload a txt file and it shouldn't be allowed and an error should be visible
  • Upload a pdf file less than 5 mb and it should be allowed to be uploaded
  • Uploaded file can be deleted
  • New hyperlinks can be added
  • Existing hyperlinks can be deleted

References

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