Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 16 additions & 0 deletions app/controllers/items_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# app/controllers/items_controller.rb
class ItemsController < ApplicationController
def index
questionnaire = Questionnaire.find(params[:questionnaire_id])

Rails.logger.info "Items for Questionnaire #{questionnaire.id}:"
questionnaire.items.each do |item|
Rails.logger.info item.attributes.inspect
end

items_json = questionnaire.items.as_json
Rails.logger.info "JSON being rendered: #{items_json.inspect}"

render json: items_json
end
end
7 changes: 7 additions & 0 deletions app/controllers/question_types_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class QuestionTypesController < ApplicationController
Copy link
Member

Choose a reason for hiding this comment

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

Should be ItemTypesController

# GET /item_types
def index
question_types = QuestionType.all
render json: question_types
end
end
7 changes: 7 additions & 0 deletions app/controllers/questionnaire_types_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class QuestionnaireTypesController < ApplicationController
# GET /questionnaire_types
def index
questionnaire_types = QuestionnaireType.all
render json: questionnaire_types
end
end
66 changes: 48 additions & 18 deletions app/controllers/questionnaires_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,62 @@ def show
# Create method creates a questionnaire and returns the JSON object of the created questionnaire
# POST on /questionnaires
# Instructor Id statically defined since implementation of Instructor model is out of scope of E2345.
def create
begin
@questionnaire = Questionnaire.new(questionnaire_params)
@questionnaire.display_type = sanitize_display_type(@questionnaire.questionnaire_type)
@questionnaire.save!
render json: @questionnaire, status: :created and return
rescue ActiveRecord::RecordInvalid
render json: $ERROR_INFO.to_s, status: :unprocessable_entity
end
def create
begin
@questionnaire = Questionnaire.new(questionnaire_params)
@questionnaire.display_type = sanitize_display_type(@questionnaire.questionnaire_type)
@questionnaire.save!
render json: @questionnaire, status: :created and return
rescue ActiveRecord::RecordInvalid => e
render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity

rescue => e
render json: { error: e.message, backtrace: e.backtrace.take(5) }, status: :internal_server_error
end
end

# Destroy method deletes the questionnaire object with id- {:id}
# DELETE on /questionnaires/:id
def destroy
begin
@questionnaire = Questionnaire.find(params[:id])
@questionnaire.delete
rescue ActiveRecord::RecordNotFound
begin
@questionnaire = Questionnaire.find(params[:id])
@questionnaire.destroy! # ensures dependent items are removed
rescue ActiveRecord::RecordNotFound
render json: $ERROR_INFO.to_s, status: :not_found and return
end
end
end

# Update method updates the questionnaire object with id - {:id} and returns the updated questionnaire JSON object
# PUT on /questionnaires/:id

def update
@questionnaire = Questionnaire.find(params[:id])
if @questionnaire.update(questionnaire_params)
@questionnaire = Questionnaire.find(params[:id])

ActiveRecord::Base.transaction do
# Delete all existing items to avoid duplicate items
@questionnaire.items.destroy_all

# Update questionnaire attributes (excluding items)
if @questionnaire.update(questionnaire_params.except(:items_attributes))

# Re-create (already existing ones are deleted) items from submitted params
if questionnaire_params[:items_attributes].present?
questionnaire_params[:items_attributes].each do |item_param|
@questionnaire.items.create!(item_param.except(:id, :_destroy))
end
end

render json: @questionnaire, status: :ok
else
render json: @questionnaire.errors.full_messages, status: :unprocessable_entity
end
end
rescue ActiveRecord::RecordInvalid => e
render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity
rescue ActiveRecord::RecordNotFound
render json: { error: 'Questionnaire not found' }, status: :not_found
end

# Copy method creates a copy of questionnaire with id - {:id} and return its JSON object
# POST on /questionnaires/copy/:id
def copy
Expand Down Expand Up @@ -87,9 +110,16 @@ def toggle_access
private

