Skip to content

E2513: Reimplement sign_up_topic.rb as project_topic.rb #172

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-3.2.7
3.2.7
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ PLATFORMS
aarch64-linux
arm64-darwin-22
arm64-darwin-23
arm64-darwin-24
x64-mingw-ucrt
x86_64-linux

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/assignments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def show_assignment_details
end

# check if assignment has topics
# has_topics is set to true if there is SignUpTopic corresponding to the input assignment id
# has_topics is set to true if there is ProjectTopic corresponding to the input assignment id
def has_topics
assignment = Assignment.find_by(id: params[:assignment_id])
if assignment.nil?
Expand Down
84 changes: 84 additions & 0 deletions app/controllers/api/v1/project_topics_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
class Api::V1::ProjectTopicsController < ApplicationController
before_action :set_project_topic, only: %i[ show update ]

# GET /api/v1/project_topics?assignment_id=&topic_ids[]=
# Retrieve ProjectTopics by two query parameters - assignment_id (compulsory) and an array of topic_ids (optional)
def index
if params[:assignment_id].nil?
render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity
elsif params[:topic_ids].nil?
@project_topics = ProjectTopic.where(assignment_id: params[:assignment_id])
render json: @project_topics, status: :ok
else
@project_topics = ProjectTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids])
render json: @project_topics, status: :ok
end
# render json: {message: 'All selected topics have been loaded successfully.', project_topics: @stopics}, status: 200
end

# POST /project_topics
# The create method allows the instructor to create a new topic
# params[:project_topic][:topic_identifier] follows a json format
# The method takes inputs and outputs the if the topic creation was successful.
def create
@project_topic = ProjectTopic.new(project_topic_params)
@assignment = Assignment.find(params[:project_topic][:assignment_id])
@project_topic.micropayment = params[:micropayment] if @assignment.microtask?
if @project_topic.save
# undo_link "The topic: \"#{@project_topic.topic_name}\" has been created successfully. "
render json: { message: "The topic: \"#{@project_topic.topic_name}\" has been created successfully. " }, status: :created
else
render json: { message: @project_topic.errors }, status: :unprocessable_entity
end
end

# PATCH/PUT /project_topics/1
# updates parameters present in project_topic_params.
def update
if @project_topic.update(project_topic_params)
render json: { message: "The topic: \"#{@project_topic.topic_name}\" has been updated successfully. " }, status: 200
else
render json: @project_topic.errors, status: :unprocessable_entity
end
end

# Show a ProjectTopic by ID
def show
render json: @project_topic, status: :ok
end

# Similar to index method, this method destroys ProjectTopics by two query parameters
# assignment_id is compulsory.
# topic_ids[] is optional
def destroy
# render json: {message: @sign_up_topic}
# filters topics based on assignment id (required) and topic identifiers (optional)
if params[:assignment_id].nil?
render json: { message: 'Assignment ID is required!' }, status: :unprocessable_entity
elsif params[:topic_ids].nil?
@project_topics = ProjectTopic.where(assignment_id: params[:assignment_id])
# render json: @project_topics, status: :ok
else
@project_topics = ProjectTopic.where(assignment_id: params[:assignment_id], topic_identifier: params[:topic_ids])
# render json: @project_topics, status: :ok
end

if @project_topics.each(&:delete)
render json: { message: "The topic has been deleted successfully. " }, status: :no_content
else
render json: @project_topic.errors, status: :unprocessable_entity
end
end

private

# Use callbacks to share common setup or constraints between actions.
def set_project_topic
@project_topic = ProjectTopic.find(params[:id])
end

# Only allow a list of trusted parameters through.
def project_topic_params
params.require(:project_topic).permit(:topic_identifier, :category, :topic_name, :max_choosers, :assignment_id)
end
end
84 changes: 0 additions & 84 deletions app/controllers/api/v1/sign_up_topics_controller.rb

This file was deleted.

4 changes: 2 additions & 2 deletions app/controllers/api/v1/signed_up_teams_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ class Api::V1::SignedUpTeamsController < ApplicationController
# Retrieves sign_up_topic using topic_id as a parameter
def index
# puts params[:topic_id]
@sign_up_topic = SignUpTopic.find(params[:topic_id])
@signed_up_team = SignedUpTeam.find_team_participants(@sign_up_topic.assignment_id)
@project_topic = ProjectTopic.find(params[:topic_id])
@signed_up_team = SignedUpTeam.find_team_participants(@project_topic.assignment_id)
render json: @signed_up_team
end

Expand Down
4 changes: 2 additions & 2 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Assignment < ApplicationRecord
has_many :questionnaires, through: :assignment_questionnaires
has_many :response_maps, foreign_key: 'reviewed_object_id', dependent: :destroy, inverse_of: :assignment
has_many :review_mappings, class_name: 'ReviewResponseMap', foreign_key: 'reviewed_object_id', dependent: :destroy, inverse_of: :assignment
has_many :sign_up_topics , class_name: 'SignUpTopic', foreign_key: 'assignment_id', dependent: :destroy
has_many :project_topics , class_name: 'ProjectTopic', foreign_key: 'assignment_id', dependent: :destroy
has_many :due_dates, class_name: 'DueDate', foreign_key: 'due_date_id', dependent: :destroy, as: :parent
belongs_to :course, optional: true
belongs_to :instructor, class_name: 'User', inverse_of: :assignments
Expand Down Expand Up @@ -138,7 +138,7 @@ def staggered_and_no_topic?(topic_id)
#This method return the value of the has_topics field for the given assignment object.
# has_topics is of boolean type and is set true if there is any topic associated with the assignment.
def topics?
@has_topics ||= sign_up_topics.any?
@has_topics ||= project_topics.any?
end

