CSC/ECE 517 Fall 2024 - E2477. Reimplement suggestion controller.rb (Design Document)
Problem Statement
The suggestion_controller.rb in Expertiza is responsible for managing all operations related to submitting and approving suggestions for new project topics proposed by students or teams. However, this controller currently violates the Single Responsibility Principle by directly interacting with members of other Model classes. To enhance maintainability and clarity, this functionality should be appropriately distributed among dedicated classes. Along with this, it needs to be updated to follow the new back end, which is being implemented using json and api paths.
Design Goal
- Enhance Documentation: Add comprehensive comments throughout the controller, clearly explaining the purpose and functionality of custom methods
- Reimplement Notification Method: Simplify the notification method by refining its control logic and reducing complexity for better readability and efficiency. Additionally, "notification" is a poor name; it should be renamed to a verb that clearly indicates the action being performed
- Clarify Method Names:
- Rename reject_suggestion to reject for clarity
- Rename update_suggestion to update to better reflect its functionality
- Rename approve_suggestion to approve for clarity
- Refactor Email Sending Logic: Move the send_email method to the Mailer class located in app/mailers/mailer.rb to separate concerns and streamline the code
- Address DRY Violations: In views/suggestion/show.html.erb and views/suggestion/student_view.html.erb, identify and resolve the DRY (Don't Repeat Yourself) violation by merging these two files into a single view to enhance maintainability
- Update Tests: Revise existing tests to ensure they pass after the changes. Additionally, improve test coverage for the controller by adding new tests to verify the updated functionality
- During the reimplementation, review the structure and functionality of existing suggestion-related classes for insights and best practices. Remember, this is a complete reimplementation, not a refactor
Since this is a reimplementation project, some functionalities may be altered. Ensure that all changes are properly tested and that existing tests are updated to reflect these modifications.
Class Diagram
Model associations
In the diagram, the grayed out models are only read by the controller, and never created, updated, or deleted.
Controller and collaborators
Solutions/Details of Changes Made
Enhance Documentation
This is a pending change. Comments to the controller, helpers, and spec file have not been added or revised yet.
Reimplement Notification Method
The original method is hard to follow due to its nested if-else blocks and performing of multiple tasks. Additionally, the related functionality is split among all methods that are part of the approval process, making the final outcome difficult to follow.
The entire approval process has been reimplemented to create logical segmentation and easy to follow and understand methods.
- 'approve_suggestion' is now 'approve', and performs the four high-level steps required:
- Update the suggestion's status to 'Approved'
- Create a topic entry in the database from the suggested topic
- Sign the suggester and his team up for the newly created topic
- Send out an email informing all teammates that the suggested topic was approved
- 'create_topic_from_suggestion!' is simply a wrapper method which creates a new record of the SignUpTopic model. It exists to keep the overarching 'approve' method short
- 'sign_team_up_to_assignment_and_topic!' performs the necessary steps to signing the student and his team up for the newly created topic. It creates a new team if necessary and de-registers any waitlisted assignments that the team might have
- 'send_notice_of_approval!' sends out the notification email informing all team members that their topic has been approved
Clarify Method Names
Each method has been examined for relevance and conciseness. Most methods are renamed to standard rails controller methods, or now exhibit rails naming convention. These would be:
Old method name | Action | New method name |
---|---|---|
action_allowed? | removed | - |
add_comment | no change | add_comment |
list | renamed | index |
student_view | removed | - |
student_edit | removed | - |
show | no change | show |
update_suggestion | renamed | update |
new | removed | - |
create | no change | create |
submit | removed | - |
send_email | renamed | send_notice_of_approval! |
notification | renamed | sign_team_up_to_assignment_and_topic! |
approve_suggestion | merged into approve | approve |
reject_suggestion | renamed | reject |
suggestion_params | removed | - |
- | added | destroy |
- | added | create_topic_from_suggestion! |
Refactor Email Sending Logic
The original implementation first constructs a list of recipients iteratively. This adds to the length of the method, but also causes and increased number of database queries, slowing the program down.
In the reimplemented version, the code that finds the recipients' emails has been compacted into a single query chain, and thus inlined into the call to the Mailer class. The act of sending the email is moved into the Mailer helper class to separate concerns and streamline the code.
Address DRY Violations
Since this project is to reimplement the controller as an API which works with JSON only, there are no view files and thus this objective does not require any action. It is perhaps a holdover from before the decision to split Expertiza into frontend and backend.
Update Tests
Currently, this task is being worked on and is not complete yet.
Files Added/Modified
Models:
Old | New |
---|---|
class Suggestion < ApplicationRecord
validates :title, :description, presence: true
has_many :suggestion_comments
end
|
class Suggestion < ApplicationRecord
has_many :suggestion_comments, dependent: :delete_all
validates :title, uniqueness: { case_sensitive: false }
validates :description, presence: true
end
|
Old | New |
---|---|
class SuggestionComment < ApplicationRecord
validates :comments, presence: true
belongs_to :suggestion
end
|
class SuggestionComment < ApplicationRecord
belongs_to :suggestion
belongs_to :user
validates :comment, presence: true
end
|
class CreateSuggestions < ActiveRecord::Migration[7.0]
def change
create_table :suggestions do |t|
t.string :title
t.text :description
t.string :status
t.boolean :auto_signup
t.references :assignment, null: false, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
end
class CreateSuggestionComments < ActiveRecord::Migration[7.0]
def change
create_table :suggestion_comments do |t|
t.text :comment
t.references :suggestion, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
The models and accompanying migrations form the basis of the reimplementation. The model files were updated to improve their attribute validation and fix some bugs with the model associations. The 'comments' field was removed from the 'Suggestion' model since the 'SuggestionComment' model exists. As a result of the migrations, 'schema.rb' is changed, but excluded from the above list since it is an auto-generated file.
Controller:
resources :suggestions do
collection do
post :add_comment
post :approve
post :reject
end
end
Old | New |
---|---|
class SuggestionController < ApplicationController
include AuthorizationHelper
def action_allowed?
case params[:action]
when 'create', 'new', 'student_view', 'student_edit', 'update_suggestion', 'submit'
current_user_has_student_privileges?
else
current_user_has_ta_privileges?
end
end
def add_comment
@suggestion_comment = SuggestionComment.new(vote: params[:suggestion_comment][:vote], comments: params[:suggestion_comment][:comments])
@suggestion_comment.suggestion_id = params[:id]
@suggestion_comment.commenter = session[:user].name
if @suggestion_comment.save
flash[:notice] = 'Your comment has been successfully added.'
else
flash[:error] = 'There was an error in adding your comment.'
end
if current_user_has_student_privileges?
redirect_to action: 'student_view', id: params[:id]
else
redirect_to action: 'show', id: params[:id]
end
end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify method: :post, only: %i[destroy create update],
redirect_to: { action: :list }
def list
@suggestions = Suggestion.where(assignment_id: params[:id])
@assignment = Assignment.find(params[:id])
end
def student_view
@suggestion = Suggestion.find(params[:id])
end
def student_edit
@suggestion = Suggestion.find(params[:id])
end
def show
@suggestion = Suggestion.find(params[:id])
end
def update_suggestion
Suggestion.find(params[:id]).update_attributes(title: params[:suggestion][:title],
description: params[:suggestion][:description],
signup_preference: params[:suggestion][:signup_preference])
redirect_to action: 'new', id: Suggestion.find(params[:id]).assignment_id
end
def new
@suggestion = Suggestion.new
session[:assignment_id] = params[:id]
@suggestions = Suggestion.where(unityID: session[:user].name, assignment_id: params[:id])
@assignment = Assignment.find(params[:id])
end
def create
@suggestion = Suggestion.new(suggestion_params)
@suggestion.assignment_id = session[:assignment_id]
@assignment = Assignment.find(session[:assignment_id])
@suggestion.status = 'Initiated'
@suggestion.unityID = if params[:suggestion_anonymous].nil?
session[:user].name
else
''
end
if @suggestion.save
flash[:success] = 'Thank you for your suggestion!' unless @suggestion.unityID.empty?
flash[:success] = 'You have submitted an anonymous suggestion. It will not show in the suggested topic table below.' if @suggestion.unityID.empty?
end
redirect_to action: 'new', id: @suggestion.assignment_id
end
def submit
if !params[:add_comment].nil?
add_comment
elsif !params[:approve_suggestion].nil?
approve_suggestion
elsif !params[:reject_suggestion].nil?
reject_suggestion
end
end
# If the user submits a suggestion and gets it approved -> Send email
# If user submits a suggestion anonymously and it gets approved -> DOES NOT get an email
def send_email
proposer = User.find_by(id: @user_id)
if proposer
teams_users = TeamsUser.where(team_id: @team_id)
cc_mail_list = []
teams_users.each do |teams_user|
cc_mail_list << User.find(teams_user.user_id).email if teams_user.user_id != proposer.id
end
Mailer.suggested_topic_approved_message(
to: proposer.email,
cc: cc_mail_list,
subject: "Suggested topic '#{@suggestion.title}' has been approved",
body: {
approved_topic_name: @suggestion.title,
proposer: proposer.name
}
).deliver_now!
end
end
def notification
if @suggestion.signup_preference == 'Y'
if @team_id.nil?
new_team = AssignmentTeam.create(name: 'Team_' + rand(10_000).to_s,
parent_id: @signuptopic.assignment_id, type: 'AssignmentTeam')
new_team.create_new_team(@user_id, @signuptopic)
else
if @topic_id.nil?
# clean waitlists
SignedUpTeam.where(team_id: @team_id, is_waitlisted: 1).destroy_all
SignedUpTeam.create(topic_id: @signuptopic.id, team_id: @team_id, is_waitlisted: 0)
else
@signuptopic.private_to = @user_id
@signuptopic.save
# if this team has topic, Expertiza will send an email (suggested_topic_approved_message) to this team
send_email
end
end
else
# if this team has topic, Expertiza will send an email (suggested_topic_approved_message) to this team
send_email
end
end
def approve_suggestion
approve
notification
redirect_to action: 'show', id: @suggestion
end
def reject_suggestion
@suggestion = Suggestion.find(params[:id])
if @suggestion.update_attribute('status', 'Rejected')
flash[:notice] = 'The suggestion has been successfully rejected.'
else
flash[:error] = 'An error occurred when rejecting the suggestion.'
end
redirect_to action: 'show', id: @suggestion
end
private
def suggestion_params
params.require(:suggestion).permit(:assignment_id, :title, :description,
:status, :unityID, :signup_preference)
end
def approve
@suggestion = Suggestion.find(params[:id])
@user_id = User.find_by(name: @suggestion.unityID).try(:id)
if @user_id
@team_id = TeamsUser.team_id(@suggestion.assignment_id, @user_id)
@topic_id = SignedUpTeam.topic_id(@suggestion.assignment_id, @user_id)
end
# After getting topic from user/team, get the suggestion
@signuptopic = SignUpTopic.new_topic_from_suggestion(@suggestion)
# Get success only if the signuptopic object was returned from its class
if @signuptopic != 'failed'
flash[:success] = 'The suggestion was successfully approved.'
else
flash[:error] = 'An error occurred when approving the suggestion.'
end
end
end
|
class Api::V1::SuggestionsController < ApplicationController
include PrivilegeHelper
def add_comment
render json: SuggestionComment.create!(
comment: params[:comment],
suggestion_id: params[:id],
user_id: @current_user.id
), status: :ok
rescue ActiveRecord::RecordInvalid => e
render json: e.record.errors, status: :unprocessable_entity
end
def approve
if PrivilegeHelper.current_user_has_ta_privileges?
transaction do
@suggestion = Suggestion.find(params[:id])
@suggestion.update_attribute('status', 'Approved')
create_topic_from_suggestion!
unless @suggestion.user_id.nil?
@suggester = User.find(@suggestion.user_id)
sign_team_up_to_assignment_and_topic!
send_notice_of_approval!
end
render json: @suggestion, status: :ok
end
else
render json: { error: 'Students cannot approve a suggestion.' }, status: :forbidden
end
rescue ActiveRecord::RecordNotFound => e
render json: e, status: :not_found
rescue ActiveRecord::RecordInvalid => e
render json: e.record.errors, status: :unprocessable_entity
end
def create
render json: Suggestion.create!(
title: params[:title],
description: params[:description],
status: 'Initialized',
auto_signup: params[:auto_signup],
assignment_id: params[:assignment_id],
user_id: params[:suggestion_anonymous] ? nil : @current_user.id
), status: :ok
rescue ActiveRecord::RecordInvalid => e
render json: e.record.errors, status: :unprocessable_entity
end
def destroy
if PrivilegeHelper.current_user_has_ta_privileges?
Suggestion.find(params[:id]).destroy!
render json: {}, status: :ok
else
render json: { error: 'Students do not have permission to delete suggestions.' }, status: :forbidden
end
rescue ActiveRecord::RecordNotFound => e
render json: e, status: :not_found
rescue ActiveRecord::RecordNotDestroyed => e
render json: e, status: :unprocessable_entity
end
def index
if PrivilegeHelper.current_user_has_ta_privileges?
render json: Suggestion.where(assignment_id: params[:id]), status: :ok
else
render json: { error: 'Students do not have permission to view all suggestions.' }, status: :forbidden
end
end
def reject
if PrivilegeHelper.current_user_has_ta_privileges?
suggestion = Suggestion.find(params[:id])
if suggestion.status == 'Initialized'
suggestion.update_attribute('status', 'Rejected')
render json: suggestion, status: :ok
else
render json: { error: 'Suggestion has already been approved or rejected.' }, status: :unprocessable_entity
end
else
render json: { error: 'Students cannot reject a suggestion.' }, status: :forbidden
end
rescue ActiveRecord::RecordNotFound => e
render json: e, status: :not_found
end
def show
@suggestion = Suggestion.find(params[:id])
puts @suggestion.user_id
puts @current_user.id
if @suggestion.user_id == @current_user.id || PrivilegeHelper.current_user_has_ta_privileges?
render json: {
suggestion: @suggestion,
comments: SuggestionComment.where(suggestion_id: params[:id])
}, status: :ok
else
render json: { error: 'Students can only view their own suggestions.' }, status: :forbidden
end
rescue ActiveRecord::RecordNotFound => e
render json: e, status: :not_found
end
private
def create_topic_from_suggestion!
@signuptopic = SignUpTopic.create!(
topic_identifier: "S#{Suggestion.where(assignment_id: @suggestion.assignment_id).count}",
topic_name: @suggestion.title,
assignment_id: @suggestion.assignment_id,
max_choosers: 1
)
end
def send_notice_of_approval!
Mailer.send_topic_approved_message(
to: @suggester.email,
cc: User.joins(:teams_users).where(teams_users: { team_id: @team.id }).where.not(id: @suggester.id).map(&:email),
subject: "Suggested topic '#{@suggestion.title}' has been approved",
body: {
approved_topic_name: @suggestion.title,
suggester: @suggester.name
}
)
end
def sign_team_up_to_assignment_and_topic!
return unless @suggestion.auto_signup == true
@team = Team.where(assignment_id: @signuptopic.assignment_id).joins(:teams_user)
.where(teams_user: { user_id: @suggester.id }).first
if @team.nil?
@team = Team.create!(assignment_id: @signuptopic.assignment_id)
TeamsUser.create!(team_id: @team.id, user_id: @suggester.id)
end
if SignedUpTeam.exists?(sign_up_topic_id: @signuptopic.id, team_id: @team.id, is_waitlisted: false)
SignedUpTeam.where(team_id: @team.id, is_waitlisted: 1).destroy_all
SignedUpTeam.create!(sign_up_topic_id: @signuptopic.id, team_id: @team.id, is_waitlisted: false)
end
@signuptopic.update_attribute(:private_to, @suggester.id)
end
end
|
The controller defines the behavior around suggestions and suggestion comments. It has been completely reimplemented to follow API convention and streamline the various endpoints that the frontend will interact with. Notable changes are:
- Responses are in JSON instead of HTML
- Methods have proper error handling
- Public method names have been changed to standard Rails controller methods or method naming conventions
- Private method names describe their public side effects
- 'approve', 'notification', and 'send_email' are re-split into 'create_topic_from_suggestion!', 'send_notice_of_approval!', and 'sign_team_up_to_assignment_and_topic!' methods to improve logical code segmentation
- 'submit' method removed entirely as it is a "do-this-or-that" method; instead, the sub-actions are called directly
- The method that sends the email is simplified to a single call to the Mailer class and now uses a Rails query chain to reduce the number of database queries
- Each controller method uses a call to PrivilegeHelper instead of using the now deprecated 'allow_action?' method
Helpers:
class Mailer < ActionMailer::Base
default from: 'expertiza.mailer@gmail.com'
def send_topic_approved_message(defn)
@body = defn[:body]
@topic_name = defn[:body][:approved_topic_name]
@proposer = defn[:body][:proposer]
defn[:to] = 'expertiza.mailer@gmail.com' if Rails.env.development? || Rails.env.test?
mail(subject: defn[:subject], to: defn[:to], bcc: defn[:cc]).deliver_now!
end
end
Old (authorization_helper.rb) | New (privilege_helper.rb) |
---|---|
module AuthorizationHelper
# Notes:
# We use session directly instead of current_role_name and the like
# Because helpers do not seem to have access to the methods defined in app/controllers/application_controller.rb
# PUBLIC METHODS
# Determine if the currently logged-in user has the privileges of a Super-Admin
def current_user_has_super_admin_privileges?
current_user_has_privileges_of?('Super-Administrator')
end
# Determine if the currently logged-in user has the privileges of an Admin (or higher)
def current_user_has_admin_privileges?
current_user_has_privileges_of?('Administrator')
end
# Determine if the currently logged-in user has the privileges of an Instructor (or higher)
def current_user_has_instructor_privileges?
current_user_has_privileges_of?('Instructor')
end
# Determine if the currently logged-in user has the privileges of a TA (or higher)
def current_user_has_ta_privileges?
current_user_has_privileges_of?('Teaching Assistant')
end
# Determine if the currently logged-in user has the privileges of a Student (or higher)
def current_user_has_student_privileges?
current_user_has_privileges_of?('Student')
end
# ...
# Determine if the currently logged-in user has the privileges of the given role name (or higher privileges)
# Let the Role model define this logic for the sake of DRY
# If there is no currently logged-in user simply return false
def current_user_has_privileges_of?(role_name)
current_user_and_role_exist? && session[:user].role.has_all_privileges_of?(Role.find_by(name: role_name))
end
# ...
def current_user_and_role_exist?
user_logged_in? && !session[:user].role.nil?
end
end
|
module PrivilegeHelper
# Determine if the currently logged-in user has the privileges of a Super-Admin
def self.current_user_has_super_admin_privileges?
current_user_has_privileges_of?('Super-Administrator')
end
# Determine if the currently logged-in user has the privileges of an Admin (or higher)
def self.current_user_has_admin_privileges?
current_user_has_privileges_of?('Administrator')
end
# Determine if the currently logged-in user has the privileges of an Instructor (or higher)
def self.current_user_has_instructor_privileges?
current_user_has_privileges_of?('Instructor')
end
# Determine if the currently logged-in user has the privileges of a TA (or higher)
def self.current_user_has_ta_privileges?
current_user_has_privileges_of?('Teaching Assistant')
end
# Determine if the currently logged-in user has the privileges of a Student (or higher)
def self.current_user_has_student_privileges?
current_user_has_privileges_of?('Student')
end
# Determine if the currently logged-in user has the privileges of the given role name (or higher privileges)
# Let the Role model define this logic for the sake of DRY
# If there is no currently logged-in user simply return false
def self.current_user_has_privileges_of?(role_name)
current_user_and_role_exist? && @current_user.role.all_privileges_of?(Role.find_by(name: role_name))
end
def self.current_user_and_role_exist?
!@current_user.nil? && !@current_user.role.nil?
end
end
|
The helpers exist for the single responsibility principle, and to allow common features to be defined in one place and usable everywhere. The two helpers that are implemented are:
- The mailer helper, which is responsible for sending emails
- The privilege helper, which helps determine the permissions level of the current user
Controller spec:
The controller spec file is part of the test plan and explained in the next section of this document.
Test Plan
Update suggestions_controller test file, to test the changed file, follow the change to api format, and use RSpec testing.
Detailed Test Plan
1. Method: show
* Only shows when user is authorized to view the specified suggestion * Does not show when user is unauthorized
2. Method: add_comment
* adds a comment and then returns showing said comment * returns an error when comment creation fails
3. Method: approve
* approves the suggestion and shows updated suggestion if user is authorized to do so * returns a forbidden error when user is unauthorized to approve suggestions
4. Method: reject
* rejects the suggestion and returns to suggestion when user is authorized * returns a forbidden error when user is unauthorized to reject suggestions
5. Method: edit
* edits suggestion and shows updated suggestion when authorized to edit the suggestion * returns forbidden error when user is authorized
6. Method: create
* creates suggestion if given suggestion data is valid, otherwise return an error
7. Method: destroy
* deletes the suggestion if user has the permissions to delete suggestions
8. Method: index
* show all suggestions if user has the privileges otherwise returns forbidden error
Current Spec file
require 'swagger_helper'
require 'rails_helper'
def auth_token_header(user)
post '/login', params: { user_name: user.name, password: 'password' }
{ Authorization: "Bearer #{JSON.parse(response.body)['token']}" }
end
RSpec.describe 'Suggestions API', type: :request do
let(:instructor) { instance_double(User, id: 6, role: 'instructor') }
let(:student) { instance_double(User, id: 1, name: 'student_user', role: 'student', password: 'password') }
let(:assignment) { instance_double(Assignment, id: 1, instructor:) }
let(:suggestion) do
instance_double(Suggestion, id: 1, assignment_id: assignment.id, user_id: student.id, title: 'Test Title')
end
let(:suggestion_comment) { instance_double(SuggestionComment) }
def stub_current_user(user)
allow_any_instance_of(ApplicationController).to receive(:session).and_return({ user_id: user.id })
end
before(:each) do
allow(Assignment).to receive(:find).with(assignment.id.to_s).and_return(assignment)
allow(Suggestion).to receive(:find).with(suggestion.id.to_s).and_return(suggestion)
stub_current_user(student)
end
describe '#show' do
context 'when user is authorized' do
it 'returns suggestion details and comments' do
student_institution = Institution.create!(name: 'North Carolina State University')
student_role = Role.create!(name: 'Student')
student_user = User.create!(name: 'student_user', password: 'password', email: 'example@gmail.com',
full_name: 'Student User', role: student_role, institution: student_institution)
student_suggestion = Suggestion.create!(title: 'Sample suggestion', description: 'Sample Text',
status: 'Initialized', auto_signup: false, user_id: student_user)
get "/api/v1/suggestions/#{student_suggestion.id}", headers: auth_token_header(student_user)
expect(response).to have_http_status(:ok)
parsed_response = JSON.parse(response.body)
puts parsed_response
expect(parsed_response['suggestion']['id']).to eq(student_suggestion.id)
expect(parsed_response['comments']).to be_an(Array)
end
end
context 'when user is unauthorized' do
it 'returns a forbidden error' do
stub_current_user(instance_double(User, id: 2, role: 'student'))
get "/api/v1/suggestions/#{suggestion.id}"
expect(response).to have_http_status(:forbidden)
parsed_response = JSON.parse(response.body)
expect(parsed_response['error']).to eq('Students can only view their own suggestions.')
end
end
end
describe '#add_comment' do
it 'adds a comment and returns the comment JSON' do
allow(SuggestionComment).to receive(:create!).and_return(suggestion_comment)
allow(suggestion_comment).to receive(:as_json).and_return({ comment: 'Test comment' })
stub_current_user(student, 'student', student.role)
post "/api/v1/suggestions/#{suggestion.id}/comments", params: { comment: 'Test comment' }
expect(response).to have_http_status(:ok)
parsed_response = JSON.parse(response.body)
expect(parsed_response['comment']).to eq('Test comment')
end
it 'returns an error when comment creation fails' do
allow(SuggestionComment).to receive(:create!).and_raise(ActiveRecord::RecordInvalid.new(suggestion_comment))
stub_current_user(student, 'student', student.role)
post "/api/v1/suggestions/#{suggestion.id}/comments", params: { comment: '' }
expect(response).to have_http_status(:unprocessable_entity)
parsed_response = JSON.parse(response.body)
expect(parsed_response).to include('errors')
end
end
describe '#approve' do
context 'when user is authorized' do
it 'approves the suggestion and returns the updated suggestion JSON' do
allow(suggestion).to receive(:update_attribute).with('status', 'Approved').and_return(true)
stub_current_user(instructor, 'instructor', instructor.role)
post "/api/v1/suggestions/#{suggestion.id}/approve"
expect(response).to have_http_status(:ok)
parsed_response = JSON.parse(response.body)
expect(parsed_response['status']).to eq('Approved')
end
end
context 'when user is unauthorized' do
it 'returns a forbidden error' do
stub_current_user(student, 'student', student.role)
post "/api/v1/suggestions/#{suggestion.id}/approve"
expect(response).to have_http_status(:forbidden)
parsed_response = JSON.parse(response.body)
expect(parsed_response['error']).to eq('Students cannot approve a suggestion.')
end
end
end
describe '#reject' do
context 'when user is authorized' do
it 'rejects the suggestion and returns the updated suggestion JSON' do
allow(suggestion).to receive(:update_attribute).with('status', 'Rejected').and_return(true)
stub_current_user(instructor, 'instructor', instructor.role)
post "/api/v1/suggestions/#{suggestion.id}/reject"
expect(response).to have_http_status(:ok)
parsed_response = JSON.parse(response.body)
expect(parsed_response['status']).to eq('Rejected')
end
end
context 'when user is unauthorized' do
it 'returns a forbidden error' do
stub_current_user(student, 'student', student.role)
post "/api/v1/suggestions/#{suggestion.id}/reject"
expect(response).to have_http_status(:forbidden)
parsed_response = JSON.parse(response.body)
expect(parsed_response['error']).to eq('Students cannot reject a suggestion.')
end
end
end
end
Next Steps
- Add comments into the code to improve code documentation
- Update test coverage to cover larger amounts of code
- Add missing associations to the Suggestion model
- Add mailer email template
Team
Mentor
- Piyush Prasad (pprasad3@ncsu.edu)
Members
- Anthony Spendlove (aspendl@ncsu.edu)
- Sean McLellan (spmclell@ncsu.edu)
- Dhananjay Raghu (draghu@ncsu.edu)
References
External Links
- Project repo
- Project board
- [# Pull request]