CSC/ECE 517 Spring 2023 -E2347. Reimplement duties controller.rb and badges controller.rb

From Expertiza_Wiki
Jump to navigation Jump to search

Problem Statement

The problem at hand involves the Duties and Badge modules of Expertiza. Mainly, the task is to improve the code logic and design by following the Object Oriented Design principles and methods and make the code base more efficient, easy to read and structured. We have two controller classes "duties_controller.rb" and "badge_controller.rb".

The duties_controller in the Duties module defines several actions, including create, edit, update, and delete. The create action is used to save the new duty to the database, while the edit action renders the form for editing an existing duty. The update action updates the duty in the database, and the delete action deletes a duty from the database.

On the other hand, the Badge module has a create action that creates a new Badge instance using user parameters and saves an image file if attached. It also updates the image_name attribute of the badge instance. Additionally, there is a new action that sets up a new Badge instance and a redirect_to_assignment method to redirect to the previous page visited by the user. This method is used with the new action to keep track of the page the user was on when they requested a new Badge instance.

The task is to ensure that the Duties and Badge modules function correctly and that the create, edit, update, delete, and new actions all work as intended as well as to optimize, restructure and reimplement the code by following the Object Oriented way. This will help manage duties and badges in Expertiza effectively.

Aim of this Project

The aim of this Project is:

  1. Re-implementing CRUD operations for each controller
  2. Fix bugs and minor flows in the existing code base and functionality
  3. Removing redundant or unclear code logic
  4. Writing RSpec test scripts for both the controllers

By meeting these objectives, both controllers can perform their fundamental create, read, update, and delete operations, while also improving the code's readability and functionality. Additionally, it will aid in addressing any current bugs, thereby enhancing the accuracy of the code.

Design and Implementation

We have given two controllers, duties_controller.rb and badges_controller.rb where we have to reimplement the code using a more object-oriented and efficient way. One potential way to refactor this code in a more object-oriented and efficient manner is to extract responsibilities into smaller objects with clear roles and interfaces. This can help reduce complexity and coupling between different parts of the system, making it easier to maintain and extend over time. Additionally, adopting design patterns and principles like SOLID can help improve the overall quality and modularity of the codebase. Overall, investing time and effort in improving the design and architecture of the code can pay off in the long term by reducing technical debt and enabling faster and more effective development.

After gaining a grasp of the system's flow, we delved into the code within the controllers. Upon initial inspection, it became apparent that the controllers were managing all of the CRUD operations but there were some minor changes that can enhance it and make it more readable and easy to understand. We discerned which functionalities did not pertain to the duties_controler as well as the badges_controller and determined which redundant code should be eliminated.

The MVC architecture is well-suited for object-oriented principles-focused projects or applications because it follows the principles of separation of concerns, modularity, and extensibility. Separating the application into three distinct components, each with a specific responsibility, it allows for greater flexibility in making changes and adding new features without affecting the entire system. Furthermore, the MVC architecture promotes code reuse, since each component can be designed and tested independently. This results in cleaner, more maintainable code and can reduce the overall development time and cost of the project. Overall, the MVC architecture is an effective way to organize and structure complex object-oriented projects or applications, providing a solid foundation for software development that is easily maintainable and scalable.


Our reimplementation project has addressed the following issues:

  • badges_controller.rb: The BadgesController has been re-implemented in a more efficient and object-oriented manner by adhering to the RESTful API for the controller. The controller logic has been split into two separate parts: the Badge model and the BadgesController controller. Previously, all CRUD methods and dependent methods were contained in the same controller class, which went against the principles of the MVC pattern. As part of the changes, the dependent and validation methods have been relocated to the Badge.rb model class, which has made it much easier and more intuitive for coders to work with the code. These changes have already been implemented and are now in effect.

Here is the previous code of the "badges_controller.rb"

