Skip to content

Commit

Permalink
Add Grading API (#1482)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nitish145 committed Jun 25, 2020
1 parent 341dbbd commit ef8590c
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 1 deletion.
52 changes: 52 additions & 0 deletions app/controllers/api/v1/grades_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

class Api::V1::GradesController < Api::V1::BaseController
include ActionView::Helpers::SanitizeHelper

before_action :authenticate_user!
before_action :load_create_resources, only: %i[create]
before_action :set_grade, only: %i[update destroy]
before_action :check_access

def create
@grade.user_id = @current_user.id
@grade.grade = grade_params[:grade]
@grade.remarks = sanitize grade_params[:remarks]

if @grade.save
render json: Api::V1::GradeSerializer.new(@grade), status: :created
else
api_error(status: 422, errors: @grade.errors)
end
end

def update
@grade.update!(grade_params)
render json: Api::V1::GradeSerializer.new(@grade), status: :accepted
end

def destroy
@grade.destroy!
render json: {}, status: :no_content
end

private

def load_create_resources
@grade = Grade.new(
assignment_id: params[:assignment_id], project_id: params[:project_id]
)
end

def set_grade
@grade = Grade.find(params[:id])
end

def check_access
authorize @grade, :mentor?
end

def grade_params
params.require(:grade).permit(:grade, :remarks)
end
end
2 changes: 1 addition & 1 deletion app/models/grade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Grade < ApplicationRecord

validates :grade, :user_id, :project_id, :assignment_id, presence: true
validate :grading_scale, :assignment_project
validates :project_id, uniqueness: { scope: :assignment_id }

private

Expand Down Expand Up @@ -46,7 +47,6 @@ def self.to_csv(assignment_id)
group_members.each do |member|
submission = submissions.find do |s| (


s.author_id == member.id &&
s.assignment_id == assignment_id)
end
Expand Down
7 changes: 7 additions & 0 deletions app/serializers/api/v1/grade_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class Api::V1::GradeSerializer
include FastJsonapi::ObjectSerializer

attributes :grade, :remarks, :created_at, :updated_at
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
resources :users do
get 'projects', to: 'projects#user_projects', on: :member
end
post '/assignments/:assignment_id/projects/:project_id/grades', to: 'grades#create'
resources :grades, only: [:update, :destroy]
end
end
end
7 changes: 7 additions & 0 deletions db/migrate/20200604034729_add_unique_grade_validation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddUniqueGradeValidation < ActiveRecord::Migration[6.0]
def change
add_index :grades, %i[project_id assignment_id], unique: true
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
t.bigint "assignment_id"
t.string "remarks"
t.index ["assignment_id"], name: "index_grades_on_assignment_id"
t.index ["project_id", "assignment_id"], name: "index_grades_on_project_id_and_assignment_id", unique: true
t.index ["project_id"], name: "index_grades_on_project_id"
t.index ["user_id"], name: "index_grades_on_user_id"
end
Expand Down
116 changes: 116 additions & 0 deletions spec/requests/api/v1/grades_controller/create_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Api::V1::GradesController, "#create", type: :request do
describe "create a grade" do
let!(:mentor) { FactoryBot.create(:user) }
let!(:group) { FactoryBot.create(:group, mentor: mentor) }
let!(:assignment) { FactoryBot.create(:assignment, group: group, grading_scale: :letter) }
let!(:project) { FactoryBot.create(:project, assignment: assignment) }

context "when not authenticated" do
before do
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades", as: :json
end

it "returns status unauthenticated" do
expect(response).to have_http_status(401)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when not authorized to grade assignment" do
before do
token = get_auth_token(FactoryBot.create(:user))
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades",
headers: { "Authorization": "Token #{token}" },
params: create_params, as: :json
end

it "returns status unauthorized" do
expect(response).to have_http_status(403)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized but tries to create grade with invalid params" do
before do
token = get_auth_token(mentor)
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades",
headers: { "Authorization": "Token #{token}" },
params: { "invalid": "invalid" }, as: :json
end

it "returns status bad_request" do
expect(response).to have_http_status(400)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized but tries to create duplicate grade" do
before do
FactoryBot.create(
:grade, project: project, assignment: assignment, \
user_id: mentor.id, grade: "A", remarks: "Good"
)
token = get_auth_token(mentor)
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades",
headers: { "Authorization": "Token #{token}" },
params: create_params, as: :json
end

it "returns status unprocessable_identity" do
expect(response).to have_http_status(422)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized but tries to create grade with different grading scale" do
before do
token = get_auth_token(mentor)
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades",
headers: { "Authorization": "Token #{token}" },
params: invalid_grading_scale_params, as: :json
end

it "returns status unprocessable_identity" do
expect(response).to have_http_status(422)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized to grade an assignment" do
before do
token = get_auth_token(mentor)
post "/api/v1/assignments/#{assignment.id}/projects/#{project.id}/grades",
headers: { "Authorization": "Token #{token}" },
params: create_params, as: :json
end

it "returns status created & grade details" do
expect(response).to have_http_status(201)
expect(response).to match_response_schema("grade")
expect(response.parsed_body["data"]["attributes"]["grade"]).to eq("A")
end
end

def create_params
{
"grade": {
"grade": "A",
"remarks": "Nice Work"
}
}
end

def invalid_grading_scale_params
{
"grade": {
"grade": 100,
"remarks": "Nice Work"
}
}
end
end
end
70 changes: 70 additions & 0 deletions spec/requests/api/v1/grades_controller/destroy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Api::V1::GradesController, "#destroy", type: :request do
describe "delete specific grade" do
let!(:mentor) { FactoryBot.create(:user) }
let!(:group) { FactoryBot.create(:group, mentor: mentor) }
let!(:assignment) { FactoryBot.create(:assignment, group: group, grading_scale: :letter) }
let!(:project) { FactoryBot.create(:project, assignment: assignment) }
let!(:grade) do
FactoryBot.create(
:grade, project: project, assignment: assignment, \
user_id: mentor.id, grade: "A", remarks: "Good"
)
end

context "when not authenticated" do
before do
delete "/api/v1/grades/#{grade.id}", as: :json
end

it "returns status unauthenticated" do
expect(response).to have_http_status(401)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authenticated as random user and don't have delete access" do
before do
token = get_auth_token(FactoryBot.create(:user))
delete "/api/v1/grades/#{grade.id}",
headers: { "Authorization": "Token #{token}" }, as: :json
end

it "returns status unauthorized" do
expect(response).to have_http_status(403)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized but tries to delete non existent grade" do
before do
token = get_auth_token(mentor)
delete "/api/v1/grades/0",
headers: { "Authorization": "Token #{token}" }, as: :json
end

it "returns status not_found" do
expect(response).to have_http_status(404)
expect(response.parsed_body).to have_jsonapi_errors
end
end

context "when authorized to delete grade" do
before do
token = get_auth_token(mentor)
delete "/api/v1/grades/#{grade.id}",
headers: { "Authorization": "Token #{token}" }, as: :json
end

it "delete group & return status no_content" do
expect { Grade.find(grade.id) }.to raise_exception(
ActiveRecord::RecordNotFound
)
expect(response).to have_http_status(204)
end
end
end
end
Loading

0 comments on commit ef8590c

Please sign in to comment.