#This method return if the given assignment is a team assignment.
Expand Down
2 changes: 1 addition & 1 deletion app/models/bookmark.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Bookmark < ApplicationRecord
belongs_to :user
# belongs_to :topic, class_name: "SignUpTopic"
# belongs_to :topic, class_name: "ProjectTopic"
has_many :bookmark_ratings
validates :url, presence: true
validates :title, presence: true
Expand Down
89 changes: 89 additions & 0 deletions app/models/project_topic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class ProjectTopic < ApplicationRecord
has_many :signed_up_teams, dependent: :destroy
has_many :teams, through: :signed_up_teams
belongs_to :assignment

# Ensures the number of max choosers is non-negative
validates :max_choosers, numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}

# Ensures topic name is present
validates :topic_name, presence: true

# Attempts to sign up a team for this topic.
# If slots are available, it's confirmed; otherwise, waitlisted.
# Also removes any previous waitlist entries for the same team on other topics.
def signup_team(team)
return false if signed_up_teams.exists?(team: team)
ActiveRecord::Base.transaction do
signed_up_team = signed_up_teams.create!(
team: team,
is_waitlisted: !slot_available?
)
remove_from_waitlist(team) unless signed_up_team.is_waitlisted?
true
end
rescue ActiveRecord::RecordInvalid
false
end

# Drops a team from this topic and promotes a waitlisted team if necessary.
def drop_team(team)
signed_up_team = signed_up_teams.find_by(team: team)
return unless signed_up_team
team_confirmed = !signed_up_team.is_waitlisted?
signed_up_team.destroy!
promote_waitlisted_team if team_confirmed
end

# Returns the number of available slots left for this topic.
def available_slots
max_choosers - confirmed_teams_count
end

# Checks if there are any open slots for this topic.
def slot_available?
available_slots.positive?
end

# Returns all SignedUpTeam entries (both confirmed and waitlisted).
def get_signed_up_teams
signed_up_teams
end

# Returns only confirmed teams associated with this topic.
def confirmed_teams
teams.joins(:signed_up_teams)
.where(signed_up_teams: { is_waitlisted: false })
end

# Returns only waitlisted teams ordered by signup time (FIFO).
def waitlisted_teams
teams.joins(:signed_up_teams)
.where(signed_up_teams: { is_waitlisted: true })
.order('signed_up_teams.created_at ASC')
end

private

# Returns the count of teams confirmed for this topic.
def confirmed_teams_count
signed_up_teams.confirmed.count
end

# Promotes the earliest waitlisted team to confirmed.
def promote_waitlisted_team
next_signup = SignedUpTeam.where(project_topic_id: id, is_waitlisted: true).order(:created_at).first
return unless next_signup

next_signup.update_column(:is_waitlisted, false)
remove_from_waitlist(next_signup.team)
end

# Removes waitlist entries for the given team from all other topics.
def remove_from_waitlist(team)
team.signed_up_teams.waitlisted.where.not(project_topic_id: id).destroy_all
end
end
7 changes: 0 additions & 7 deletions app/models/sign_up_topic.rb

This file was deleted.

51 changes: 50 additions & 1 deletion app/models/signed_up_team.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
class SignedUpTeam < ApplicationRecord
belongs_to :sign_up_topic
# Scope to return confirmed signups
scope :confirmed, -> { where(is_waitlisted: false) }

# Scope to return waitlisted signups
scope :waitlisted, -> { where(is_waitlisted: true) }

belongs_to :project_topic
belongs_to :team

# Validations for presence and uniqueness of team-topic pairing
validates :project_topic, presence: true
validates :team, presence: true,
uniqueness: { scope: :project_topic }

# Calls ProjectTopic's signup_team method to initiate signup
def self.signup_for_topic(team, topic)
topic.signup_team(team)
end

# Removes all signups (confirmed and waitlisted) for the given team
def self.remove_team_signups(team)
team.signed_up_teams.includes(:project_topic).each do |sut|
sut.project_topic.drop_team(team)
end
end

# Returns all users in a given team
def self.find_team_participants(team_id)
team = Team.find_by(id: team_id)
return [] unless team

team.users.to_a
end

# Returns all users in a given team that's signed up for a topic
def self.find_project_topic_team_users(team_id)
signed_up_team = SignedUpTeam.find_by(team_id: team_id)
return [] unless signed_up_team

signed_up_team.team.try(:users).to_a
end

# Returns project topic the given user signed up for
def self.find_user_project_topic(user_id)
user = User.find_by(id: user_id)
return [] unless user

ProjectTopic.joins(:signed_up_teams)
.where(signed_up_teams: { team_id: user.teams.pluck(:id) })
.distinct.to_a
end
end
Loading