app/controllers/concerns/course/survey/reordering_concern.rb
# frozen_string_literal: true
module Course::Survey::ReorderingConcern
extend ActiveSupport::Concern
def reorder_sections
if valid_section_ordering?(ordered_section_ids)
update_sections_ordering(ordered_section_ids)
render_survey_with_questions_json
else
head :bad_request
end
end
def reorder_questions
if valid_question_ordering?(reorder_params)
update_questions_ordering(reorder_params)
render_survey_with_questions_json
else
head :bad_request
end
end
private
def ordered_section_ids
@section_ids ||= begin
integer_type = ActiveModel::Type::Integer.new
reorder_params.map { |id| integer_type.cast(id) }
end
end
def reorder_params
params.require(:ordering)
end
# Checks if the given list of section ids matches the survey sections ids.
#
# @param [Array<Integer>] proposed_ordering List of section ids
# @return [Boolean] true if the proposed ordering is valid
def valid_section_ordering?(proposed_ordering)
valid_section_ids?(proposed_ordering, require_all: true)
end
# Checks if a proposed question ordering is valid. The sections should belong to the current
# survey and each question for this survey should be present in the ordering.
#
# @param [Array<Array(Integer, Array<Integer>)>] proposed_ordering
# Each element in the second-level array consist of a section's id and an ordered array
# of question_ids for questions belonging to that section.
# @return [Boolean]
def valid_question_ordering?(proposed_ordering)
ordering_hash = proposed_ordering.to_h
section_ids = ordering_hash.keys
question_ids = ordering_hash.values.flatten
valid_section_ids?(section_ids) && valid_question_ids?(question_ids)
end
# Checks if an array of section_ids belong to this survey. If require_all is true,
# ensure all sections ids are included in the given array.
#
# @param [Array<Integer>] section_ids
# @return [Boolean]
def valid_section_ids?(section_ids, require_all: false)
given_set = section_ids.to_set
return false if given_set.size != section_ids.size
valid_set = @survey.sections.pluck(:id).to_set
require_all ? given_set == valid_set : given_set.subset?(valid_set)
end
# Checks if a given array of question_ids matches the list of question_ids for this survey.
#
# @param [Array<Integer>] question_ids
# @return [Boolean]
def valid_question_ids?(question_ids)
survey_question_ids = @survey.questions.order(id: :asc).pluck(:id)
question_ids.sort == survey_question_ids
end
# Persists a given section ordering for this survey.
#
# @param [Array<Integer>] ordering
def update_sections_ordering(ordering)
weights = ordering.map.with_index { |id, weight| [id, weight] }.to_h
Course::Survey::Section.transaction do
@survey.sections.each do |survey|
survey.update_attribute(:weight, weights[survey.id])
end
end
end
# Persists a given question ordering for this survey.
#
# @param [Array<Array(Integer, Array<Integer>)>] ordering
# Each element in the second-level array consist of a section's id and an ordered array
# of question_ids for questions belonging to that section.
def update_questions_ordering(ordering)
questions_hash = @survey.questions.to_h { |question| [question.id, question] }
Course::Survey::Question.transaction do
ordering.each do |section_id, question_ids|
question_ids.each_with_index do |question_id, index|
question = questions_hash[question_id]
update_question_ordering(question, index, section_id)
end
end
end
end
# Updates the weight and section_id for the given question.
#
# @param [Course::Survey::Question] question
# @param [Integer] weight
# @param [Integer] section_id
def update_question_ordering(question, weight, section_id)
attibutes = { weight: weight }
attibutes[:section_id] = section_id if question.section_id != section_id
raise ActiveRecord::Rollback unless question.update(attibutes)
end
end