def questionnaire_params
params.require(:questionnaire).permit(:name, :questionnaire_type, :private, :min_question_score, :max_question_score, :instructor_id)
params.require(:questionnaire).permit(:name, :questionnaire_type, :private,
:min_question_score, :max_question_score, :instructor_id,
items_attributes: [
:id, :txt, :question_type, :seq, :weight,
:size, :alternatives, :min_label, :max_label, :textarea_width, :textarea_height, :textbox_width, :col_names, :row_names,
:break_before, :_destroy, :max_value
])
end

# To match the expected format, replace a space by a for questionnaire types with 2 or more words
def sanitize_display_type(type)
display_type = type.split('Questionnaire')[0]
if %w[AuthorFeedback CourseSurvey TeammateReview GlobalSurvey AssignmentSurvey BookmarkRating].include?(display_type)
Expand All @@ -98,4 +128,4 @@ def sanitize_display_type(type)
display_type
end

end
end
12 changes: 8 additions & 4 deletions app/models/Item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class Item < ApplicationRecord
before_create :set_seq
belongs_to :questionnaire # each item belongs to a specific questionnaire
belongs_to :questionnaire, optional: true
has_many :answers, dependent: :destroy, foreign_key: 'item_id'
attr_accessor :choice_strategy

Expand All @@ -18,14 +18,18 @@ def scorable?
def scored?
question_type.in?(%w[ScaleItem CriterionItem])
end

def set_seq
self.seq = questionnaire.items.size + 1
end

