Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEPRECATED PR]: structure flow to create the assignment #39

Closed
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions app/controllers/api/v1/assignments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,59 @@
module Api
module V1
class AssignmentsController < ApplicationController
module V1
class AssignmentsController < ApplicationController
before_action :validate_ids!, only: [:create]

def index
render json: 'not yet implemented', status: 501
def index
render json: 'not yet implemented', status: 501
end

# POST /courses/:course_id/lms/:lms_id/assignments
def create
course_to_lms = fetch_course_to_lms(params[:course_id], params[:lms_id])
return unless course_to_lms

return if assignment_exists?(course_to_lms, params[:name], params[:external_assignment_id])

create_and_render_assignment(course_to_lms, params[:name], params[:external_assignment_id])
end

def destroy
render json: 'not yet implemented', status: 501
end

private

def fetch_course_to_lms(course_id, lms_id)
course_to_lms = CourseToLms.find_by(course_id: course_id, lms_id: lms_id)
unless course_to_lms
render json: { error: 'No such Course_LMS association' }, status: :not_found
end
course_to_lms
end

def create
render json: 'not yet implemented', status: 501
def assignment_exists?(course_to_lms, assignment_name, external_assignment_id)
existing_assignment = Assignment.find_by(course_to_lms_id: course_to_lms.id, name: assignment_name, external_assignment_id: external_assignment_id)
if existing_assignment
render json: { message: 'Record already exists' }, status: :ok
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This render causes side effects which we always try to avoid. Instead, we should try to make all renders happen from the controller method which the route calls.

return true
end
false
end

def destroy
render json: 'not yet implemented', status: 501
def create_and_render_assignment(course_to_lms, assignment_name, external_assignment_id)
assignment = Assignment.new(course_to_lms_id: course_to_lms.id, name: assignment_name, external_assignment_id: external_assignment_id)
if assignment.save
render json: assignment, status: :created
else
render json: assignment.errors, status: :unprocessable_entity
end
end
Comment on lines +44 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say it might be better to include this in the controller method logic itself since it renders stuff. Since the method is clear about the fact that it renders, it makes it a bit clearer, but I still tend to avoid it since it is "side-effecty"


def validate_ids!
if params[:name].blank? || params[:external_assignment_id].blank? || params[:course_id].blank? || params[:lms_id].blank?
render json: { error: 'Params required' }, status: :bad_request
end
end
Comment on lines +53 to 57
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added a validation class to do some of this validation as well (eg: ensuring that they are integers). The reason why this is important is because we need to make sure we are not introducing any vulnerabilities with user provided input.

end
end
end
45 changes: 40 additions & 5 deletions app/controllers/api/v1/lmss_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,45 @@
module Api
module V1
class LmssController < BaseController
before_action :validate_name!, only: [:create]
before_action :validate_ids!, only: [:create]

# POST /courses/:course_id/lmss
def create
render :json => 'not yet implemented'.to_json, status: 501
course_id = params[:course_id] # course_id from the URL
lms_id = params[:lms_id] # lms_id from the body
external_course_id = params[:external_course_id] # external_course_id from the body

# Ensure the course exists
unless Course.exists?(course_id)
render json: { error: 'Course not found' }, status: :not_found
return
end

# Ensure the lms exists
unless Lms.exists?(lms_id)
render json: { error: 'Lms not found' }, status: :not_found
return
end

# Check if the entry already exists
existing_entry = CourseToLms.find_by(course_id: course_id, lms_id: lms_id, external_course_id: external_course_id)
if existing_entry
render json: { message: 'The association between the specified course and LMS already exists.' }, status: :ok
return
end

# Create the course_to_lms entry
course_to_lms = CourseToLms.new(
course_id: course_id,
lms_id: lms_id,
external_course_id: external_course_id
)

if course_to_lms.save
render json: course_to_lms, status: :created
else
render json: course_to_lms.errors, status: :unprocessable_entity
end
end

