CSC/ECE 517 Fall 2016/E1653. Fix and improve rubric criteria
E1653. Fix and Improve Rubric Criteria
Expertiza background
Expertiza is an open source web application based on Ruby on Rails framework. This application facilitates submission and peer review of assignments and team projects. Students can upload their assignments and URLs linked to their work on expertiza which can be reviewed by the instructor and peer reviewed by other students. Expertiza also expedites the process of selecting/assigning topics and choosing team mates for team projects. Instructors can also create and customize assignments for students and create review rubrics which are used by students for peer reviewing others' work.
Tasks identified
- Change allow_action? method of questionnaires controller to restrict unauthorized access to edit review rubrics. Only those Instructors who own the rubric or their Teaching Assistants should be allowed edit them.
- Display an error message when a user who is not the owner of a questionnaire attempts to edit it and redirect him back to the page he came from.
- Fix the working of import and export methods(dumping and loading criterion) in the Questionnaire controller.
- Perform feature testing for the import and export methods of questionnaire controller.
- Remove old and unused code related to rubric import and export.
- Write feature tests for criterion advice.
Modified/Created files
- questionnaires_controller.rb
- QuestionnaireHelper.rb
- Questionnaire.rb
- questionnaire_spec.rb
- spec/features/import_export_csv_oss/ (New folder created containing 3 test files)
Summary of implementation
New functionality
- An instructor can no longer change others' review rubrics but can only view them. If he attempts to do so, an error message will be displayed and he will be redirected back.
- Only those review rubrics can be modified by an instructor which are owned by him.
- A Teaching Assistant can modify only those review rubrics which are owned by the instructor under whom he works.
- A CSV file containing the questions with the correct column names can now be imported into the rubric.
- A rubric can also be exported in CSV format to a destination path on our local system.
Changes in source code
1. Changes in allow_action? method of the Questionnaires controller.
The action_allow? method earlier provided access to all users to modify or view any review rubric.
# Source code before implementation. def action_allowed? ['Super-Administrator', 'Administrator', 'Instructor', 'Teaching Assistant', 'Student'].include? current_role_name end
The action_allow? method now provides access to modify a rubric to those instructors who own the rubric or the Teaching Assistants who work under them. However, any user can view the contents of the review rubric.
# Source code after implementation. def action_allowed? case params[:action] when 'edit', 'update', 'delete', 'toggle_access' #Modifications can only be done by papertrail q= Questionnaire.find_by(id:params[:id]) owner_inst_id = q.instructor_id if(current_user.role_id==6) #Identifying if the current user is a Teaching Assistant current_ta = current_user; end b= (current_user.id == owner_inst_id) if(!current_ta.nil?) b = b or (current_ta.parent_id == owner_inst_id) end return b else #Allow all others ['Super-Administrator', 'Administrator', 'Instructor', 'Teaching Assistant', 'Student'].include? current_role_name end end
2. a. Modification of import method declared in the questionnaires controller.
The code initially was not using the read method to read the file data and was unable to import a file.
#source code before implementation def import @questionnaire = Questionnaire.find(params[:id]) file = params['csv'] @questionnaire.questions << QuestionnaireHelper.get_questions_from_csv(@questionnaire, file) end
The modified code now reads data from the file and calls the method get_questions_from_csv and passes the data to it. The method reads data in rows and saves each row as a question. It then saves each question.
#source code after implementation def import questionnaire_id = (params[:id]) begin file_data = File.read(params[:csv]) QuestionnaireHelper.get_questions_from_csv(file_data,params[:id]) #Questionnaire.import(file_data) redirect_to edit_questionnaire_path(questionnaire_id.to_sym), notice: "All questions have been successfully imported!" rescue redirect_to edit_questionnaire_path(questionnaire_id.to_sym), notice: $ERROR_INFO end end
b. Removing unwanted code and modifying the get_questions_from_csv method declared in the questionnaire controller.
The method get_questions_from_csv defined in the questionnaire helper was unable to read data from the file.
#source code before implementation def self.get_questions_from_csv(questionnaire, file) questions = [] custom_rubric = questionnaire.section == "Custom" CSV::Reader.parse(file) do |row| unless row.empty? i = 0 score = questionnaire.max_question_score q = Question.new q_type = QuestionType.new if custom_rubric q.true_false = false row.each do |cell| case i when CSV_QUESTION q.txt = cell.strip unless cell.nil? when CSV_TYPE unless cell.nil? q.true_false = cell.downcase.strip == Question::TRUE_FALSE.downcase q_type.q_type = cell.strip if custom_rubric end when CSV_PARAM if custom_rubric q_type.parameters = cell.strip if cell end when CSV_WEIGHT q.weight = cell.strip.to_i if cell else if score >= questionnaire.min_question_score and !cell.nil? a = QuestionAdvice.new(score: score, advice: cell.strip) if custom_rubric a = QuestionAdvice.new(score: questionnaire.min_question_score + i - 4, advice: cell.strip) score -= 1 q.question_advices << a end end i += 1 end q.save q_type.question = q if custom_rubric q_type.save if custom_rubric questions << q end end questions end
The unwanted code from the get_questions_from_csv method is now removed and the modified code successfully reads data from the csv file and saves questions present in the from of rows in the rubric.
#source code after implementation def self.get_questions_from_csv(file_data,id) CSV.parse(file_data, headers: true) do |row| # row.each do |cell| questions_hash = row.to_hash ques = Question.new(questions_hash) ques.questionnaire_id=id ques.save end # end CSV.parse end
3. Removing dysfunctional code from the QuestionnaireHelper and adding a new method in questionnaire.rb model
The create_questionnaire_csv method was not functional and was removed from the QuestionnaireHelper
def self.create_questionnaire_csv(questionnaire, _user_name) csv_data = CSV.generate do |csv| for question in questionnaire.questions # Each row is formatted as follows # Question, question advice (from high score to low), type, weight row = [] row << question.txt row << question.type row << question.alternatives || '' row << question.size || '' row << question.weight # loop through all the question advice from highest score to lowest score adjust_advice_size(questionnaire, question) for advice in question.question_advices.sort {|x, y| y.score <=> x.score } row << advice.advice end csv << row end end csv_data end
The to_csv function was added in the questionnaire.rb model class which converts an imported file to csv format
def to_csv(ques) questions = ques csv_data = CSV.generate do |csv| row = ['seq','txt','type','weight','size','max_label','min_label','alternatives'] csv << row for question in questions row = [] row << question.seq row << question.txt row << question.type row << question.weight row << question.size || '' row << question.max_label row << question.min_label row << question.alternatives csv << row end end end
Testing using RSpec
A set of test cases using the RSpec framework have been added to test the implemented functionalities. Test data from spec/features/import_export_csv_oss/ was used to run the test cases.
1. The following test case checks if the imported file is not empty.
describe 'Import questions from CSV' do it 'should not be an empty file', js: true do login_as("instructor6") visit '/questionnaires/1/edit' click_button "Import from CSV" expect(page).to have_content('No such file') end
2.The following test case validates an imported CSV file. The test case passes if all the questions get successfully imported.
it 'should be a valid CSV file', js: true do login_as("instructor6") visit '/questionnaires/1/edit' file_path=Rails.root+"spec/features/import_export_csv_oss/navjot.csv" attach_file('csv',file_path) click_button "Import from CSV" expect(page).to have_content('All questions have been successfully imported!') end
3. The following test case validates the names of all columns in the imported CSV file.
it 'should have valid column names', js: true do login_as("instructor6") visit '/questionnaires/1/edit' file_path=Rails.root+"spec/features/import_export_csv_oss/navjot (propername).csv" attach_file('csv',file_path) click_button "Import from CSV" expect(page).to have_content('unknown attribute') end