def as_json(options = {})
super(options.merge({
only: %i[txt weight seq question_type size alternatives break_before min_label max_label created_at updated_at],
only: %i[
txt weight seq question_type size alternatives break_before
min_label max_label created_at updated_at textarea_width
textarea_height textbox_width col_names row_names
],
include: {
questionnaire: { only: %i[name id] }
}
Expand Down Expand Up @@ -73,4 +77,4 @@ def self.for(record)
# Cast the existing record to the desired subclass
klass.new(record.attributes)
end
end
end
7 changes: 7 additions & 0 deletions app/models/question_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class QuestionType < ApplicationRecord
Copy link
Member

Choose a reason for hiding this comment

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

Should be ItemType

# Validations
validates :name, presence: true, uniqueness: true

# Associations (if any later)
# has_many :questionnaires, foreign_key: :questionnaire_type, primary_key: :name
Copy link
Member

Choose a reason for hiding this comment

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

has_many: consider replacing by belongs_to:

end
11 changes: 6 additions & 5 deletions app/models/questionnaire.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
class Questionnaire < ApplicationRecord
belongs_to :instructor
has_many :items, class_name: "Item", foreign_key: "questionnaire_id", dependent: :destroy # the collection of questions associated with this Questionnaire
accepts_nested_attributes_for :items, allow_destroy: true
before_destroy :check_for_question_associations

validate :validate_questionnaire
validates :name, presence: true
validates :max_question_score, :min_question_score, numericality: true


# after_initialize :post_initialization
# @print_name = 'Review Rubric'
Expand All @@ -28,7 +29,7 @@ def symbol
def get_assessments_for(participant)
participant.reviews
end

# validate the entries for this questionnaire
def validate_questionnaire
errors.add(:max_question_score, 'The maximum item score must be a positive integer.') if max_question_score < 1
Expand Down Expand Up @@ -64,9 +65,9 @@ def self.copy_questionnaire_details(params)
questionnaire
end

# Check_for_question_associations checks if questionnaire has associated questions or not
# Check_for_question_associations checks if questionnaire has associated items or not
Copy link
Member

Choose a reason for hiding this comment

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

check_for_item_associations

def check_for_question_associations
if questions.any?
if items.any?
raise ActiveRecord::DeleteRestrictionError.new( "Cannot delete record because dependent questions exist")
end
end
Expand All @@ -82,4 +83,4 @@ def as_json(options = {})
hash['instructor'] ||= { id: nil, name: nil }
end
end
end
end
7 changes: 7 additions & 0 deletions app/models/questionnaire_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class QuestionnaireType < ApplicationRecord
# Validations
validates :name, presence: true, uniqueness: true

# Associations (if any later)
# has_many :questionnaires, foreign_key: :questionnaire_type, primary_key: :name
end
15 changes: 14 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@
end
end

resources :questionnaire_types, only: [:index] do
end

resources :question_types, only: [:index] do
end


resources :questions do
collection do
get :types
Expand All @@ -77,6 +84,12 @@
end
end

# config/routes.rb
resources :questionnaires do
resources :items, only: [:index]
end


resources :signed_up_teams do
collection do
post '/sign_up', to: 'signed_up_teams#sign_up'
Expand Down Expand Up @@ -149,4 +162,4 @@
get '/:participant_id/instructor_review', to: 'grades#instructor_review'
end
end
end
end
7 changes: 7 additions & 0 deletions db/migrate/20251013123456_add_textarea_width_to_items.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddTextareaWidthToItems < ActiveRecord::Migration[8.0]
def change
add_column :items, :textarea_width, :integer
add_column :items, :textarea_height, :integer
add_column :items, :textbox_width, :integer
end
end
6 changes: 6 additions & 0 deletions db/migrate/20251013123457_add_cols_rows_to_items.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddColsRowsToItems < ActiveRecord::Migration[8.0]
def change
add_column :items, :col_names, :string
add_column :items, :row_names, :string
end
end
5 changes: 5 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 65 additions & 23 deletions db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
# frozen_string_literal: true

begin
# Create an instritution
inst_id = Institution.create!(
name: 'North Carolina State University'
).id

Role.create!(id: 1, name: 'Super Administrator')
Role.create!(id: 2, name: 'Administrator')
Role.create!(id: 3, name: 'Instructor')
Role.create!(id: 4, name: 'Teaching Assistant')
Role.create!(id: 5, name: 'Student')

# Create an admin user
User.create!(
name: 'admin',
email: '[email protected]',
password: 'password123',
full_name: 'admin admin',
institution_id: 1,
role_id: 1
)
# Create an institution
inst = Institution.find_or_create_by!(
name: 'North Carolina State University'
)
inst_id = inst.id

# Create Roles
Role.find_or_create_by!(id: 1, name: 'Super Administrator')
Role.find_or_create_by!(id: 2, name: 'Administrator')
Role.find_or_create_by!(id: 3, name: 'Instructor')
Role.find_or_create_by!(id: 4, name: 'Teaching Assistant')
Role.find_or_create_by!(id: 5, name: 'Student')

# Create an admin user
User.find_or_create_by!(name: 'admin') do |user|
user.email = '[email protected]'
user.password = 'password123'
user.full_name = 'admin admin'
user.institution_id = 1
user.role_id = 1
end

# Check if we should generate random data
# We assume if instructors exist, we've already seeded random data
if User.where(role_id: 3).exists?
puts "Random data already seeded (Instructors found). Skipping..."
else
# Generate Random Users
num_students = 48
num_assignments = 8
Expand Down Expand Up @@ -122,7 +127,44 @@
team_id: team_ids[i%num_teams],
).id
end
end

questionnaire_type_names = [
'Review',
'Author feedback',
'Teammate review',
'Survey',
'Quiz',
'Bookmark rating',
'Teammate review',
'Assignment survey',
'Course evaluation',
'Global survey'
]

questionnaire_types = {}
questionnaire_type_names.each do |type_name|
questionnaire_types[type_name] = QuestionnaireType.find_or_create_by!(name: type_name)
end
puts "Created questionnaire types: #{questionnaire_types.keys.join(', ')}"

question_type_names = [
'Section header',
'Table header',
'Column header',
'Criterion',
'Text field',
'Text area',
'Dropdown',
'Multiple choice',
'Scale',
'Grid',
'Checkbox',
'Upload',
]

rescue ActiveRecord::RecordInvalid => e
puts e, 'The db has already been seeded'
question_types = {}
question_type_names.each do |type_name|
question_types[type_name] = QuestionType.find_or_create_by!(name: type_name)
end
puts "Created item types: #{question_types.keys.join(', ')}"