def index
Expand All @@ -20,9 +55,9 @@ def destroy
# TODO: this should be exported to its own (validator) class.
# TODO: this validation should also check the config file for the name of lms's.
#
def validate_name!
if params['name'].blank?
render :json => 'name parameter is required', status: 401
def validate_ids!
if params[:course_id].blank? || params[:lms_id].blank? || params[:external_course_id].blank?
render json: { error: 'course_id and lms_id are required' }, status: :bad_request
Comment on lines +58 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets also use the validation library for these as well

end
end
end
Expand Down
7 changes: 7 additions & 0 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# app/models/assignment.rb
class Assignment < ApplicationRecord
belongs_to :course_to_lms

validates :name, presence: true
validates :external_assignment_id, presence: true
end
6 changes: 6 additions & 0 deletions app/models/course.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# app/models/course.rb

class Course < ApplicationRecord


end
11 changes: 11 additions & 0 deletions app/models/course_to_lms.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# app/models/course_to_lms.rb
class CourseToLms < ApplicationRecord
# Association with Course and Lms
belongs_to :course
belongs_to :lms

# You can add validations here if needed, for example:
validates :course_id, presence: true
validates :lms_id, presence: true
end

5 changes: 5 additions & 0 deletions app/models/lms.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# app/models/lms.rb
class Lms < ApplicationRecord


end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class ChangeLmsIdToCourseToLmsIdInAssignments < ActiveRecord::Migration[7.1]
def change
remove_index :assignments, :lms_id
remove_column :assignments, :lms_id, :bigint
add_column :assignments, :course_to_lms_id, :bigint, null: false
add_index :assignments, :course_to_lms_id
add_foreign_key :assignments, :course_to_lmss, column: :course_to_lms_id
end
end
8 changes: 4 additions & 4 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_04_20_211809) do
ActiveRecord::Schema[7.1].define(version: 2024_04_20_232708) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "assignments", force: :cascade do |t|
t.bigint "lms_id"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "external_assignment_id"
t.index ["lms_id"], name: "index_assignments_on_lms_id"
t.bigint "course_to_lms_id", null: false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is supposed to be a reference to a join table, it should prob be a foreign key only

t.index ["course_to_lms_id"], name: "index_assignments_on_course_to_lms_id"
Comment on lines +22 to +23
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignment is owned by a course which is owned by an lms so shouldn't we use that relation to get this value instead of adding it to every assignment?

end

create_table "course_to_lmss", force: :cascade do |t|
Expand Down Expand Up @@ -89,7 +89,7 @@
t.index ["email"], name: "index_users_on_email", unique: true
end

add_foreign_key "assignments", "lmss"
add_foreign_key "assignments", "course_to_lmss"
add_foreign_key "course_to_lmss", "courses"
add_foreign_key "course_to_lmss", "lmss"
add_foreign_key "extensions", "assignments"
Expand Down
60 changes: 52 additions & 8 deletions spec/controllers/api/v1/assignments_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,72 @@
require 'rails_helper'
module Api
module V1
RSpec.describe AssignmentsController, type: :request do
let(:mock_course_id) { 1 }
let(:mock_lms_id) { 1 }
RSpec.describe AssignmentsController do
def json_response
JSON.parse(response.body)
end

let(:mock_course) { Course.create!(course_name: "Test Course") }
let(:mock_lms) { Lms.create!(lms_name: "Test LMS") }
let(:mock_course_to_lms) { CourseToLms.create!(course_id: mock_course.id, lms_id: mock_lms.id) }

let(:valid_params) { { name: "Test Assignment", external_assignment_id: "123ABC", course_id: mock_course.id, lms_id: mock_lms.id } }

before do
mock_course
mock_lms
mock_course_to_lms
end

after do
Assignment.delete_all
CourseToLms.delete_all
Course.delete_all
Lms.delete_all
end

describe "GET /api/v1/courses/:course_id/lmss/:lms_id/assignments" do
it "throws a 501 error" do
get "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments"
get :index, params: { course_id: mock_course.id, lms_id: mock_lms.id }
expect(response).to have_http_status(501)
end
end