class BadgesController < ApplicationController
  include AuthorizationHelper

  def action_allowed?
    current_user_has_ta_privileges?
  end

  def new
    @badge = Badge.new
    session[:return_to] ||= request.referer
  end

  def redirect_to_assignment
    redirect_to session.delete(:return_to)
  end

  def create
    @badge = Badge.new(badge_params)
    image_file = params[:badge][:image_file]
    if image_file
      File.open(Rails.root.join('app', 'assets', 'images', 'badges', image_file.original_filename), 'wb') do |file|
        file.write(image_file.read)
      end
      @badge.image_name = image_file.original_filename
    else
      @badge.image_name = ''
    end

    respond_to do |format|
      if @badge.save
        format.html { redirect_to session.delete(:return_to), notice: 'Badge was successfully created' }
      else
        format.html { render :new }
        format.json { render json: @badge.errors, status: :unprocessable_entity }
      end
    end
  end

  def badge_params
    params.require(:badge).permit(:name, :description, :image_name)
  end
end

Here is how we planned to implement the "Badge.rb" model class:

This model will handle the validations and image upload for the Badge object. The upload_image method can be called from the create action in the BadgesController to save the image to the file system.

class Badge < ApplicationRecord

  def self.upload_image(image_file)
    return '' unless image_file

    image_name = image_file.original_filename
    File.open(Rails.root.join('app', 'assets', 'images', 'badges', image_name), 'wb') do |file|
      file.write(image_file.read)
    end
    image_name
  end
end

Similarly, we planned to re-implement the BadgesController.rb as follows:

This BadgesController now follows the RESTful conventions, which makes it easier to understand and maintain. It also leverages the Badge model to handle the validations and image upload.

class BadgesController < ApplicationController
  before_action :set_badge, only: [:show, :edit, :update, :destroy]
  before_action :set_return_to, only: [:new]

  def index
    @badges = Badge.all
    render json: { badges: @badges }, status: :ok
  end

  def new
    @badge = Badge.new
    render json: { badge: @badge }, status: :ok
  end

  def show
    render json: { badge: @badge }, status: :ok
  end

  def create
    @badge = Badge.new(badge_params)

    if @badge.save
      render json: { badge: @badge }, status: :created
    else
      render json: { errors: @badge.errors.full_messages }, status: :unprocessable_entity
    end
  end

  def update
    if @badge.update(badge_params)
      render json: { badge: @badge }, status: :ok
    else
      render json: { errors: @badge.errors.full_messages }, status: :unprocessable_entity
    end
  end

  def destroy
    @badge.destroy
    render json: { message: "Badge was successfully destroyed." }, status: :ok
  end

  private

  def set_badge
    @badge = Badge.find(params[:id])
  end


  def set_return_to
    session[:return_to] ||= request.referer
  end

  def badge_params
    params.require(:badge).permit(:name, :description, :image_name, :image_file)
  end
end


  • duties_controller.rb: To reimplement the DutiesController in a more modular, reusable and efficient, we can do the following changes :-
  1. Check if the user is authorized to perform the actions on the resource.
  2. Removed the action_allowed? method.
  3. As the record is already found, we can remove 'find' from 'update_attributes'.
  4. Changed the delete action to destroy for consistency with Rails conventions.
  5. Add a rescue block in set_duty to handle cases when the duty record is not found.
  6. Display errors in a more user friendly format.
  7. Remove 'AuthorizationHelper' since we are using 'authorize_resource'.
  8. Change the parameter fetching of assignment_id to '@duty.assignment_id' as this increases dependency on user input a lot.
  9. Removing unnecessary semicolons.

Here is the previous implementation of the duties_controller.rb


The class diagram below explains how the classes and the objects interact with each other. Also, it gives a brief idea about the implementation of the operations implemented in the Controllers.

Test Methodology

Testing with Swagger UI

As part of our testing process, we have implemented Swagger UI automated testing to thoroughly test the DutiesController and BadgesController, as well as the Duty and Badge model classes. Swagger UI is a tool that allows us to test each API endpoint by sending HTTP requests and receiving responses.

