CSC/ECE 517 Spring 2023 -E2347. Reimplement duties controller.rb and badges controller.rb
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:
- Re-implementing CRUD operations for each controller
- Fix bugs and minor flows in the existing code base and functionality
- Removing redundant or unclear code logic
- 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 :-
- Check if the user is authorized to perform the actions on the resource.
- Removed the action_allowed? method.
- As the record is already found, we can remove 'find' from 'update_attributes'.
- Changed the delete action to destroy for consistency with Rails conventions.
- Add a rescue block in set_duty to handle cases when the duty record is not found.
- Display errors in a more user friendly format.
- Remove 'AuthorizationHelper' since we are using 'authorize_resource'.
- Change the parameter fetching of assignment_id to '@duty.assignment_id' as this increases dependency on user input a lot.
- 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/