describe "POST /api/v1/courses/:course_id/lmss/:lms_id/assignments" do
it "throws a 501 error" do
post "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments"
expect(response).to have_http_status(501)
context 'when valid parameters are provided' do
it 'creates a new assignment and returns status :created' do
post :create, params: valid_params
expect(response).to have_http_status(:created)
expect(json_response["name"]).to eq(valid_params[:name])
expect(json_response["external_assignment_id"]).to eq(valid_params[:external_assignment_id])
end
end

context 'when course_to_lms does not exist' do
it 'returns status :not_found' do
post :create, params: { course_id: -1, lms_id: -1, name: "Test Assignment", external_assignment_id: "123ABC" }
expect(response).to have_http_status(:not_found)
expect(response.body).to include('No such Course_LMS association')
end
end

context 'when the assignment already exists' do
it 'returns status :ok' do
Assignment.create!(course_to_lms_id: mock_course_to_lms.id, name: "Test Assignment", external_assignment_id: "123ABC")
post :create, params: valid_params
expect(response).to have_http_status(:ok)
expect(response.body).to include('Record already exists')
end
end



end

describe "DELETE /api/v1/courses/:course_id/lmss/:lms_id/assignments/:id" do
let(:mock_assignment_id) { 1 }
it "throws a 501 error" do
delete "/api/v1/courses/#{mock_course_id}/lmss/#{mock_lms_id}/assignments/#{mock_assignment_id}"
delete :destroy, params: { course_id: mock_course.id, lms_id: mock_lms.id, id: mock_assignment_id }
expect(response).to have_http_status(501)
end
end
Expand Down
75 changes: 60 additions & 15 deletions spec/controllers/api/v1/lmss_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,71 @@
module Api
module V1
describe LmssController do
let(:mock_course_id) { 16 }
let(:mock_course_name) { 'testCourseName' }
describe 'create' do
it 'throws a 501 error' do
post :create, params: {
course_id: :mock_course_id,
name: :mock_course_name,
}
expect(response.status).to eq(501)
def json_response
JSON.parse(response.body)
end

before do
# Manually create a course and LMS in the database
@course = Course.create!(course_name: "Mock CS169 Course")
@lms = Lms.create!(lms_name: "Mock Canvas", use_auth_token: true)
@external_course_id = "mock_external_course_id"
end

after do
# Clean up the specifically created data
CourseToLms.delete_all
Course.delete_all
Lms.delete_all
end

describe 'POST #create' do
context 'when valid parameters are provided' do
it 'creates a new course_to_lms association and returns status :created' do
post :create, params: { course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id}
expect(response).to have_http_status(:created)
expect(json_response['course_id']).to eq(@course.id)
expect(json_response['lms_id']).to eq(@lms.id)
end
end

it 'throws a 401 error if the name is not specified' do
post :create, params: {
course_id: :mock_course_id,
}
expect(response.status).to eq(401)
expect(response.body).to eq('name parameter is required')

context 'when lms_id is missing' do
it 'returns status :bad_request' do
post :create, params: { course_id: @course.id, external_course_id: @external_course_id}
expect(response).to have_http_status(:bad_request)
expect(response.body).to include('course_id and lms_id are required')
end
end

context 'when course does not exist' do
it 'returns status :not_found' do
post :create, params: { course_id: -1, lms_id: @lms.id, external_course_id: @external_course_id}
expect(response).to have_http_status(:not_found)
expect(response.body).to include('Course not found')
end
end

context 'when lms does not exist' do
it 'returns status :not_found' do
post :create, params: { course_id: @course.id, lms_id: -1, external_course_id: @external_course_id}
expect(response).to have_http_status(:not_found)
expect(response.body).to include('Lms not found')
end
end

context 'when the association already exists' do
it 'returns status :ok' do
CourseToLms.create!(course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id)
post :create, params: { course_id: @course.id, lms_id: @lms.id, external_course_id: @external_course_id}
expect(response).to have_http_status(:ok)
expect(response.body).to include('The association between the specified course and LMS already exists.')
end
end
end



describe 'index' do
it 'throws a 501 error' do
get :index, params: { course_id: :mock_course_id }
Expand Down
Loading