CSC/ECE 517 Spring 2016/Refactor review mapping controller.rb: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
{{Infobox software
==E1553. Refactoring the Versions Controller==
| name                  =  
| logo                  = <!-- Image name is enough -->
| author                =
| developer              =
| released              = <!-- {{Start date and age|YYYY|MM|DD|df=yes/no}} -->
| latest release version =
| latest release date    = <!-- {{Start date and age|YYYY|MM|DD|df=yes/no}} -->
| status                =
| programming language  =
| operating system      =
| license                =  
| website                = {{URL|example.org}}
}}
Devise is a [https://en.wikipedia.org/wiki/Rack_(web_server_interface) rack] based full-fledged authentication system for [https://en.wikipedia.org/wiki/Ruby_on_Rails Rails]. It is a complete [https://en.wikipedia.org/wiki/Model-view-controller MVC] solution meaning it can support various models, views and controllers as part of its code and can be used be directly by developers. Devise is simple to use and starts up with a couple of commands but it is also highly customizable. Devise saves a lot of time and effort as many applications require user registration and authentication mechanisms which are difficult to develop from scratch.


=='''History'''==
This page provides a description of the Expertiza based OSS project.  
Devise is first introduced in January 2010 by [https://github.com/plataformatec Plataformatec], a company which builds web and mobile applications. Devise is one of the few authentication systems which support rack based applications and hence can support Rails 3 and up as they are completely rack based. The latest version of Devise available is v3.5.3 and it is up to date with Rails 5 beta 2.
===Warden===
Devise gem is built on top of a rack application called [https://github.com/hassox/warden/wiki Warden] which is used to verify the identity of logged in user using a session string. In Warden, the id which is a primary key of a user is somehow stored to match it later with the logged in user. Warden also provides restricted access to guest users depending on the functional requirements of an application.<br/><br/>
Since Warden does not know of the existence of the Rails application, it cannot provide any helper methods, controller classes, views etc. This is where Devise comes into play and can integrate with Rails seemlessly. Devise interacts often with Warden using Strategy design pattern for [https://en.wikipedia.org/wiki/Encryption encrypting] passwords, [https://en.wikipedia.org/wiki/Basic_access_authentication HTTP Authentication] etc.


=='''Installation'''==
Although Devise is very useful and reduces the amount of effort in developing authentication mechanisms significantly, it requires a good understanding of the Rails framework. Hence it is advised for beginners to not use Devise.<br/><br/>
There are several commands that are required for the successful installation of Devise. They are listed out below:
*Add the devise gem to your gemfile.
gem 'devise'
*Run bundle command to install the gem.
bundle install
*You need to run the generator next which will install an initializer and creates all the configuration files.
rails generate devise:install
*Now you can add Devise to any of your models using the generator. This will create a class with the model name given and routes etc. The model will be configured with default Devise modules. The config/routes.rb file will be configured to point to the Devise controller.
rails generate devise user //Assuming that the model name is user
*Next, add any configuration changes that are required and then run:
rake db:migrate
*The following step will create Devise views but is optional. Devise has views for every generic operation like Login or SignUp and can be used directly instead of creating custom views.
rails generate devise:views users
* There are also many routes that are defined in config/routes.rb with a line like:
devise_for :users


=='''Modules'''==
__TOC__
When a devise generator is invoked a model class is created in app/models for you modify for your specific application requirements. This is the place where many important configuration changes are specified. Perhaps the most important are the Devise modules which provides essential functionalities like enhanced securtiy.<br/><br/>
 
There are 10 modules listed on the official page of Devise by Plataformatec. These modules are features that are contained in Devise and can be used by the developers depending on the use-cases or requirements of their application. Below is the list of modules:
 
*Database Authenticable
===Introduction to Expertiza===
*Omniauthable
 
*Confirmable
[http://expertiza.ncsu.edu/ Expertiza] is an open source project based on [http://rubyonrails.org/ 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.
*Recoverable
 
*Registerable
===Problem Statement===
*Rememberable
The following tasks were accomplished in this project:
*Trackable
 
*Timeoutable
* Improved the clarity of code by improving the variable and parameter names.
*Validatable
* Long character strings were taken and given appropriate names.
*Lockable
* Handled pagination by a separate helper module, which can be used by multiple controllers.
Information regarding each module is listed in README as well as the [http://devise.plataformatec.com.br/ Plataformatec] website. The modules are included in an application in this way:
* Implemented action_allowed for access_control  to prevent unauthorized access of methods.
* Prevented displaying of all versions for all users and tables when a user views the index page.
* Added missing CRUD methods to Versions Controller
* Added RSPEC testcases for testing changes done in Versions Controller
 
===About Versions Controller===
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.
 
===Current Implementation===
 
 
=====Functionality=====
* Any user irrespective of his/ her privileges can view all the versions.
::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 user can delete any version
::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.
* Filtering of versions were restricted to the current user
::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.
 
=====Drawbacks and Solutions=====
* '''Problem 1''': The method paginate_list is doing more than one thing.
::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.
<pre>
# For filtering the versions list with proper search and pagination.
  def paginate_list(id, user_id, item_type, event, datetime)
    # Set up the search criteria
    criteria = ''
    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')
    criteria = criteria + "event = '#{event}' AND " if event && !(event.eql? 'Any')
    criteria = criteria + "created_at >= '#{time_to_string(params[:start_time])}' AND "
    criteria = criteria + "created_at <= '#{time_to_string(params[:end_time])}' AND "
 
    if current_role == 'Instructor' || current_role == 'Administrator'
 
    end
 
    # Remove the last ' AND '
    criteria = criteria[0..-5]
 
    versions = Version.page(params[:page]).order('id').per_page(25).where(criteria)
    versions
  end
</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:
**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''': The search criteria created in the method paginate_list was difficult to comprehend.
::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.
* '''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===
*The method paginate_list has been split into 2 methods now.
** 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.
** paginate_list – this method will call the paginate API.
: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>
  # pagination.
  def paginate_list(versions)
    paginate(versions, VERSIONS_PER_PAGE);
  end
 
  def BuildSearchCriteria(id, user_id, item_type, event)
    # 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)
    "whodunnit = #{user_id} AND " if user_id && user_id.to_i > 0
  end
 
  def add_user_filter
    "whodunnit = #{current_user.try(:id)} AND " if current_user.try(:id) && current_user.try(:id).to_i > 0
  end
 
  def add_event_filter (event)
    "event = '#{event}' AND " if event && !(event.eql? 'Any')
  end
 
  def add_date_time_filter
    "created_at >= '#{time_to_string(params[:start_time])}' AND " +
        "created_at <= '#{time_to_string(params[:end_time])}'"
  end
 
  def add_version_type_filter (version_type)
    "item_type = '#{version_type}' AND " if version_type && !(version_type.eql? 'Any')
  end
</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.
 
<pre>
<pre>
class User < ActiveRecord::Base
module PaginationHelper
   devise :database_authenticable, :omniauthable, :confirmable, :rememberable,
 
          :trackable, :timeoutable, :lockable
   def paginate (items, number_of_items_per_page)
    items.page(params[:page]).per_page(number_of_items_per_page)
  end
 
end
end
</pre>
</pre>
In addition to the classes generated Devise also generates a [https://en.wikipedia.org/wiki/Data_migration#Database_migration database migration] in which fields related to the functionalities of these modules are added. Each field is related to a specific module and hence when a module is not require some of the fields may be removed from the migration to the [https://en.wikipedia.org/wiki/Database database]. Also most of these modules have specific [https://en.wikipedia.org/wiki/Form_(HTML) forms] and view associated with them. The forms are used by an end user to type in his/her information which will then be sent to the Devise controllers.


=='''Methods'''==
===Code improvements===
There are many classes in Devise which include models, controllers, helpers, views, routes etc. But much of the functionality offered by Devise is exposed via simple helper methods. Some of the most important methods which can be used in building our own application are:
* 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.
<br/>
** It is not easy to understand what 25 is unless the programmer takes a close look at the code.
*'''authenticate_user! :''' This method is used to check whether a user is logged in before he/she attempts to perform a specific set of controller actions. authenticate_user! may be called with before_action as shown below to ensure the user is logged in before performing any of the operations.
** 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.
before_action :authenticate_user!
* 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.
If only some of the actions need authentication and some do not, we can use except clause so that only some actions are blocked as guest and others are accessible. The code with except clause is as below:
 
before_action :authenticate_user! except [:index, :show]
In the above example index and show are two controller actions associated with operations which do not require user authentication and can be browsed as a guest.Authenticate user may also be used with in a controller action so that it is application only for that specification. We need to use before_filter instead of before_action to achieve this purpose.
<pre>
<pre>
class EndUserBaseController < ApplicationController
def action_allowed?
  before_filter :authentication_user!
    true
end
  end
</pre>
</pre>
In this example the application will authenticate a user only if he is trying to perform an action associated with the EndUserBaseController. If in any of the above cases a user is not logged in the application backs off and redirects to its sign-in page.
 
*'''current_user :''' current_user method is used to return the model class corresponding to the user who is currently signed in. For example, if you are building a messaging application, you may retrieve all the sent messages of a user as:
: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’.
 
<pre>
<pre>
class SentMessagesController < ApplicationController
  def action_allowed?
  before_filter :authentication_user!
    case params[:action]
  def index
    when 'new', 'create', 'edit', 'update'
    @sent_messages = current_user.sent_messages.all
    #Modifications can only be done by papertrail
  end
      return false
end
    when 'destroy_all'
      ['Super-Administrator',
      'Administrator'].include? current_role_name
    else
      #Allow all others
      ['Super-Administrator',
      'Administrator',
      'Instructor',
      'Teaching Assistant',
      'Student'].include? current_role_name
    end
  end
</pre>
</pre>
Notice how authenticate_user! is used before checking the messages of the current user so as to ensure that the user is signed in before checking his/her messages.
 
*'''user_signed_in? :''' As the name suggests, user_signed_in? method is used to check whether a user is signed in. This is useful when you want to show two different pages depending on whether a user has logged in or not. For example, when a user is logged in you want to show him/her an option to Logout otherwise you want to show Register or Login options.
===Automated Testing using RSPEC===
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>
<% if user_signed_in? %>
user-expertiza $rspec spec
  <li><%- link_to "Logout", destroy_user_session_path, method :delete %></li>
.
<% else %>
.
  <li><%- link_to "Sign Up", new_user_registration_path %></li>
.
  <li><%- link_to "Login", new_user_session_path %></li>
Finished in 5.39 seconds (files took 25.33 seconds to load)
<% end %>
66 examples, 0 failures
 
Randomized with seed 19254
.
.
</pre>
</pre>
We need to use method :delete to logout so that Devise will only logout when a HTTP delete request is made by user and does not accidentally logout because of a malicious link that automatically logs out the user.
*'''sign_in(@user) and sign_out(@user) :''' These methods are used to login(sign_in(@user)) or logout(sign_out(@user)) a newly created or existing user.
*'''user_session :''' This method returns metadata about the user that is currently logged in.
The methods that are most frequently used by developers are current_user and user_signed_in? which are present as helper methods. Also if the methods are to be referred to an Admin then replace user in each method with admin i.e. current_user becomes current_admin etc.


=='''See Also'''==
===Testing from UI===
=='''References'''==
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.
  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:
  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.
 
===References===
 
#[https://github.com/expertiza/expertiza Expertiza on GitHub]
#[https://github.com/WintersLt/expertiza GitHub Project Repository Fork]
#[http://expertiza.ncsu.edu/ The live Expertiza website]
#[http://bit.ly/myexpertiza  Demo link]
#[http://wikis.lib.ncsu.edu/index.php/Expertiza Expertiza project documentation wiki]
#[https://relishapp.com/rspec Rspec Documentation]
#Clean Code: A handbook of agile software craftsmanship. Author: Robert C Martin

Revision as of 21:05, 23 March 2016

E1553. Refactoring the Versions Controller

This page provides a description of the Expertiza based OSS project.



Introduction to 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

The following tasks were accomplished in this project:

  • Improved the clarity of code by improving the variable and parameter names.
  • Long character strings were taken and given appropriate names.
  • Handled pagination by a separate helper module, which can be used by multiple controllers.
  • Implemented action_allowed for access_control to prevent unauthorized access of methods.
  • Prevented displaying of all versions for all users and tables when a user views the index page.
  • Added missing CRUD methods to Versions Controller
  • Added RSPEC testcases for testing changes done in Versions Controller

About Versions Controller

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.

Current Implementation

Functionality
  • Any user irrespective of his/ her privileges can view all the versions.
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 user can delete any version
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.
  • Filtering of versions were restricted to the current user
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.
Drawbacks and Solutions
  • Problem 1: The method paginate_list is doing more than one thing.
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.
# For filtering the versions list with proper search and pagination.
  def paginate_list(id, user_id, item_type, event, datetime)
    # Set up the search criteria
    criteria = ''
    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')
    criteria = criteria + "event = '#{event}' AND " if event && !(event.eql? 'Any')
    criteria = criteria + "created_at >= '#{time_to_string(params[:start_time])}' AND "
    criteria = criteria + "created_at <= '#{time_to_string(params[:end_time])}' AND "

    if current_role == 'Instructor' || current_role == 'Administrator'

    end

    # Remove the last ' AND '
    criteria = criteria[0..-5]

    versions = Version.page(params[:page]).order('id').per_page(25).where(criteria)
    versions
  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: The search criteria created in the method paginate_list was difficult to comprehend.
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.
  • 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

  • The method paginate_list has been split into 2 methods now.
    • 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.
    • paginate_list – this method will call the paginate API.
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.
  # pagination.
  def paginate_list(versions)
    paginate(versions, VERSIONS_PER_PAGE);
  end

  def BuildSearchCriteria(id, user_id, item_type, event)
    # 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
  • 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.
  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)
    "whodunnit = #{user_id} AND " if user_id && user_id.to_i > 0
  end

  def add_user_filter
    "whodunnit = #{current_user.try(:id)} AND " if current_user.try(:id) && current_user.try(:id).to_i > 0
  end

  def add_event_filter (event)
    "event = '#{event}' AND " if event && !(event.eql? 'Any')
  end

  def add_date_time_filter
    "created_at >= '#{time_to_string(params[:start_time])}' AND " +
        "created_at <= '#{time_to_string(params[:end_time])}'"
  end

  def add_version_type_filter (version_type)
    "item_type = '#{version_type}' AND " if version_type && !(version_type.eql? 'Any')
  end
  • 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.
module PaginationHelper

  def paginate (items, number_of_items_per_page)
    items.page(params[:page]).per_page(number_of_items_per_page)
  end

end

Code improvements

  • 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.
    • 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.
def action_allowed?
    true
  end
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’.
  def action_allowed?
    case params[:action]
    when 'new', 'create', 'edit', 'update'
    #Modifications can only be done by papertrail
      return false
    when 'destroy_all'
      ['Super-Administrator',
       'Administrator'].include? current_role_name
    else
      #Allow all others
      ['Super-Administrator',
       'Administrator',
       'Instructor',
       'Teaching Assistant',
       'Student'].include? current_role_name
    end
  end

Automated Testing using RSPEC

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.

user-expertiza $rspec spec
.
.
.
Finished in 5.39 seconds (files took 25.33 seconds to load)
66 examples, 0 failures

Randomized with seed 19254
.
.

Testing 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.

  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:

  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.

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