To ensure that each endpoint is working correctly, we have created test cases for each route of the DutiesController and BadgesController. For each test case, we make a valid request with the correct parameters and verify that the expected response is obtained. This helps us ensure that each endpoint is capable of processing requests and returning the correct output.

In addition to testing valid requests, we have also tested for invalid requests. This involves submitting requests with an incorrect number of parameters, invalid parameter types, and values that do not meet the validation requirements of the endpoint. When testing invalid requests, we expect the endpoint to return a status 422 Invalid Request as the response. By testing invalid requests, we can ensure that each endpoint is capable of handling unexpected inputs and responding appropriately.

By employing Swagger UI automated testing, we have been able to thoroughly test each API endpoint and ensure that they are capable of handling both valid and invalid requests. This helps us ensure that our API functions as intended and that any issues are caught early in the development process. Here are some snapshots of the API testing done using Swagger:

Testing with RSpec

As part of our testing process, we have implemented RSpec test cases to thoroughly test our code base. Using RSpec allows us to write tests that are independent of any external dependencies and can be easily run to validate the functionality of our code. With RSpec, we have the ability to write unit tests that focus on individual pieces of functionality and integration tests that test the interactions between different components. By utilizing RSpec, we have greater confidence in the correctness of our code and can quickly identify any issues that may arise as changes are made. We have already employed RSpec to ensure that our DutiesController, BadgesController, Duty, and Badge model classes are thoroughly tested and free of errors.

Specifically, for the BadgesController and DutiesController, we have written test cases to thoroughly test the CRUD operations and their associated functionalities. We have tested API functionalities by writing test cases for the get, patch, and post API calls. For example, we can write a test case to ensure that the controller behaves as intended when a user attempts to update a badge. This involves verifying that the update request is received by the controller, the badge is updated in the database with the correct information, and the updated badge is returned in the response. By writing test cases in this manner, we can ensure that our API functions as intended and any issues are caught early in the development process.


Given below is a example of an RSpec test case for BadgesController and its operations:

  • Sample test cases for BadgesController:
RSpec.describe BadgesController, type: :controller do
  describe "GET #index" do
    it "returns a successful response" do
      get :index
      expect(response).to be_successful
    end

    it "assigns all badges to @badges" do
      badge = Badge.create(name: "Test Badge", description: "This is a test badge")
      get :index
      expect(assigns(:badges)).to eq([badge])
    end
  end
 describe "POST #create" do
    context "with valid attributes" do
      it "creates a new badge" do
        expect {
          post :create, params: { badge: { name: "Test Badge", description: "This is a test badge", image_file: 
      fixture_file_upload(Rails.root.join('spec', 'fixtures', 'badge.png'), 'image/png') } }
        }.to change(Badge, :count).by(1)
      end

      it "redirects to the badges index page" do
        post :create, params: { badge: { name: "Test Badge", description: "This is a test badge", image_file: 
      fixture_file_upload(Rails.root.join('spec', 'fixtures', 'badge.png'), 'image/png') } }
        expect(response).to redirect_to(badges_url)
      end
    end

    context "with invalid attributes" do
      it "does not create a new badge" do
        expect {
          post :create, params: { badge: { name: "", description: "This is a test badge", image_file: fixture_file_upload(Rails.root.join('spec', 
       'fixtures', 'badge.png'), 'image/png') } }
        }.not_to change(Badge, :count)
      end

      it "renders the new template" do
        post :create, params: { badge: { name: "", description: "This is a test badge", image_file: fixture_file_upload(Rails.root.join('spec', 
       'fixtures', 'badge.png'), 'image/png') } }
        expect(response).to render_template(:new)
      end
    end
  end

Contributors

Sanket Tangade(sstangad@ncsu.edu)
Kunal Patil(kpatil5@ncsu.edu)
Yash Sonar(ysonar@ncsu.edu)

Mentor:
Kartiki Bhandakkar(kbhanda3@ncsu.edu)

References

https://vahid.blog/post/2021-04-16-understanding-the-model-view-controller-mvc-pattern/