User:Spurush

From Expertiza_Wiki
Revision as of 19:12, 31 October 2023 by Spurush (talk | contribs)
Jump to navigation Jump to search

E2368. Reimplement due_date.rb

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



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.

Project Changes

In this project, the following specific changes were implemented:

  • Removed methods used exclusively in test files.
  • Created a new file, due_date_helper.rb, to handle specific functionalities.
  • Moved pertinent methods from the model class to the new helper class.
  • Refactored each method to comply with DRY (Don't Repeat Yourself), SRP (Single Responsibility Principle), and other Ruby conventions.
  • Renamed methods to accurately describe their responsibilities.
  • Addressed identified code smells and issues using Code Climate analysis.

These changes aimed to enhance code quality, organization, and adherence to best practices in the Expertiza project.

Current Implementation of DueDate Class

The current implementation of the `DueDate` class can be summarized in three key points:

Deadline and Time Handling

The class manages due dates for various elements within an assignment. It includes functionalities to:

  • Determine the current due date based on time comparisons.
  • Check for teammate review allowance based on due dates.
  • Set flags and validate due date formats.

Assignment and Topic-Based Due Date Retrieval

The class retrieves the next due date for assignments or topics. It includes logic to:

  • Handle staggered deadlines for topics and fallback procedures for unavailable topic-specific due dates.
  • Retrieve assignment-specific due dates considering the current time.

Functionality for Assignment Duplication

The class offers methods to handle due date duplication between assignments. It includes functionalities to:

  • Copy due dates from one assignment to another.
  • Set due dates for assignments based on various parameters.
  • Calculate assignment-specific rounds for responses like author feedback, quizzes, and reviews based on deadlines.

This `DueDate` class serves as a central component within the Expertiza project for managing and handling due dates, validation, and retrieval concerning assignment and deadline management.

Improvements in the New Implementation of DueDate Class

The changes made in the new implementation of the `DueDate` class addressed several issues, along with their corresponding solutions:

Removed methods used only in the test files

  • Problem: The code contained methods tailored exclusively for testing, leading to unnecessary clutter and potential confusion within the primary codebase.
  • Solution: Removal of test-specific methods streamlined the code, enhancing focus and clarity, ensuring that the main code is more comprehensible for developers working on functional components.

The method `set_duedate` was originally housed in the `due_date.rb` file and was later relocated to the `due_date_spec.rb` file within the Expertiza project. This method is responsible for setting a due date.

The relocated `set_duedate` method performs the following tasks:

def self.set_duedate(duedate, deadline, assign_id, max_round)
  submit_duedate = DueDate.new(duedate)
  submit_duedate.deadline_type_id = deadline
  submit_duedate.parent_id = assign_id
  submit_duedate.round = max_round
  submit_duedate.save
end

Created a new due_date_helper.rb file

  • Problem: The primary model class had an abundance of methods, including helper functions, affecting readability and maintainability.
  • Solution: The establishment of a dedicated file for due date helper methods segregated the code, enabling a clear distinction between model-specific functions and those supporting due date functionalities, leading to improved modularity and organization.

Moved relevant methods from the model class to the helper class

  • Problem: The model class contained methods better suited for helper functions, violating the Single Responsibility Principle and causing complexity.
  • Solution: Relocating pertinent methods to the helper class aligned the code with the Single Responsibility Principle, enhancing maintainability and organization by ensuring distinct purposes for each class.

Below are the contents of the newly implemented file app/helpers/due_date_helper.rb

# frozen_string_literal: true

# app/helpers/due_date_helper.rb

# This module contains helper methods related to due dates.
module DueDateHelper
  def self.deadline_sort(due_dates)
    # Override the comparator operator to sort due dates by due_at
    due_dates.sort { |m1, m2| m1.due_at.to_i <=> m2.due_at.to_i }
  end

...

Refactored each method according to DRY, SRP, and other Ruby conventions

  • Problem: Methods were potentially repetitive, non-optimized, or didn't adhere to best practices, causing inefficiencies and confusion.
  • Solution: Method refactoring according to DRY, SRP, and Ruby conventions optimized the code for clarity, efficiency, and maintainability, ensuring each method has a clear purpose and follows standard practices.


Before refactoring

  def self.done_in_assignment_round(assignment_id, response)
    # for author feedback, quiz, teammate review and metareview, Expertiza only support one round, so the round # should be 1
    return 0 if ResponseMap.find(response.map_id).type != 'ReviewResponseMap'

    due_dates = DueDate.where(parent_id: assignment_id)
    # sorted so that the earliest deadline is at the first
    sorted_deadlines = deadline_sort(due_dates)
    due_dates.reject { |due_date| due_date.deadline_type_id != 1 && due_date.deadline_type_id != 2 }
    round = 1
    sorted_deadlines.each do |due_date|
      break if response.created_at < due_date.due_at

      round += 1 if due_date.deadline_type_id == 2
    end
    round
  end

After refactoring

  def self.calculate_assignment_round(assignment_id, response)
    return 0 unless ResponseMap.find(response.map_id).type == 'ReviewResponseMap'

    due_dates = DueDate.where(parent_id: assignment_id)
    sorted_deadlines = deadline_sort(due_dates)
    determine_assignment_round(response, sorted_deadlines)
  end

  def self.determine_assignment_round(response, sorted_due_dates)
    round = 1
    sorted_due_dates.each do |due_date|
      break if response.created_at < due_date.due_at

      round += 1 if due_date.deadline_type_id == 2
    end
    round
  end

Renamed methods to describe their responsibility

  • Problem: Method names might not have accurately described their functionality, leading to confusion for developers.
  • Solution: Method renaming with descriptive names clarified their purpose, improving code readability and comprehension for developers to understand method functionalities without deep code inspection.

Before Renaming

def self.done_in_assignment_round(assignment_id, response)
..
def self.set_duedate(duedate, deadline, assign_id, max_round)
..
def self.get_next_due_date(assignment_id, topic_id = nil)
def self.calculate_assignment_round(assignment_id, response)
..
def set_due_date(duedate, deadline, assign_id, max_round)
..
def self.find_next_topic_due_date(assignment_id, topic_id)


Fixed code smells using Code Climate

  • Problem: Code Climate identified issues like inefficiencies, redundancies, or style violations, potentially impacting code quality and maintainability.
  • Solution: Addressing Code Climate's feedback resolved potential issues, leading to an optimized, robust, and maintainable codebase aligning with higher standards.

Before refactoring, the same block of code given below was present in due_date_spec.rb and due_date_helper_spec.rb

before(:each) do
    @deadline_type = build(:deadline_type)
    @deadline_right = build(:deadline_right)
    @assignment_due_date = build(:assignment_due_date, deadline_type: @deadline_type,
                                                       submission_allowed_id: @deadline_right.id, review_allowed_id: @deadline_right.id,
                                                       review_of_review_allowed_id: @deadline_right.id, due_at: '2015-12-30 23:30:12')

    @due_dates = []
    10.times.each do |n|
      date = if n == 1 || n == 9
               nil
             else
               Time.zone.now - 60 * n
             end
      @due_dates << build(:assignment_due_date, due_at: date)
    end
  end

After refactoring, we created a new file spec/support/shared_contexts.rb to house the common block of code.

RSpec.shared_context 'with_deadline_setup' do
  before(:each) do
    @deadline_type = build(:deadline_type)
    @deadline_right = build(:deadline_right)
    @assignment_due_date = build(
      :assignment_due_date,
      deadline_type: @deadline_type,
      submission_allowed_id: @deadline_right.id,
      review_allowed_id: @deadline_right.id,
      review_of_review_allowed_id: @deadline_right.id,
      due_at: '2015-12-30 23:30:12'
    )

    @due_dates = []
    10.times.each do |n|
      date = if n == 1 || n == 9
               nil
             else
               Time.zone.now - 60 * n
             end
      @due_dates << build(:assignment_due_date, due_at: date)
    end
  end
end

By recognizing these problems and applying their respective solutions, the new implementation of the `DueDate` class aims to enhance code quality, readability, and maintainability within the Expertiza project.

Tests

Created a new file spec/helpers/due_date_helper_spec.rb to handle all the validation for due_date_helper module. Below are some examples -

require 'support/shared_contexts'

describe DueDateHelper do
  def set_due_date(duedate, deadline, assign_id, max_round)
    ActiveRecord::Base.transaction do
      submit_duedate = DueDate.new(duedate)
      submit_duedate.deadline_type_id = deadline
      submit_duedate.parent_id = assign_id
      submit_duedate.round = max_round
      submit_duedate.save
    end
  end

  include_context 'with_deadline_setup'

  it 'sort duedate records' do
    sorted_due_dates = @due_dates
    expect(sorted_due_dates.each_cons(2).all? { |m1, m2| (m1.due_at <=> m2.due_at) != 1 }).to eql false

    sorted_due_dates = DueDateHelper.deadline_sort(@due_dates)
    expect(sorted_due_dates.each_cons(2).all? { |m1, m2| (m1.due_at <=> m2.due_at) != 1 }).to eql true
  end

  describe '#calculate_assignment_round' do
    it 'return 0 when no response map' do
      response = ReviewResponseMap.create
      response.type = 'ResponseMap'
      response.save
      expect(DueDateHelper.calculate_assignment_round(1, response)).to eql 0
    end

...

References

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